iOS runtime 学习分享
2015-12-18 09:25
323 查看
这是团队小伙伴在内部的一次技术分享, 很开心, 我们团队越来越好了.
申明: 部分资料来自于知名论坛和博客,已在文中给出相关源连接
前言
runtime
对象、消息、运行期之间的联系
从self入手
Class
[Meta Class](#Meta Class)
runtime源码地址
runtime简单的运用实例
对象关联
消息转发
方法调配
其他
总结
学习runtime是程序员进阶的一个必要的过程,不单单是oc,所有语言的编程学习到一定的水平,就要开始研究改语言底层的编译运行机制,我们这里所说的研究,并不是值要灵活使用底层的方法,而是去了解和熟知底层运行的机制,当然我现在的理解也是很浅显的,所以只能分享一些基础的学习内容,后续大家可以自行研究。
但是还有一个大家平常用不到的环节,当程序运行起来以后,为其提供相关支持的代码,就叫Object-C运行时(runtime)。
编译后的代码
上述编译后的代码可以看到两处个关键函数objc_msgSend和objc_msgSendSuper,看一下runtime源码中的这两个函数是如何定义的
里面的调用机制大体就是这样了,以上面的分析,回过头来看开始的代码,当输出[self class]和[super class]时,是个怎样的过程。
当使用[self class]时,这时的self是Son,在使用objc_msgSend时,第一个参数是receiver也就是self。第二个参数,要先找到class这个方法的selector,先从Son这个类开始找,没有,然后到Son的父类 Farther中去找,也没有,再去Farther的父类NSObject去找,一层一层向上找之后,在NSObject的类中发现这个class方法,而NSObject的这个class方法,就是返回receiver的类别,所以这里输出Son。
当使用[super class]时,这时要转换成objc_msgSendSuper的方法。先构造objc_super的结构体吧,第一个成员变量就是self, 第二个成员变量是Farther,然后要找class这个selector,先去superClass也就是Farther中去找,没有,然后去Farther的父类中去找,结果还是在NSObject中找到了。然后内部使用函数objc_msgSend(objc_super->receiver, @selector(class)) 去调用,此时已经和我们用[self class]调用时相同了,此时的receiver还是Son,所以这里返回的也是Son。
顺藤摸瓜就找到了 Class的定义
那么objc_class 又是什么呢? 大家会发现 objc_class里面的东西就多了
可以看到运行时一个类还关联了它的父类指针,类名,版本,信息,大小,成员变量列表,方法列表,缓存,还有附属的协议。
然后看一下变量和方法的结构体
可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。
换个说法
在Objective-C的设计哲学中,一切都是对象。Class在设计中本身也是一个对象。而这个Class对象的对应的类,我们叫它 Meta Class。即Class结构体中的 isa 指向的就是它的 Meta Class。
多说无益还是通过代码来理解吧
我在网上找了一些资料刨根问底Objective-C Runtime 地址
每个Class都有一个isa指针指向一个唯一的Meta Class
每一个Meta Class的isa指针都指向最上层的Meta Class(图中的NSObject的Meta Class)
最上层的Meta Class的isa指针指向自己,形成一个回路
每一个Meta Class的super class指针指向它原本Class的 Super Class的Meta Class。但是最上层的Meta Class的 Super Class指向NSObject Class本身
最上层的NSObject Class的super class指向 nil
为了更加清楚的知道整个函数调用过程,编译过后可获得如下代码:
先看前两个调用:
最外层是 objc_msgSend函数,转发消息。
函数第一个参数是 (id)((Class ()(id, SEL))(void )objc_msgSend)((id)objc_getClass(“NSObject”), sel_registerName(“class”))
函数第二个参数是转发的selector
函数第三个参数是 ((Class ()(id, SEL))(void )objc_msgSend)((id)objc_getClass(“NSObject”), sel_registerName(“class”))
我们注意到第一个参数和第三个参数对应重写的是[NSObject class],即使用objc_msgSend向 NSObject Class 发送 @selector(class) 这个消息
打开objc源代码,在 Object.mm 中发现+ (Class)class实现如下:
所以即返回Class类的对象本身。看如下输出:
继续打开objc源代码,在 Object.mm 中,我们发现 isKindOfClass的实现如下:
对着上面Meta Class的图和实现,我们可以看出
当 NSObject Class对象第一次进行比较时,得到它的isa为 NSObject的Meta Class, 这个时候 NSObject Meta Class 和 NSObject Class不相等。
然后取NSObject 的Meta Class 的Super class,这个时候又变成了 NSObject Class, 所以返回相等。
所以上述第一个输出结果是 YES 。
我们在看下 ‘isMemberOfClass’的实现:
综上所述,当前的 isa 指向 NSObject 的 Meta Class, 所以和 NSObject Class不相等。
所以上述第二个输出结果为 NO 。
继续看后面两个调用:
Son Class 的isa指向的是 Son的Meta Class,和Son Class不相等
Son Meta Class的super class 指向的是 NSObject Meta Class, 和 Son Class不相等
NSObject Meta Class的 super class 指向 NSObject Class,和 Son Class 不相等
NSObject Class 的super class 指向 nil, 和 Son Class不相等
所以后面两个调用的结果都输出为 NO 。
由于时间的原因,runtime的一些核心函数就讲到这里,下面附上runtime的源码
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
属性object、key、value都非常容易理解,oject是需要关联的对象,key,value是一套键值对。objc_AssociationPolicy这个属性大家可能是第一次见,这个是存储策略的值
讲到存储策略,我们来一起回顾一下oc对属性的存储策略,内存管理语义
assgin 一般只对纯量类型的简单赋值操作(NSInterger,int,enum…)。
strong 定义了一种有用关系,为这种属性设置新值时,设置方法会保留新值,并释放旧值,然后再将新值设置上去。
weak 定义了一种非拥有的关系,为这种属性设置新值时,设置方法会既不保留新值,也不会释放旧值,但是在所指对象销毁时,会清空该属性。
copy 和strong表达类似。不同的是设置方法并不保留新值,而是将其拷贝,这样可以保护属性的封装性,不用担心在不知情的情况下被修改,常用于NSString(当然自己写的类可以自己实现copy方法)。
为给定的键和策略为某对象设置关联对象值
objc_getAssociatedObject(id object, const void *key)
移除指定对象的全部关联对象
objc_removeAssociatedObjects(id object)
大家都知道oc是动态的语言,底层都是c语言实现的代码,所要调用的代码直到运行期才会确定,甚至可以在运行时改变调用方法,这些都是通过消息传递来实现的。
通俗一点Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。
消息传递函数原型
这个函数是消息传递机制的核心函数,self 是接收者,op是选择子。
objc_msgSend会依据相关的接受者和选择子来调用适当的方法,为了完成此操作,该方法需要在接受者所属的类中搜寻方法列表,如果找不到就会往父类找,再找不到就会执行消息转发(下面会讲)。可能这里大家会怀疑oc的执行效率,毕竟方法列表是灰常多的。oc在这里这里做了高效的缓存处理,这里我就不细讲了。
我们可以借助关联对象的源码来加深消息发送一下理解
消息发送其他相关函数大家感兴趣可以自己去了解一下,如:objc_msgSend_stret,objc_msgSend_fpret,objc_msgSendSuper等
推荐大家读一篇博客[译]Friday Q&A : 动手实现 objc_msgSend
该方法的参数就是消息传递里面那个未知的选择子,返回值为BOOL类型,表示这个类是否能新增一个实例方法用来处理这个选择子。resolveInstanceMethod处理实例方法,resolveClassMethod处理类方法。
该方法的参数就是消息传递里面那个未知的选择子,若当前接收者能找到备援对象,则将其返回,否则返回nil。
完整的消息转发可以参考大神的奇yin巧计博客地址
代码
消息转发的处理步骤如下:
接受者在每一步都有机会处理消息,越往后处理的代价就越大。所以最好是能在第一步处理完。
类的方法列表会把选择子的名称映射到相关的方法实现只想,使动态派发系统能够根据此找到应该调用的方法。这些方法都是用函数指针形式来表示。这种指针叫IMP原型如下
实现方法调配是通过两个步骤实现的,一是取出类对象方法列表里面相应的方法,二是交换类对象列表里面的方法。
实现简单的用例
这样的话,NSString 对象执行 uppercaseString 就变成 uppercaseString,反之亦然。 没见过这么坑人的。所以下面就贴一下大神在开普勒项目如何hook 解决掉别人的bug。
使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数
使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称
我就不一一总结了。大家可以自己去做深入学习,多看看runtime源码
iOS runtime 学习分享
Author:Liao Zusheng申明: 部分资料来自于知名论坛和博客,已在文中给出相关源连接
前言
runtime
对象、消息、运行期之间的联系
从self入手
Class
[Meta Class](#Meta Class)
runtime源码地址
runtime简单的运用实例
对象关联
消息转发
方法调配
其他
总结
前言
在开始分享之前,我也是犹豫了很长时间是否需要在团队里面分享runtime,因为runtime是一柄双刃剑,理解的好,用的好,就能写出一些高效实用的代码,用的不好,也能坑队友于无形之中。触发我想分享runtime的动力是源自于开普勒项目的一次BUG,然后遏制我这个想法的也是开普勒项目的一个BUG,所谓的双刃剑特性在一个小项目展示的淋漓尽致。学习runtime是程序员进阶的一个必要的过程,不单单是oc,所有语言的编程学习到一定的水平,就要开始研究改语言底层的编译运行机制,我们这里所说的研究,并不是值要灵活使用底层的方法,而是去了解和熟知底层运行的机制,当然我现在的理解也是很浅显的,所以只能分享一些基础的学习内容,后续大家可以自行研究。
runtime
对象、消息、运行期之间的联系
面向对象编程中,对象就是基本构造单元,在对象与对象之间传递数据并执行任务的过程就叫消息传递,一般情况下,我们开发人员做好这两点就可以了,剩下的事情就交给编译器以及底层的基库去完成。但是还有一个大家平常用不到的环节,当程序运行起来以后,为其提供相关支持的代码,就叫Object-C运行时(runtime)。
从self入手
先抛出一个问题,阅读源码,大家猜一下输出结果,并尝试讲出运行逻辑。[code]#import <Foundation/Foundation.h> #import "Father.h" @interface Son :Father @end @implementation Son - (instancetype)init{ self = [super init]; if (self){ NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
编译后的代码
[code]// @implementation Son static instancetype _I_Son_init(Son * self, SEL _cmd) { self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init")); if (self){ NSLog((NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_ef2320_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_ef2320_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class")))); } return self; } // @end
上述编译后的代码可以看到两处个关键函数objc_msgSend和objc_msgSendSuper,看一下runtime源码中的这两个函数是如何定义的
[code]#ifndef OBJC_SUPER #define OBJC_SUPER struct objc_super { id receiver; #if !defined(__cplusplus) && !__OBJC2__ Class class; /* For compatibility with old objc-runtime.h header */ #else Class super_class; #endif /* super_class is the first class to search */ }; #endif /* Basic Messaging Primitives * * On some architectures, use objc_msgSend_stret for some struct return types. * On some architectures, use objc_msgSend_fpret for some float return types. * On some architectures, use objc_msgSend_fp2ret for some float return types. * * These functions must be cast to an appropriate function pointer type * before being called. */ OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) __OSX_***AILABLE_STARTING(__MAC_10_0, __IPHONE_2_0); OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) __OSX_***AILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
里面的调用机制大体就是这样了,以上面的分析,回过头来看开始的代码,当输出[self class]和[super class]时,是个怎样的过程。
当使用[self class]时,这时的self是Son,在使用objc_msgSend时,第一个参数是receiver也就是self。第二个参数,要先找到class这个方法的selector,先从Son这个类开始找,没有,然后到Son的父类 Farther中去找,也没有,再去Farther的父类NSObject去找,一层一层向上找之后,在NSObject的类中发现这个class方法,而NSObject的这个class方法,就是返回receiver的类别,所以这里输出Son。
当使用[super class]时,这时要转换成objc_msgSendSuper的方法。先构造objc_super的结构体吧,第一个成员变量就是self, 第二个成员变量是Farther,然后要找class这个selector,先去superClass也就是Farther中去找,没有,然后去Farther的父类中去找,结果还是在NSObject中找到了。然后内部使用函数objc_msgSend(objc_super->receiver, @selector(class)) 去调用,此时已经和我们用[self class]调用时相同了,此时的receiver还是Son,所以这里返回的也是Son。
Class
首先来看看id的定义,id是指向一个类实例的指针[code]typedef struct objc_object *id;
顺藤摸瓜就找到了 Class的定义
[code]typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
那么objc_class 又是什么呢? 大家会发现 objc_class里面的东西就多了
[code]struct objc_class { Class isa; #if !__OBJC2__ Class super_class OBJC2_UN***AILABLE; const char *name OBJC2_UN***AILABLE; long version OBJC2_UN***AILABLE; long info OBJC2_UN***AILABLE; long instance_size OBJC2_UN***AILABLE; struct objc_ivar_list *ivars OBJC2_UN***AILABLE; struct objc_method_list **methodLists OBJC2_UN***AILABLE; struct objc_cache *cache OBJC2_UN***AILABLE; struct objc_protocol_list *protocols OBJC2_UN***AILABLE; #endif } OBJC2_UN***AILABLE;
可以看到运行时一个类还关联了它的父类指针,类名,版本,信息,大小,成员变量列表,方法列表,缓存,还有附属的协议。
然后看一下变量和方法的结构体
[code]struct objc_ivar { char *ivar_name OBJC2_UN***AILABLE; char *ivar_type OBJC2_UN***AILABLE; int ivar_offset OBJC2_UN***AILABLE; #ifdef __LP64__ int space OBJC2_UN***AILABLE; #endif } OBJC2_UN***AILABLE; struct objc_ivar_list { int ivar_count OBJC2_UN***AILABLE; #ifdef __LP64__ int space OBJC2_UN***AILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UN***AILABLE; } OBJC2_UN***AILABLE; struct objc_method { SEL method_name OBJC2_UN***AILABLE; char *method_types OBJC2_UN***AILABLE; IMP method_imp OBJC2_UN***AILABLE; } OBJC2_UN***AILABLE; struct objc_method_list { struct objc_method_list *obsolete OBJC2_UN***AILABLE; int method_count OBJC2_UN***AILABLE; #ifdef __LP64__ int space OBJC2_UN***AILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UN***AILABLE; } OBJC2_UN***AILABLE;
可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。
Meta Class
一个 ObjC 类同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西。当你发出一个类似[NSObject alloc]的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。你会说 NSObject 的子类时,你的类就会指向 NSObject 做为其超类。但是所有的元类都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc] 这条消息发给类对象的时候,objc_msgSend()会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。换个说法
在Objective-C的设计哲学中,一切都是对象。Class在设计中本身也是一个对象。而这个Class对象的对应的类,我们叫它 Meta Class。即Class结构体中的 isa 指向的就是它的 Meta Class。
多说无益还是通过代码来理解吧
我在网上找了一些资料刨根问底Objective-C Runtime 地址
[code] BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; BOOL res3 = [(id)[Son class] isKindOfClass:[Son class]]; BOOL res4 = [(id)[Son class] isMemberOfClass:[Son class]]; NSLog(@"%@ %@ %@ %@", res1 ? @"YES" : @"NO" , res2 ? @"YES" : @"NO" , res3 ? @"YES" : @"NO" , res4 ? @"YES" : @"NO"); //结果 2015-12-09 01:37:15.881 Test[26428:716711] YES NO NO NO
每个Class都有一个isa指针指向一个唯一的Meta Class
每一个Meta Class的isa指针都指向最上层的Meta Class(图中的NSObject的Meta Class)
最上层的Meta Class的isa指针指向自己,形成一个回路
每一个Meta Class的super class指针指向它原本Class的 Super Class的Meta Class。但是最上层的Meta Class的 Super Class指向NSObject Class本身
最上层的NSObject Class的super class指向 nil
为了更加清楚的知道整个函数调用过程,编译过后可获得如下代码:
[code]BOOL res1 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")), sel_registerName("isKindOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class"))); BOOL res2 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class")), sel_registerName("isMemberOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class"))); BOOL res3 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Son"), sel_registerName("class")), sel_registerName("isKindOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Son"), sel_registerName("class"))); BOOL res4 = ((BOOL (*)(id, SEL, Class))(void *)objc_msgSend)((id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Son"), sel_registerName("class")), sel_registerName("isMemberOfClass:"), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Son"), sel_registerName("class"))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_0, res1 ? (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_1 : (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_2 , res2 ? (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_3 : (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_4 , res3 ? (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_5 : (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_6 , res4 ? (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_7 : (NSString *)&__NSConstantStringImpl__var_folders_t6_gy06h2qd2bx0b09430gmfn158szr0t_T_son_73d230_mi_8);
先看前两个调用:
最外层是 objc_msgSend函数,转发消息。
函数第一个参数是 (id)((Class ()(id, SEL))(void )objc_msgSend)((id)objc_getClass(“NSObject”), sel_registerName(“class”))
函数第二个参数是转发的selector
函数第三个参数是 ((Class ()(id, SEL))(void )objc_msgSend)((id)objc_getClass(“NSObject”), sel_registerName(“class”))
我们注意到第一个参数和第三个参数对应重写的是[NSObject class],即使用objc_msgSend向 NSObject Class 发送 @selector(class) 这个消息
打开objc源代码,在 Object.mm 中发现+ (Class)class实现如下:
[code]+ (Class)class { return self; }
所以即返回Class类的对象本身。看如下输出:
[code]NSLog(@"%p", [NSObject class]); NSLog(@"%p", [NSObject class]); 2014-11-05 18:48:30.939 Test[11682:865988] 0x7fff768d40f0 2014-11-05 18:48:30.940 Test[11682:865988] 0x7fff768d40f0
继续打开objc源代码,在 Object.mm 中,我们发现 isKindOfClass的实现如下:
[code]- (BOOL)isKindOf:aClass { Class cls; for (cls = isa; cls; cls = cls->superclass) if (cls == (Class)aClass) return YES; return NO; }
对着上面Meta Class的图和实现,我们可以看出
当 NSObject Class对象第一次进行比较时,得到它的isa为 NSObject的Meta Class, 这个时候 NSObject Meta Class 和 NSObject Class不相等。
然后取NSObject 的Meta Class 的Super class,这个时候又变成了 NSObject Class, 所以返回相等。
所以上述第一个输出结果是 YES 。
我们在看下 ‘isMemberOfClass’的实现:
[code]- (BOOL)isMemberOf:aClass { return isa == (Class)aClass; }
综上所述,当前的 isa 指向 NSObject 的 Meta Class, 所以和 NSObject Class不相等。
所以上述第二个输出结果为 NO 。
继续看后面两个调用:
Son Class 的isa指向的是 Son的Meta Class,和Son Class不相等
Son Meta Class的super class 指向的是 NSObject Meta Class, 和 Son Class不相等
NSObject Meta Class的 super class 指向 NSObject Class,和 Son Class 不相等
NSObject Class 的super class 指向 nil, 和 Son Class不相等
所以后面两个调用的结果都输出为 NO 。
由于时间的原因,runtime的一些核心函数就讲到这里,下面附上runtime的源码
runtime 源码地址
下载地址runtime简单的运用实例
常见的runtime运用实例 | 推荐使用指数 |
---|---|
类对象关联 | |
消息转发 | ** |
方法调配 | 不推荐 |
对象关联
设置关联对象的值objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
属性object、key、value都非常容易理解,oject是需要关联的对象,key,value是一套键值对。objc_AssociationPolicy这个属性大家可能是第一次见,这个是存储策略的值
关联类型 | 等效的@property属性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic,retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic,copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
assgin 一般只对纯量类型的简单赋值操作(NSInterger,int,enum…)。
strong 定义了一种有用关系,为这种属性设置新值时,设置方法会保留新值,并释放旧值,然后再将新值设置上去。
weak 定义了一种非拥有的关系,为这种属性设置新值时,设置方法会既不保留新值,也不会释放旧值,但是在所指对象销毁时,会清空该属性。
copy 和strong表达类似。不同的是设置方法并不保留新值,而是将其拷贝,这样可以保护属性的封装性,不用担心在不知情的情况下被修改,常用于NSString(当然自己写的类可以自己实现copy方法)。
为给定的键和策略为某对象设置关联对象值
objc_getAssociatedObject(id object, const void *key)
移除指定对象的全部关联对象
objc_removeAssociatedObjects(id object)
[code]static const char *UIAlertView_Associated = "UIAlertView_Associated"; - (void)associatedAlert{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"title" message:@"message" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"confirm", nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex){ if(buttonIndex == 0){ NSLog(@"do cancel"); }else{ }NSLog(@"do confirm"); }; objc_setAssociatedObject(alert, UIAlertView_Associated, block, OBJC_ASSOCIATION_COPY_NONATOMIC); [alert show]; } - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ void (^block)(NSInteger) = objc_getAssociatedObject(alertView, UIAlertView_Associated); if(block) block(buttonIndex); }
消息转发
在讲消息转发前,先要理解什么是消息发送(objc_msgSend),什么时候触发消息发送。理解动态和静态
用一段代码来理解一下[code]- (void)doSomeThing:(int)type{ if(type == 1){ [self doSend]; }else{ [self doReceive]; } } - (void)doSomeThingDynamic:(int)type{ SEL doAnyThing = nil; if(type == 1){ doAnyThing = @selector(doSend); }else{ doAnyThing = @selector(doReceive); } if (doAnyThing) [self performSelector:doAnyThing]; } - (void)doReceive{ NSLog(@"reveive"); } - (void)doSend{ NSLog(@"send"); }
大家都知道oc是动态的语言,底层都是c语言实现的代码,所要调用的代码直到运行期才会确定,甚至可以在运行时改变调用方法,这些都是通过消息传递来实现的。
通俗一点Objc 中发送消息是用中括号([])把接收者和消息括起来,而直到运行时才会把消息与方法实现绑定。
消息传递函数原型
[code]id objc_msgSend(id self, SEL op, ...)
这个函数是消息传递机制的核心函数,self 是接收者,op是选择子。
[code]// oc 常见的方法 id returnValue = [someObject messageName:parameter]; //编译器转会称如下函数 id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
objc_msgSend会依据相关的接受者和选择子来调用适当的方法,为了完成此操作,该方法需要在接受者所属的类中搜寻方法列表,如果找不到就会往父类找,再找不到就会执行消息转发(下面会讲)。可能这里大家会怀疑oc的执行效率,毕竟方法列表是灰常多的。oc在这里这里做了高效的缓存处理,这里我就不细讲了。
我们可以借助关联对象的源码来加深消息发送一下理解
[code]void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { #if SUPPORT_GC if (UseGC) { if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) { value = objc_msgSend(value, SEL_copy); } auto_zone_set_associative_ref(gc_zone, object, (void *)key, value); } else #endif { // Note, creates a retained reference in non-GC. _object_set_associative_reference(object, (void *)key, value, policy); } }
消息发送其他相关函数大家感兴趣可以自己去了解一下,如:objc_msgSend_stret,objc_msgSend_fpret,objc_msgSendSuper等
推荐大家读一篇博客[译]Friday Q&A : 动手实现 objc_msgSend
消息转发
前面讲到了对象的消息传递机制,有提到过当对象接收到无法解读的消息后,就会启动消息转发。对象在收到无法解读的消息后,首先将调用所属类的类方法:[code]+ (BOOL)resolveInstanceMethod:(SEL)sel + (BOOL)resolveClassMethod:(SEL)sel
该方法的参数就是消息传递里面那个未知的选择子,返回值为BOOL类型,表示这个类是否能新增一个实例方法用来处理这个选择子。resolveInstanceMethod处理实例方法,resolveClassMethod处理类方法。
备援接受者
当接收到的选择子不能处理时,还可以转发给其他的接受者处理[code]- (id)forwardingTargetForSelector:(SEL)aSelector;
该方法的参数就是消息传递里面那个未知的选择子,若当前接收者能找到备援对象,则将其返回,否则返回nil。
完整的消息转发
这是转发的最后一步,首先创建NSInvocation 对象,把与尚未处理的消息有关的全部细节都封装在里面。这个对象包含了选择子、目标、参数等。[code]- (void)forwardInvocation:(NSInvocation *)anInvocation
完整的消息转发可以参考大神的奇yin巧计博客地址
代码
[code]- (void)forwardInvocation:(NSInvocation *)anInvocation{
if (strcmp(anInvocation.methodSignature.methodReturnType, "@") == 0)
{
anInvocation.selector = @selector(__uxy_nil);
[anInvocation invokeWithTarget:self];
return;
}
for (NSObject *object in XYNullObjects)
{
if ([object respondsToSelector:anInvocation.selector])
{
[anInvocation invokeWithTarget:object];
return;
}
}
[self doesNotRecognizeSelector:anInvocation.selector];
}
- (id)__uxy_nil
{
return nil;
}
消息转发的处理步骤如下:
接受者在每一步都有机会处理消息,越往后处理的代价就越大。所以最好是能在第一步处理完。
方法调配
既然对象的方法都是动态加载,自然就可以动态改变对象执行的方法,但是此方法过于残暴,一般情况下,建议慎重选择。类的方法列表会把选择子的名称映射到相关的方法实现只想,使动态派发系统能够根据此找到应该调用的方法。这些方法都是用函数指针形式来表示。这种指针叫IMP原型如下
[code]id (*IMP)(id,SEL,)
实现方法调配是通过两个步骤实现的,一是取出类对象方法列表里面相应的方法,二是交换类对象列表里面的方法。
[code]// 获取类对象的方法 Method class_getClassMethod(Class cls, SEL name); // 交换两个类对象方法 void method_exchangeImplementations(Method m1, Method m2)
实现简单的用例
[code]Method lowMethod = class_getClassMethod([NSString class], @selector(lowercaseString)); Method upMethod = class_getClassMethod([NSString class], @selector(uppercaseString)); method_exchangeImplementations(lowMethod, upMethod);
这样的话,NSString 对象执行 uppercaseString 就变成 uppercaseString,反之亦然。 没见过这么坑人的。所以下面就贴一下大神在开普勒项目如何hook 解决掉别人的bug。
[code]+ (void)load{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keplerAppDidFinishLaunchingNotification) name:UIApplicationDidFinishLaunchingNotification object:nil]; [self __swizzleInstanceMethodWithClass:NSClassFromString(@"__NSCFString") originalSel:@selector(objectForKey:) replacementSel:@selector(paObjectForKey:)]; } + (void)__swizzleInstanceMethodWithClass:(Class)clazz originalSel:(SEL)original replacementSel:(SEL)replacement { Method a = class_getInstanceMethod(clazz, original); Method b = class_getInstanceMethod([PAKepler class], replacement); if (class_addMethod(clazz, original, method_getImplementation(b), method_getTypeEncoding(b))) { class_replaceMethod(clazz, replacement, method_getImplementation(a), method_getTypeEncoding(a)); } else { method_exchangeImplementations(a, b); } } - (id)paObjectForKey:(id)key { return nil; }
其他
还有一些比较常用的runtime方法如:使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数
使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称
我就不一一总结了。大家可以自己去做深入学习,多看看runtime源码
总结
揭开runtime神秘面纱之后,我们不难发现,runtime并不是那么的难以理解和使用。但是,这些只是runtime比较粗浅的部分,真正研究透彻还需要花大量的时间,我们应该试着去理解oc runtime的运行机制,底层的设计思想,写出高效且易维护的代码,并不一定要给自己的代码冠上runtime之名,相比之下,花心思研究runtime,还不如去研究接口设计,代码重构,设计模式,编程思想等。相关文章推荐