Objective-C:运行时runtime

简介:
1.是否可以把比较耗时的操作放在通知中心中?
 

通知在哪一个线程发的,那么对通知事件的处理就在同一个线程中进行;

如果在异步线程发的通知,那么可以执行比较耗时的操作;
如果在主线程发的通知,那么就不可以执行比较耗时的操作。
 
2.Foundation对象和CoreFoundation对象有什么区别?
Foundation对象时OC的;
CoreFoundation对象是C的;
Foundation对象和CoreFoundation对象是可以互相转换的,数据类型之间的转换
ARC:__bridge_retaind、__bridge_transfer、CFBridgingRetain、CFBridgingRelease
非ARC : __bridge
 
 
3.什么是runtime?

1> runtime是一套底层的C语言API(包含很多强大实用的C语言数据类型、C语言函数)

2> 实际上,平时我们编写的OC代码,底层都是基于runtime实现的

* 也就是说,平时我们编写的OC代码,最终都是转成了底层的runtime代码(C语言代码)

 

runtime有啥用?

1> 能动态产生一个类、一个成员变量、一个方法

2> 能动态修改一个类、一个成员变量、一个方法

3> 能动态删除一个类、一个成员变量、一个方法

 

常见的函数、头文件

#import <objc/runtime.h> : 成员变量、类、方法

Ivar * class_copyIvarList : 获得某个类内部的所有成员变量

Method * class_copyMethodList : 获得某个类内部的所有方法

Method class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)

Method class_getClassMethod : 获得某个类方法(加号+开头)

method_exchangeImplementations : 交换2个方法的具体实现

 

#import <objc/message.h> : 消息机制

objc_msgSend(….)

 

什么是iOS Swizzle? 利用运行时函数交换2个方法的实现

 

具体的距离如下:

1、测试运行时的消息机制:要在测试类头文件中导入<objc/message.h>

Person类:

复制代码
#import <UIKit/UIKit.h>

@interface Person : NSObject //<NSCoding>
@property (copy,nonatomic)NSString *name;
@property (assign,nonatomic)NSInteger age;
@property (assign,nonatomic)CGFloat height;
-(void)run;
@end


#import "Person.h"
#import <objc/runtime.h>

@implementation Person
-(void)run
{
    NSLog(@"run-----");
}

@end
复制代码

测试类:

复制代码
//测试运行时的消息机制
-(void)testMessage
{
    //<objc/message.h>
    Person *p = [[Person alloc]init];
    
    p.age = 20;
    objc_msgSend(p, @selector(setAge:),20);  // <====> [p setAge:20]
    NSLog(@"%zi",p.age);
    
    objc_msgSend(p, @selector(age)); //<======> [p age]
    
    [p run];
    objc_msgSend(p, @selector(run));  // <====> [p run]
}
复制代码

测试结果如下:

2016-01-29 13:16:27.397 Runtime-运行时[2881:130121] 20
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.398 Runtime-运行时[2881:130121] eat------
2016-01-29 13:16:27.399 Runtime-运行时[2881:130121] eat------

 2、获取运行时的成员属性

复制代码
//获取运行时的的成员属性
-(void)testRuntimeIvar
{
    //<objc/runtime.h>
    //Ivar:成员变量
    unsigned int count = 0;
    Ivar *ivars =  class_copyIvarList([Person class], &count);
    NSLog(@"%d",count);  //取得成员变量的数量
    for (int i=0; i<count; i++)
    {
        //取得i位置的成员变量
        Ivar ivar = ivars[i];
        
        //const char *ivar_getName(Ivar v) 获取属性名称
        const char *ivarName = ivar_getName(ivar);
        
        //const char *ivar_getTypeEncoding(Ivar v)  获取成员变量的类型
        const char *ivarType = ivar_getTypeEncoding(ivar);
        
        NSLog(@"%d %s %s",i,ivarName,ivarType);
    }
}
复制代码

测试结果如下:

2016-01-29 13:19:01.013 Runtime-运行时[2921:134136] 3
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 0 _name @"NSString"
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 1 _age q
2016-01-29 13:19:01.014 Runtime-运行时[2921:134136] 2 _height d

扩展:利用这个上面获取属性的这个方法,可以很轻松的实现对大量的类的属性进行归档和解归档

复制代码
//归档
-(void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    Ivar *ivars =  class_copyIvarList([Person class], &count);
    for (int i=0; i<count; i++)
    {
        Ivar ivar = ivars[i];
        const char *ivarName = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:ivarName];
        
        [encoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

//解归档
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if (self)
    {
        unsigned int count = 0;
        Ivar *ivars =  class_copyIvarList([Person class], &count);
        for (int i=0; i<count; i++)
        {
            Ivar ivar = ivars[i];
            const char *ivarName = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:ivarName];
            key = [decoder decodeObjectForKey:key];
        }
    }
    return self;
}
复制代码

3、获得运行时的成员方法

复制代码
//获取运行时的的成员方法
-(void)testRuntimeMethod
{
    //<objc/runtime.h>
    //Method: 成员方法
    unsigned int count = 0;
    Method *methods = class_copyMethodList([Person class], &count);
    NSLog(@"%d",count);  //取得成员方法的数量
    for (int i=0; i<count; i++)
    {
        //取得i位置的成员方法
        Method method = methods[i];
        
        //SEL method_getName(Method m) 获取方法名称
        SEL sel = method_getName(method);
        const char *selName =  sel_getName(sel);
        NSLog(@"%s",selName);
        
        //const char *method_getTypeEncoding(Method m)
        //Returns a string describing a method's parameter and return types
        const char *methodType = method_getTypeEncoding(method);
        NSLog(@"%s",methodType);
        
        //char *method_copyReturnType(Method m) 获取成员方法的返回值类型
        char *method_return_type = method_copyReturnType(method);
        NSLog(@"%s",method_return_type);
        
        //unsigned int method_getNumberOfArguments(Method m) 获取成员方法的参数个数
        count = method_getNumberOfArguments(method);
        NSLog(@"%zi",count);
    }
}
复制代码

测试结果如下:

 

复制代码
2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] 10
2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] setAge:
2016-01-29 13:21:33.081 Runtime-运行时[2975:138791] v24@0:8q16
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] v
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] 3
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] age
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] q16@0:8
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] q
2016-01-29 13:21:33.082 Runtime-运行时[2975:138791] 2
复制代码

4、获得运行时的协议

复制代码
-(void)testRuntimeProtocol
{
    //<objc/runtime.h>
    //Protocol:协议
    //unsigned int count = 0;
    //Protocol * __unsafe_unretained *protocol =  class_copyProtocolList([Person class], &count);
    //.........................
}
复制代码

5、在运行时中动态添加方法、属性、协议等

复制代码
-(void)testRuntimeAdd
{
    //动态添加方法
    //BOOL class_addMethod(Class cls, SEL name, IMP imp,const char *types)
    
    //动态替换方法
    //IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
    
    //动态添加成员变量
    //BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types)
    
    //动态添加协议
    //BOOL class_addProtocol(Class cls, Protocol *protocol)
    
    //动态添加属性
    //BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
    //动态替换属性
    //void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
    
    
    //........................
}
复制代码

6、在运行时中动态的交换两个实现方法

复制代码
-(void)testRuntimeChangedMethod
{
    //什么是iOS Swizzle? 利用运行时函数交换2个方法的实现
    
    //class_getClassMethod(__unsafe_unretained Class cls, SEL name)    //类方法
    //class_getInstanceMethod(__unsafe_unretained Class cls, SEL name) //实例方法
    //method_exchangeImplementations(Method m1, Method m2)             //动态交换实现方法
}
复制代码

 

下面我就来验证动态的交换两个实现方法:

例子一:动态交换类方法和实例方法的实现方法

<1>在Person类中声明和定义一个run方法

复制代码
#import <UIKit/UIKit.h>

@interface Person : NSObject 
-(void)run;
@end



#import "Person.h"
#import <objc/runtime.h>

@implementation Person
-(void)run
{
    NSLog(@"run-----");
}
复制代码

<2>在Person类扩展中定义eat方法和加载内存时交换这两个方法的实现

复制代码
#import "Person.h"
#import <objc/runtime.h>

@implementation Person (Extension)

//程序一运行就会加载
+(void)load
{
    //获取实例方法和类方法
    Method classMethod = class_getClassMethod([Person class], @selector(eat));
    Method instanceMethod = class_getInstanceMethod([Person class], @selector(run));
    
    //交换实例实现方法和类实现方法
    method_exchangeImplementations(classMethod, instanceMethod);
}

+(void)eat
{
    NSLog(@"eat------");
    Person *p = [[Person alloc]init];   //死循环
    [p run];
}
@end
复制代码

<3>测试如下:

    Person *p = [[Person alloc]init];
    [p run];

当Person类对象调用run方法时出现死循环:我只给出一部分结果

复制代码
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.268 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.269 Runtime-运行时[3200:161656] eat------
2016-01-29 13:38:22.269 Runtime-运行时[3200:161656] eat------
复制代码

解释原因:因为Person类一存在的时候,就会调用+(void)load方法,将run实例方法和eat类方法进行了交换,即实际上[p run]方法被没有执行,而是执行了[Person eat]方法,在[Person eat]中第一次输出NSLog(@"eat-------")后,紧接着又创建了一个新的Person对象,这个对象也调用了[p run]方法,就又调用了[Person eat]类方法,又一次输出

NSLog(@"eat-------"),一直如此循环下去...........

 

例子二:给OC内置的方法做手脚,用自定义的方法交换实现方法(以数组和可变数组为例)

<1>给NSObject、NSArray、NSMutableArray都创建扩展类。

在NSObejct扩展中创建两个类方法,用来交换实例方法和类方法

在NSArray扩展中创建两个方法,一个+(void)load方法实现交换,另一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:

和NSMutableArray创建三个方法,一个+(void)load方法实现交换,一个是自定义的用来覆盖OC内置的方法ObjectAtIndex:,还有一个用来覆盖OC内置的方法addObject:

如下:

复制代码
#import <UIKit/UIKit.h>
#import <objc/runtime.h>


@implementation NSObject(Extension)
+(void)swizlleClassMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
    //获取实例方法
    Method classMethod1 = class_getClassMethod(class, originMethod);
    Method classMethod2 = class_getClassMethod(class, otherMethod);
    
    //交换实例实现方法
    method_exchangeImplementations(classMethod1, classMethod2);
}

+(void)swizlleInstanceMethod:(Class)class originMethod:(SEL)originMethod otherMethod:(SEL)otherMethod
{
    //获取实例方法
    Method instanceMethod1 = class_getInstanceMethod(class, originMethod);
    Method instanceMethod2 = class_getInstanceMethod(class, otherMethod);
    
    //交换实例实现方法
    method_exchangeImplementations(instanceMethod1, instanceMethod2);
}
@end


@implementation NSArray(Extension)

//程序一运行就会加载
+(void)load
{
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)];
}
-(id)Test_objectAtIndex:(NSUInteger)index
{
    if (index < self.count)
    {
        //如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
        return [self Test_objectAtIndex:index];
    }
    else
    {
        //超界,返回空值
        return nil;
    }
}

@end


@implementation NSMutableArray(Extension)

//程序一运行就会加载
+(void)load
{
    //获取实例方法
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(addObject:) otherMethod:@selector(Test_addObject:)];
    
    //交换实例实现方法
    [self swizlleInstanceMethod:NSClassFromString(@"__NSArrayM") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)];

}

-(void)Test_addObject:(id)object
{
    if (object != nil)
    {
        //如果对象不为空,就调用交换后的系统的addObject:方法添加对象到可变数组中
        [self Test_addObject:object];
    }
}

-(id)Test_objectAtIndex:(NSUInteger)index
{
    if (index < self.count)
    {
         //如果索引小于数组个数,就调用交换后的系统的objectAtIndex:方法返回该位置的值
        return [self Test_objectAtIndex:index];
    }
    else
    {
        //超界,返回空值
        return nil;
    }
}
@end
复制代码

<2>测试:

声明数组:

@interface ViewController ()
@property (strong,nonatomic)NSMutableArray *names;
@property (strong,nonatomic)NSArray *books;
@end

NSArray:

    self.books = @[@"水浒传",@"西游记"];
    NSLog(@"%@",self.books[1]); //[self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1]
    NSLog(@"%@",self.books[4]); //[self.books objectAtIndex:4] --> [self.books Test_objectAtIndex:4]

测试结果:

2016-01-29 14:15:01.436 Runtime-运行时[3575:199688] 西游记
2016-01-29 14:15:01.437 Runtime-运行时[3575:199688] (null)

解释原因:

因为程序一运行,就调用了+(void)load中的[self swizlleInstanceMethod:NSClassFromString(@"__NSArrayI") originMethod:@selector(objectAtIndex:) otherMethod:@selector(Test_objectAtIndex:)]方法,

所以交换后就成了这种情况: self.books[1]---> [self.books objectAtIndex:1] --> [self.books Test_objectAtIndex:1]

明显的,1小于数组个数2,调用[self.books Test_objectAtIndex:1]-->[self.books objectAtIndex:1],返回@"西游记";

同理,由于self.books[4]的索引4大于数组个数2,所以返回null.

NSMutableArray:

复制代码
    [self.names addObject:@"jack"];
    [self.names addObject:nil];
    [self.names addObject:nil];
    [self.names addObject:@"rose"];
    [self.names addObject:@"jim"];
     NSLog(@"%@",self.names[7]);
    NSLog(@"%@",self.names);
复制代码

测试结果:

复制代码
2016-01-29 14:27:45.785 Runtime-运行时[3617:204316] (null)
2016-01-29 14:27:45.786 Runtime-运行时[3617:204316] (
    jack,
    rose,
    jim
)
复制代码

分析原理同上,这里说一点,明显的如果我们没有实现方法的交换,使用[self.name addobject:nil],程序肯定是会崩掉的,OC中是不允许向可变数组中添加空对象的,但是我们在这个方法上做一些手脚,换成自定义的方法后,可以消除这个bug,输出自己想要的结果;

 

最后还有一个知识,我们都知道,OC的分类中只能用来添加方法,是不能添加属性的。

但是有了运行时这个概念,动态的给分类添加属性也就不是事。

演示截图如下:

1.给分类添加属性:

2.对象分类属性进行关联:

发现编译一下,不报错,okay,成了。

 

 

 

 

 

 
程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!
本文转自当天真遇到现实博客园博客,原文链接:http://www.cnblogs.com/XYQ-208910/p/5024061.html如需转载请自行联系原作者
相关文章
|
7月前
|
编译器 Swift iOS开发
44 Swift和Objective-C的运行时简介
Swift和Objective-C的运行时简介
45 0
|
存储 安全 API
Objective-C Runtime 基本使用
在上一篇文章Objective-C Runtime详解 中我们探讨了Runtime的基本原理,这篇文章我们将总结一下Runtime的一些基本使用
140 0
|
存储 缓存 算法
Objective-C Runtime 详解
最近在学习Runtime的知识,恰巧发现了这篇博客[《Objective-C Runtime》](http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/),在此基础上,进行了些许补充说明,如有错误或其他想法,欢迎提出交流。
113 0
|
iOS开发 开发者
动态的Objective-C——关于消息机制与运行时的探讨(四)
动态的Objective-C——关于消息机制与运行时的探讨
119 0
|
iOS开发
动态的Objective-C——关于消息机制与运行时的探讨(三)
动态的Objective-C——关于消息机制与运行时的探讨
195 0
动态的Objective-C——关于消息机制与运行时的探讨(三)
|
iOS开发 开发者
动态的Objective-C——关于消息机制与运行时的探讨(二)
动态的Objective-C——关于消息机制与运行时的探讨
157 0
动态的Objective-C——关于消息机制与运行时的探讨(二)
|
缓存 自然语言处理 IDE
动态的Objective-C——关于消息机制与运行时的探讨(一)
动态的Objective-C——关于消息机制与运行时的探讨
151 0
动态的Objective-C——关于消息机制与运行时的探讨(一)
|
程序员 编译器 C#
Objective-C中runtime机制的应用(二)
Objective-C中runtime机制的应用
97 0
Objective-C中runtime机制的应用(二)
|
安全 编译器 iOS开发
Objective-C中runtime机制的应用(一)
Objective-C中runtime机制的应用
107 0
Objective-C中runtime机制的应用(一)
|
存储 缓存 iOS开发
深入Objective-C Runtime机制(一):类和对象的实现
1.概要      对于Runtime系统,相信大部分iOS开发工程师都有着或多或少的了解。对于Objective-C,Runtime系统是至关重要的,可以说是Runtime系统让Objective-C成为了区分于C语言,C++之外的一门独立开发语言,让OC在拥有了自己的面向对象的特性以及消息发送机制。并且因为其强大的消息发送机制,也让很多人认为Object
1954 0