高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是OC并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从OC到C语言的过渡就是由runtime来实现的。然而我们使用OC进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。
RunTime简称运行时,OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。RunTime在开发的过程中是十分重要的却又是我们经常忽略的部分,由于它更接近底层技术,所以在平时的项目中用的不太多,每次接触这部分总是一知半解后,过段时间又给遗忘了,这篇文章就好好整理下:
首先创建一个Person类,具体代码如下:
1 #import <Foundation/Foundation.h> 2 3 @interface Person : NSObject 4 5 @property(nonatomic, strong)NSString *name; 6 7 - (void)eat; 8 - (void)play; 9 10 + (void)study; 11 12 @end 13 14 15 #import "Person.h" 16 #import <objc/message.h> 17 18 @implementation Person 19 20 - (void)eat{ 21 NSString *n = [NSString stringWithFormat:@"%@吃饭", self.name]; 22 NSLog(@"%@", n); 23 } 24 25 - (void)play{ 26 27 NSString *n = [NSString stringWithFormat:@"%@玩耍", self.name]; 28 NSLog(@"%@", n); 29 } 30 31 + (void)study{ 32 NSLog(@"学习"); 33 }
接下来我们好好介绍下RunTime的以下作用:
1、使用objc_msgSend发送消息
在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,这是是参数可变的函数,能接受两个和两个以上的参数,第一个参数代表接收者,第二个参数是选择子(SEL是选择子的类型),后续的参数就是消息中的那些参数,其顺序不变掌握好消息发送,可以让我们在编程中更方便灵活。其实每个类中都有一张方法列表去存储这个类中有的方法,当发出objc_msgSend方法时候,就会顺着列表去找这个方法是否存在,如果不存在,则向该类的父类继续查找,直到找到位置。如果始终没有找到方法,那么就会进入到消息转发机制;objc_msgSend被分为2个过程:1)在cache中寻找SEL。2)在MethodTable寻找SEL。
1 //消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现 2 Person *p= [[Person alloc]init]; 3 p.name = @"啦啦"; 4 [p eat]; 5 //让对象发送消息 6 //参数1: 消息接收的对象实例 7 //参数2: 要执行的方法 8 objc_msgSend(p, @selector(eat)); 9 //调用类方法 10 [Person study]; 11 [[Person class]study]; 12 objc_msgSend([Person class], @selector(study));
使用注意:使用objc_msgSend crash解决方案
2、使用Runtime交换方法
1 Person *p= [[Person alloc]init]; 2 p.name = @"啦啦"; 3 //获取方法地址 4 Method hEat = class_getInstanceMethod([Person class], @selector(eat)); 5 Method hPlay = class_getInstanceMethod([Person class], @selector(play)); 6 //交换方法地址,相当于交换实现方式 7 method_exchangeImplementations(hEat, hPlay);
3、动态添加方法
在Objective-C中,向一个对象发送它无法处理的消息时程序会崩溃,不过Objective-C提供了一种名为动态方法决议的手段,使得我们可以在运行时动态地为一个 selector 提供实现。我们只要实现 +resolveInstanceMethod: 或 +resolveClassMethod: 方法,并在其中为指定的 selector 提供实现即可(通过调用运行时函数 class_addMethod 来添加)
1 Person *p= [[Person alloc]init]; 2 p.name = @"啦啦"; 3 [p performSelector:@selector(eat)]; 4 5 6 7 + (BOOL)resolveInstanceMethod:(SEL)sel{
//[NSStringFromSelector(sel) isEqualToString:@"eat"] 8 if (sel == @selector(eat)) { 9 //动态添加eat方法 10 // 第一个参数:给哪个类添加方法 11 // 第二个参数:添加方法的方法编号 12 // 第三个参数:添加方法的函数实现(函数地址) 13 // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd 14 class_addMethod(self, sel, (IMP)dynamicMethodIMP, "v@:"); 15 return YES; 16 } 17 return [super resolveInstanceMethod:sel]; 18 } 19 20 void dynamicMethodIMP(id self, SEL _cmd) 21 { 22 NSLog(@"动态添加"); 23 }
在方法调用中,如果没有找到方法就会转向拦截调用。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
1 + (BOOL)resolveClassMethod:(SEL)sel; 2 + (BOOL)resolveInstanceMethod:(SEL)sel; 3 //后两个方法需要转发到其他的类处理 4 - (id)forwardingTargetForSelector:(SEL)aSelector; 5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
- 第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
- 第二个方法和第一个方法相似,只不过处理的是实例方法。
- 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
- 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
4、通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
1 /* 2 * 相关定义 3 typedef struct objc_method *Method; //描述类中的一个方法 4 typedef struct objc_ivar *Ivar; //实例变量 5 typedef struct objc_category *Category; //类别Category 6 typedef struct objc_property *objc_property_t; //类中声明的属性 7 */ 8 9 unsigned int count; 10 //获取属性列表 11 objc_property_t *propertyList = class_copyPropertyList([Person class], &count); 12 for (unsigned int i = 0; i < count; i++) { 13 const char *propertyName = property_getName(propertyList[i]); 14 NSLog(@"属性---->%@", [NSString stringWithUTF8String:propertyName]); 15 } 16 17 //获取方法列表 18 Method *methodList = class_copyMethodList([Person class], &count); 19 for (unsigned int i = 0; i < count; i++) { 20 Method method = methodList[i]; 21 NSLog(@"方法---->%@", NSStringFromSelector(method_getName(method))); 22 } 23 24 //获取成员变量列表 25 Ivar *ivarList = class_copyIvarList([Person class], &count); 26 for (unsigned int i = 0; i < count; i++) { 27 Ivar myIvar = ivarList[i]; 28 const char *ivarName = ivar_getName(myIvar); 29 NSLog(@"成员变量---->%@", [NSString stringWithUTF8String:ivarName]); 30 } 31 32 //获取协议列表 33 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &count); 34 for (unsigned int i = 0; i < count; i++) { 35 Protocol *myProtocal = protocolList[i]; 36 const char *protocolName = protocol_getName(myProtocal); 37 NSLog(@"协议---->%@", [NSString stringWithUTF8String:protocolName]); 38 } 39
5、关联对象给分类增加属性
众所周知,分类中是无法设置属性的,如果在分类的声明中写@property 只能为其生成get 和 set 方法的声明,但无法生成成员变量,就是虽然点语法能调用出来,但程序执行后会crash,有人会想到使用全局变量呢?但是全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就需要借助runtime为分类增加属性的功能了。
1 /**************/ 2 @interface NSObject (DefaultOJ) 3 4 @property (nonatomic, strong) NSString *name; 5 6 @end 7 8 @implementation NSObject (DefaultOJ) 9 10 @dynamic name; 11 12 static char kDefaultNameKey; 13 14 /** 15 关联对象Runtime提供了下面几个接口: 16 id object:被关联的对象 17 const void *key:关联的key,要求唯一 18 id value:关联的对象 19 objc_AssociationPolicy policy:内存管理的策略 20 OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。 21 OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。 22 OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。 23 OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。 24 OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。 25 26 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy p
olicy) //关联对象 27 id objc_getAssociatedObject(id object, const void *key) //获取关联的对象 28 void objc_removeAssociatedObjects(id object) //移除关联的对象 29 30 */ 31 32 - (void)setName:(NSString *)name{ 33 /*
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
参数 object:给哪个对象设置属性
参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
参数 value:给属性设置的值
参数policy:存储策略 (assign 、copy 、 retain就是strong)
*/
34 objc_setAssociatedObject(self, &kDefaultNameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 35 } 36 37 - (NSString *)name{
//利用参数key 将对象object中存储的对应值取出来 38 return objc_getAssociatedObject(self, &kDefaultNameKey); 39 } 40 41 @end 42 43 44 //使用 45 NSObject *nsOJ = [[NSObject alloc]init]; 46 nsOJ.name = @"测试"; 47 NSLog(@"%@", nsOJ.name);
6、方法替换(有问题)
1 @interface ViewController () 2 3 @end 4 5 6 @implementation ViewController 7 8 + (void)load{ 9 static dispatch_once_t onceToken; 10 dispatch_once(&onceToken, ^{ 11 Class class = [self class]; 12 SEL originalSelector = @selector(viewDidLoad); 13 SEL swizzledSelector = @selector(kzViewDidLoad); 14 15 Method originalMethod = class_getInstanceMethod(class, originalSelector); 16 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 17 18 BOOL isAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeE ncoding(swizzledMethod)); 19 if (isAddMethod) { 20 NSLog(@"替换"); 21 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(ori ginalMethod)); 22 }else{ 23 NSLog(@"交换"); 24 method_exchangeImplementations(originalMethod, swizzledMethod); 25 } 26 }); 27 28 29 } 30 31 - (void)kzViewDidLoad{ 32 [super viewDidLoad]; 33 NSLog(@"替换的方法"); 34 } 35 36 37 - (void)viewDidLoad { 38 [super viewDidLoad]; 39 // Do any additional setup after loading the view, typically from a nib. 40 self.title = @"测试"; 41 NSLog(@"自带的方法");
}
7、SEL、IMP和method动态定义
- SEL selector的简写,俗称方法选择器,实质存储的是方法的名称
- IMP implement的简写,俗称方法实现,看源码得知它就是一个函数指针
- Method 对上述两者的一个包装结构.
method相关的函数介绍:
//判断类中是否包含某个方法的实现 BOOL class_respondsToSelector(Class cls, SEL sel) //获取类中的方法列表 Method *class_copyMethodList(Class cls, unsigned int *outCount) //为类添加新的方法,如果方法该方法已存在则返回NO BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) //替换类中已有方法的实现,如果该方法不存在添加该方法 IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) //获取类中的某个实例方法(减号方法) Method class_getInstanceMethod(Class cls, SEL name) //获取类中的某个类方法(加号方法) Method class_getClassMethod(Class cls, SEL name) //获取类中的方法实现 IMP class_getMethodImplementation(Class cls, SEL name) //获取类中的方法的实现,该方法的返回值类型为struct IMP class_getMethodImplementation_stret(Class cls, SEL name) //获取Method中的SEL SEL method_getName(Method m) //获取Method中的IMP IMP method_getImplementation(Method m) //获取方法的Type字符串(包含参数类型和返回值类型) const char *method_getTypeEncoding(Method m) //获取参数个数 unsigned int method_getNumberOfArguments(Method m) //获取返回值类型字符串 char *method_copyReturnType(Method m) //获取方法中第n个参数的Type char *method_copyArgumentType(Method m, unsigned int index) //获取Method的描述 struct objc_method_description *method_getDescription(Method m) //设置Method的IMP IMP method_setImplementation(Method m, IMP imp) //替换Method void method_exchangeImplementations(Method m1, Method m2) //获取SEL的名称 const char *sel_getName(SEL sel) //注册一个SEL SEL sel_registerName(const char *str) //判断两个SEL对象是否相同 BOOL sel_isEqual(SEL lhs, SEL rhs) //通过块创建函数指针,block的形式为^ReturnType(id self,参数,...) IMP imp_implementationWithBlock(id block) //获取IMP中的block id imp_getBlock(IMP anImp) //移出IMP中的block BOOL imp_removeBlock(IMP anImp) //调用target对象的sel方法 id objc_msgSend(id target, SEL sel, 参数列表...)
下面来看看上述方法在实际中如何运用:
//创建继承自NSObject类的Person类 Class Person = objc_allocateClassPair([NSObject class], "People", 0); //将Person类注册到Runtime中 objc_registerClassPair(Person); //注册test: 方法选择器 SEL sel = sel_registerName("test:"); //函数实现 IMP imp = imp_implementationWithBlock(^(id this, id args, ...){ NSLog(@"方法的调用者为 %@",this); NSLog(@"参数为 %@",args); return @"返回值测试"; }); //向Person类中添加 test:方法;函数签名为@@:@, //第一个@表示返回值类型为id, //第二个@表示的是函数的调用者类型, //第三个:表示 SEL //第四个@表示需要一个id类型的参数 class_addMethod(Person, sel, imp, "@@:@"); //替换Person从NSObject类中继承而来的description方法 class_replaceMethod(Person, @selector(description), imp_implementationWithBlock(^NSString *(id this, ...){ return @"我是Person类的对象"; }), "@@:"); //完成 [[Person alloc]init]; id p1 = objc_msgSend(objc_msgSend(Person, @selector(alloc)), @selector(init)); //调用p1的sel选择器的方法,并传递@"???"作为参数 id result = objc_msgSend(p1, sel, @"???"); //输出sel方法的返回值 NSLog(@"sel 方法的返回值为 : %@",result); unsigned int methodCount; Method *methods = class_copyMethodList(Person, &methodCount); for (int i = 0; i < methodCount; i++) { NSLog(@"方法名称:%s",sel_getName(method_getName(methods[i]))); NSLog(@"方法Types:%s",method_getDescription(methods[i])->types); } free(methods);