iOS之RunTime浅谈
首先说一下什么是runtime:
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用
在编译的时候会决定调用哪个函数( C语言的函数调用请看这里
)。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编
译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找
到对应的函数来调用。OC的代码要创造并跑起来必不可少的是XCode的IDE和运行时框架。
举例说明:
比如你[obj makeText];
则运行时就这样的:首先,编译器将代码[obj
makeText];转化为objc_msgSend(obj, @selector
(makeText));,在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的class。在Class中先去cache中
通过SEL查找对应函数method(猜测cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若
cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加
入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
Runtime就是运行时,一个程序开发的过程通常可以分为以下阶段,编辑-预编译-编译-连接-运行,运行时可以说就是我们的程序再运行的阶段发生的一些事情,在这个阶段程序通常会把一些OC的代码转化成C语言的代码,从而提高执行的效率,在这个阶段我们也可以动态的为某个对象的属性赋值,而对象的属性具体是什么类型也会在这个阶段进行确定(NSString *str = [NSData data]; 其中str在编译的时候是NSString类型,运行的时候是NSData类型)。系统也提供了Runtime的类库,让我们可以直接调用一些运行时把OC代码转化C之后的代码比如:objc_msgSend();同样也可以通过运行时,为分类添加属性,需要用到objc_getAssociatedObject和objc_setAssociatedObject函数。
总结起来,iOS中的RunTime的作用有以下几点:
1.发送消息(obj_msgSend)
2.方法交换(method_exchangeImplementations)
3.消息转发
4.动态添加方法
5.给分类添加属性
6.获取到类的成员变量及其方法
7.动态添加类
下面我想通过一个小demo说一下方法交换:
首先我觉得既然运行时是可以实现方法的交换我觉得类似于切面编程中的切面处理逻辑(即在某一个执行过程时刻执行你的逻辑),这种情况可能需要在你扩充一些系统现有的方法的时候用到(把系统的方法扩展为自己的方法,自己的方法可以既有系统的方法还可以自己扩充一些自定义的东西)。另外一个是假如你在一个比较多逻辑的app中大量用到了同一个方法的逻辑块,突然你想要换掉这块逻辑,而依次换是不现实的,这时候你只需要知道方法的名字就可以利用运行时执行替换的方法来把原来的方法逻辑换成新的。
下面看代码:
首先建立一个项目(我们都知道页面在退出的时候或者生命周期哟啊结束的时候是会调用它的dealloc方法的,我们为了说明方法的替换就把系统的dealloc方法替换成我们自己的方法):
我觉得要实现方法的交换就要找好交换点,也就是那个切面的东西,这里好像一般的runtime都会选择写在你要交换方法的类的类目里,这里新建一个UIViewController的类目文件为UIViewController+MyExtension,.m文件里的实现如下:
#import "UIViewController+MyExtension.h" #import <objc/runtime.h> @implementation UIViewController (MyExtension) + (void)load{ Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc")); Method method2 = class_getInstanceMethod(self, @selector(my_dealloc)); method_exchangeImplementations(method1, method2); } - (void)my_dealloc{ NSLog(@"%@页面已经退出,这是我的实现...",self); }
因为所有的页面都会走load方法 所以选择切面点为重写的load方法中,class_getInstanceMethod为获得实例方法(就是减号方法),如果要获得类方法需要用class_getClassMethod,此时我们在UIViewController的类目里写方法,那么每个UIViewController都会走这个类目里重写的load方法,我们在页面销毁的时候就会走my_dealloc方法,先看一下程序的运行效果:
这个是MainController页面,点击下一页会推出FirstViewController:
此时点击back回退,那个这个时候FirstViewController页面就会被销毁,系统此时会自动钓鱼dealloc方法,而我们此时替换了dealloc方法,那么我们就会在控制台得到:
此时我们就实现了交换方法的目的,当然此时你在调用我自定义的方法的时候就恰好是执行的系统的方法,因为发生了交换。
下面再通过一个小demo说一下给分类添加属性:
首先还是建立一个项目:
本示例目的在于通过调用button类目里的一个block实现button的点击事件,需要建立一个类目文件:
UIButton+aa.h文件:
#import <UIKit/UIKit.h> //定义一个block 参数为NSIndexPath类型 返回值为void typedef void(^ButtonAction) (NSIndexPath *index); @interface UIButton (aa) @property (nonatomic,strong) NSIndexPath *index; //ButtonAction类型的block变量buttonAction @property (nonatomic,copy) ButtonAction buttonAction; @end
UIButton+aa.m文件:
#import "UIButton+aa.h" #import <objc/runtime.h> @interface UIButton() @end const void *key = "key"; const void *key1 = "key1"; @implementation UIButton (aa) //set get方法自己写 @dynamic index; @dynamic buttonAction; //Index属性的set方法 - (void)setIndex:(NSIndexPath *)index{ //给谁绑定 key值 绑定的值 //语义修饰 objc_setAssociatedObject(self, key, index, OBJC_ASSOCIATION_RETAIN); } //Index属性的get方法 - (NSIndexPath *)index{ return objc_getAssociatedObject(self, key); } //ButtonAction这个block属性的set方法 - (void)setButtonAction:(ButtonAction)buttonAction{ //这里在UIButton的类目里在block的set方法里给当前的self(UIButton对象)添加一个addTarget,当UIButton类型的对象调用这个ButtonAction的block时候相当于就是实现了一个点击事件 [self addTarget:self action:@selector(selfAction) forControlEvents:UIControlEventTouchUpInside]; //给谁绑定 key值 绑定的值 语义修饰 objc_setAssociatedObject(self, key1, buttonAction, OBJC_ASSOCIATION_COPY); } //ButtonAction这个block属性的get方法 - (ButtonAction)buttonAction{ return objc_getAssociatedObject(self, key1); } //实现点击事件 - (void)selfAction{ if (self.buttonAction) {//判断block有没有被初始化 self.buttonAction([NSIndexPath indexPathForRow:1 inSection:3]); } } @end
在ViewController里调用的时候:
- (void)viewDidLoad { [super viewDidLoad]; UIButton *but = [UIButton buttonWithType:(UIButtonTypeSystem)]; but.index = [NSIndexPath indexPathForRow:0 inSection:1]; but.buttonAction = ^(NSIndexPath *index){ NSLog(@"%@",index); }; but.frame = CGRectMake(100, 100, 100, 100); but.backgroundColor = [UIColor redColor]; [self.view addSubview:but]; }
此时页面运行效果为:
下面通过一段代码来演示获取类所有实例变量和所有方法:
//获取类所有实例变量 unsigned int ivarCount = 0; Ivar *ivarArray = class_copyIvarList([UIViewController class], &ivarCount); for (int i = 0; i < ivarCount; i++) { Ivar var = ivarArray[i]; NSLog(@"%s",ivar_getName(var)); NSLog(@"%s",ivar_getTypeEncoding(var)); }
//获取类所有方法 unsigned int mCount = 0; Method *methodArray = class_copyMethodList([UIViewController class], &mCount); for (int i = 0; i< mCount; i++) { Method method = methodArray[i]; SEL aSelector = method_getName(method); NSLog(@"%s",sel_getName(aSelector)); }
字符串和类名的相互转换:
//字符串和类名的相互转换 NSString *className = NSStringFromClass([UIViewController class]); Class ThisClass = NSClassFromString(className); id tc1 = [ThisClass new]; NSLog(@"%@",tc1);
用runtime实现归档反归档的简单写法:
我们这里以实现Person类的归档反归档为例,一般情况下我们要实现Person类的归档和反归档就要在类的实现里继承<NSCoding>协议并重写两个方法:- (instancetype)initWithCoder:(NSCoder *)coder 和 - (void)encodeWithCoder:(NSCoder *)coder方法。比较麻烦,而且也是只能对本Person类型起作用。我们知道所有的类的基类是NSObject类,所以我们根据runtime的思想,我们可以定义一个NSObject的类目AutoEncoding来实现在基类层面实现归档反归档:
NSObject类目AutoEncoding,.h文件:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface NSObject (AutoEncoding)<NSCoding> - (void)encodeWithCoder:(NSCoder *)aCoder; - (instancetype)initWithCoder:(NSCoder *)aDecoder; @end
NSObject类目AutoEncoding,.m文件:
#import "NSObject+AutoEncoding.h" @implementation NSObject (AutoEncoding) - (void)encodeWithCoder:(NSCoder *)aCoder{ unsigned int ivarCount = 0; Ivar *ivarArray = class_copyIvarList([self class], &ivarCount); for (int i = 0; i<ivarCount; i++) { NSString *key = [NSString stringWithUTF8String:ivar_getName(ivarArray[i])]; id value = [self valueForKey:key]; //归档 [aCoder encodeObject:value forKey:key]; } free(ivarArray); } - (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [self init]; if(self){ //RunTime获取类所有实例变量 unsigned int ivarCount = 0; Ivar *ivarArray = class_copyIvarList([self class],&ivarCount);//这里的self指的是子类 for (int i = 0; i<ivarCount; i++) { NSString *key = [NSString stringWithUTF8String:ivar_getName(ivarArray[i])]; //decoder根据实例变量名进行反归档 id value = [aDecoder decodeObjectForKey:key]; //KVC赋值 [self setValue:value forKey:key]; } free(ivarArray);//释放内存 } return self; } @end
RunTime里有动态方法解析:
RunTime的动态方法解析其实是掩耳盗铃式的 执行不存在的方法的时候调用,其实就是在调用不存在的方法的时候在那个切面时刻替换成自己的自定义方法:
Person *p = [Person new]; [p performSelector:@selector(sssss)];
这里sssss这个名字的方法不存在,在Person.m里捕捉到这个执行点:
//类方法动态解析 调用类方法的时候就会走这个方法 + (BOOL)resolveClassMethod:(SEL)sel{ //return [super resolveClassMethod:sel]; void(^methodBlock)() = ^(){ NSLog(@"%@的%s类方法没有实现",self,sel_getName(sel)); }; if(sel == @selector(sssss)){ //当遇到要执行ssss方法的时候需要替换成block方法(imp_implementationWithBlock把block变成指针) class_addMethod([[self class]class], sel, imp_implementationWithBlock(methodBlock),"v@:"); return YES; } return [super resolveInstanceMethod:sel]; } //实例方法动态解析 调用实例方法的时候就会走这个方法 + (BOOL)resolveInstanceMethod:(SEL)sel{ void(^methodBlock)() = ^(){ NSLog(@"%@的%s实例方法没有实现",self,sel_getName(sel)); }; if(sel == @selector(sssss)){ //当遇到要执行ssss方法的时候需要替换成block方法(imp_implementationWithBlock把block变成指针) class_addMethod([self class], sel, imp_implementationWithBlock(methodBlock),"v@:"); return YES; } return [super resolveInstanceMethod:sel]; }
RunTime实现消息转发(多代理模式):
新建一个类MultiTaskDelegate文件:
在.h文件里:
#import <Foundation/Foundation.h> @protocol CKDelegate <NSObject> - (void)justForFun; @end @interface MultiTaskDelegate : NSObject //添加代理 - (void)addDelegate:(id)delegate Queue:(NSOperationQueue *)queue; //移除代理 - (void)removeDelegate:(id)delegate; @end
在.m文件里:
#import "MultiTaskDelegate.h" #import <objc/runtime.h> NSString *const CKAssociatedKey = @"MultiDelegateKey";//存代理 NSString *const CKQueueKey = @"QueueKey";//存Queue @implementation MultiTaskDelegate //2.重写方法实现消息转发 //获取方法选标签名(重写系统的方法)一旦重写了此方法程序就会崩溃 所以要内部实现一个doNothing的方法 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ //代理 NSArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey)); for (id delegate in delegateArray) { if(delegate){ //返回对象后执行forwardInvocation方法 return [delegate methodSignatureForSelector:aSelector]; } } //没有delegate就执行doNothing方法 return [self methodSignatureForSelector:@selector(doNothing)]; } //3.实现消息转发(重写系统的方法) - (void)forwardInvocation:(NSInvocation *)anInvocation{ //取出来 NSArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey)); for(id delegate in delegateArray){ NSOperationQueue *queue = objc_getAssociatedObject(self, (__bridge const void *)(CKQueueKey)); //执行代理方法 [queue addOperationWithBlock:^{ [anInvocation invokeWithTarget:delegate];//指定代理去执行方法 }]; } } //什么都不做的方法 - (void)doNothing{ } //1.添加代理 - (void)addDelegate:(id)delegate Queue:(NSOperationQueue *)queue{ NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey)); if(!delegateArray){ delegateArray = [NSMutableArray new]; } [delegateArray addObject:delegate]; //添加代理 objc_setAssociatedObject(self, (__bridge const void *)CKAssociatedKey,delegateArray,OBJC_ASSOCIATION_RETAIN_NONATOMIC); //添加线程队列 objc_setAssociatedObject(self, (__bridge const void *)(CKQueueKey), queue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } //移除代理 - (void)removeDelegate:(id)delegate{ NSMutableArray *delegateArray = objc_getAssociatedObject(self, (__bridge const void *)(CKAssociatedKey)); if([delegateArray containsObject:delegate]){ [delegateArray removeObject:delegate]; } }
调用处:
#import "ViewController.h" #import <objc/runtime.h> #import "Person.h" #import "MultiTaskDelegate.h" @interface ViewController ()<CKDelegate>//实现一个协议 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; #pragma mark 消息转发 //多代理 MultiTaskDelegate *mObject = [MultiTaskDelegate new]; [mObject addDelegate:self Queue:[NSOperationQueue mainQueue]];//指定代理 可以一直添加 Person *p = [Person new]; [mObject addDelegate:p Queue:[NSOperationQueue mainQueue]];//添加多个代理 这里Person类也继承了协议CKDelegate
[mObject performSelector:@selector(justForFun)];//此时就让ViewController执行了代理方法}
//实现代理方法 - (void)justForFun{ NSLog(@"%@执行代理方法",self);
}
@end