以前在看一些第三方源码的时候,时不时的能碰到一些关于运行时相关的代码。因而乎,就阅读了一些关于运行时的文章,感受写的都不错,写此篇文章为了记录一下,同时也从新学习一遍。app
objc_msgSend
,只有对象才能发送消息,所以以objc
开头。注意:在oc中,不管是实例对象仍是Class,都是id类型的对象让咱们来看看方法调用转化成运行时的代码,看看调用方法的真面目吧。函数
而后在main方法里面写上学习
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *obj = [[NSObject alloc] init]; } return 0; }
clang -rewrite-objc main.m
而后ls查看一下当前目录能够看到有一个main.cpp
文件,咱们用open main.cpp
打开该文件,能够看到一大串代码,咱们能够直接翻到底部,能够看到这样的代码:atom
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); } return 0; }
删除掉一些强制转换,将上面的代码简化后:spa
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSObject *obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); } return 0; }
总结:到这里,咱们能够看到调用方法的本质就是发送消息了,而且能够看到咱们写的命令行
NSObject *obj = [[NSObject alloc] init];上面这条语句发送了两次消息,第一次发送了
alloc
消息,第二次发送了init
消息。code
相信你们都知道分类是不能够添加属性的,不过咱们能够经过运行时,给分类动态的添加属性。orm
原理:给一个分类声明属性,其本质就是给这个类添加关联,并非直接把这个值的内存空间添加到类存空间。对象
咱们给NSObject
添加一个分类,而后声明一个name
属性。继承
#import <Foundation/Foundation.h> @interface NSObject (Extension) @property (nonatomic, copy) NSString *name; @end
NSObject
对象,而后给name
属性赋值,而且打印name
的值。接下来咱们在.m文件重写name
属性的setter
以及getter
方法。
#import "NSObject+Extension.h" #import <objc/runtime.h> static const char *key = "name"; @implementation NSObject (Extension) -(NSString *)name { // 根据关联的key,获取关联的值 return objc_getAssociatedObject(self, key); } -(void)setName:(NSString *)name { // 第一个参数:给哪一个对象添加关联 // 第二个参数:关联的key,经过这个key获取 // 第三个参数:关联的value // 第四个参数:关联的策略 objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end
运行程序,编译成功,运行也成功。
建立一个继承NSObject
的Student
类,声明一个study
方法。
#import <Foundation/Foundation.h> @interface Student : NSObject -(void)study; @end
建立一个Student
对象,调用study
方法。
Student *s = [[Student alloc] init]; [s performSelector:@selector(study)];
此时会出现一个经典的报错:
objc -[Student study]: unrecognized selector sent to instance 0x7fd719cbb2f0
错误缘由:调用一个未实现的实例方法
咱们在Student
类.m文件动态添加study方法的实现。
#import "Student.h" #import <objc/runtime.h> @implementation Student // void(*)() // 默认方法都有两个隐式参数, void studyStudent(id self, SEL sel){ NSLog(@"%@--%@",self,NSStringFromSelector(sel)); } // 当一个对象调用未实现的方法,会调用该方法处理,而且会把对应的方法列表传进来,咱们能够在这个方法里判断,未实现的方法是否是咱们想要动态添加的方法。 +(BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(study)) { // 第一个参数:给哪一个类添加方法 // 第二个参数:添加方法的方法编号 // 第三个参数:添加方法的函数实现(函数地址) // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd class_addMethod(self, @selector(study), studyStudent, "v@:"); // 注意:此处须要立刻结束此方法(不然,若是存在继承关系的话,会调用到父类去) return YES; } return [super resolveInstanceMethod:sel]; } @end
Method Swizzling
。场景二:系统自带的方法功能不够,给系统自带的方法扩展一些功能,而且保持原有功能。
场景一:统计整个项目每个页面停留时长。(解决方法也不是惟一的)
方式一:找到全部的控制器,而后写上:
-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:NSStringFromClass([self class])]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [MobClick endLogPageView:NSStringFromClass([self class])]; }
而后咱们一个项目可能有几十个甚至上百个页面须要统计,咱们总不可能每一个页面都这样写吧。因而乎,就有了方式二。
方式二:全部的界面都继承于一个基类,而后在基类中写上
-(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:NSStringFromClass([self class])]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [MobClick endLogPageView:NSStringFromClass([self class])]; }
但是一开始写项目的时候,并无使用到继承,因此又papapa地就整个项目的控制器都继承于一个基类,重复地将每个控制器的继承都该成了咱们建立的基类。可是,这样解决真的好么,有可能咱们有些界面是继承自UITableViewController
的,UICollectionViewController
,等等。那么你就可能会对这些控制器再单独的写上面的代码了。
好不容易将整个项目改过来了,而后某天,公司来了一位新人,你告诉他全部的类都要继承自你写的那个基类,新手老是会不经意地犯错误(也有多是人家尚未习惯),有些类忘记继承了,后期排查起来费力费时。那么有没有更好地解决方式呢?方式三就能够处理这种问题。
方式三:使用Method Swizzling
实现,给UIViewController
写一个分类。
#import "UIViewController+Help.h" #import <objc/runtime.h> @implementation UIViewController (Help) +(void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; methodSwizzling(class, @selector(viewWillAppear:), @selector(scott_viewWillAppear:)); methodSwizzling(class, @selector(viewWillDisappear:), @selector(scott_viewWillDisappear:)); }); } void methodSwizzling(Class class, SEL originSelector, SEL swizzSelector){ Method originMethod = class_getInstanceMethod(class, originSelector); Method swizzMethod = class_getInstanceMethod(class, swizzSelector); BOOL isAddMethod = class_addMethod(class, originSelector, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod)); if (isAddMethod) { class_replaceMethod(class, swizzSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod)); }else{ method_exchangeImplementations(originMethod, swizzMethod); } } -(void)scott_viewWillAppear:(BOOL)animated { [self scott_viewWillAppear:animated]; NSLog(@"调用自定义viewWillAppear"); } -(void)scott_viewWillDisappear:(BOOL)animated { [self scott_viewWillDisappear:animated]; NSLog(@"调用自定义viewWillDissappear"); } @end
这个单独开一篇给你们讲讲吧。
但愿经过本文能让你们学习到一些关于Runtime的知识,若是有什么疑问,欢迎你们一块儿讨论。