Objective-C:运行时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,成了。