如果别人问你什么是Runtime
Runtime,用C和汇编编写的运行时系统 ,实现了OC语言的动态性,支持了面向对象。
消息发送(传递)机制
在C语言中,函数的调用是在编译的时候就决定了,编译完成直接顺序执行无二义性;而OC的函数调用实际上是消息发送的过程,编译阶段并不能决定,属于动态调用(OC编译阶段可以调用任何函数即使它未实现,但C中会因为函数未实现而报错)
[receiver message];
objc_msgSend(receiver, @selector(message)); //Build Setting -> Enable Strict Checking of objc_msgSend Calls设置为NO后可直接调用
否则请使用:( (void (*) (id, SEL, id)) objc_msgSend ) (id, selector, id); //要对objc_msgSend强制转换,变成有参数和返回值的方法
解析:objc_msgSend方法执行时,@selector()获取到方法的SEL(唯一标识),然后去实例对象receiver或类的methodlist中去寻找方法的实现IMP(此过程会用到cache缓存提高查找效率,找不到还会去父类里面去找),找到之后还会把receiver和参数传递给IMP,最后IMP的返回即为函数的返回。多个类可能有共同的一个SEL但他们的IMP可能不同,但在一个类中,SEL和IMP是一对一的。所以,消息的调用其实是通过SEL找到IMP,并执行的过程。
注:OC分别通过OC源代码、Foundation框架和NSObject类三个层级中定义的方法,对runtime中的C语言函数进行直接调用,所以通常我们只需要写OC代码。
消息转发机制
当对象(包括实例和类对象)收到无法解析的方法时(即无法从methodlist中直接找到的SEL),会启用消息转发机制,如果不做任何处理,那程序则会以unrecognized selector sent to instance crush。而消息转发机制首先启用的,是动态方法解析。
动态方法解析:+(BOOL)resolveInstanceMethod:(SEL)sel / +(BOOL)resolveClassMethod:(SEL)sel,这两个方法的目的是在对象无法找到方法时,传入那个未知的SEL,给它机会用class_addMethod去动态添加方法的实现。即可以实现未找到的SEL与现有IMP的任意搭配。
+ (BOOL)resolveInstanceMethod:(SEL)selector {
NSString *selectorString = NSStringFromSelector(selector);
if ([selectorString hasPrefix:@"set"]) {
class_addMethod(self, selector, (IMP)autoDictionarySetter,"v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
如果通过动态方法解析还是没有获取到方法的实现(return NO),则启用第二种方案,备援接受者:- (id)forwardingTargetForSelector:(SEL)aSelector,即把这个无法处理的消息SEL,传递给别的对象去处理。
最后,只好启用基于NSInvocation的,完整的消息转发机制:- (void)forwardInvocation:(NSInvocation *)anInvocation,把所有未处理的target/selector/参数封装在anInvocation里并处理,处理方式多种多样(包括动态解析和备援接受者),甚至可以让一个消息触发多个对象的多个方法实现执行(转发给多个对象)。
三种方案循序渐进,代价逐渐增大。如果三种方案都无法处理这个消息的转发,则调用NSObject的doesNotRecognizeSelector抛出异常。
Runtime中的基本概念
obj.h中的Class、id、SEL
typedef struct objc_class *Class; //指向“类”(objc_class)这个结构体的指针,objc_class这个结构体里又包括指向它的元类的isa。
注:如果一个类的isa指针指向的类是它自己,则这个类就是元类。isa就是类指针。
typedef struct objc_object *id; //指向“对象实例”(objc_object)的指针,而objc_object结构里为指向这个实例的类的指针(Class isa)
注:objc_object中id是灵活的对象指针,可指向任何继承NSObject的对象且不需要强制转换。与instancetype,意义相同都是万能指针,指向一个对象,但instancetype可以在编译时就判断对象的真实类型,一般作为返回值写在构造函数(初始化/赋值函数)内。
typedef struct objc_selector *SEL; //指向“方法ID”(objc_selector)的指针,是方法名和参数的的唯一ID(而method包含了名字和实现)
注:objc_method结构体中就定义了SEL,char,IMP,IMP是指向实现方法的指针,即方法的实例,利用@selector()获取,可直接调用:
IMP methodPoint = [self methodForSelector:methodId];
methodPoint();
runtime.h中定义的成员
typedef struct objc_method *Method; //指向“类中的方法”的指针
注:Method指向的objc_method结构体中包含了SEL method_name、char *method_types、IMPmethod_imp三个属性。
typedef struct objc_ivar *Ivar; //指向“类中的变量”的指针
注:Ivar指向的objc_ivar结构体中包含了char *ivar_name、char *ivar_type、int ivar_offset,64位系统下还有int space等属性。
typedef struct objc_property *objc_property_t; //属性
typedef struct objc_category *Category; //扩展方法
一些runtime方法会用到上述成员做参数或返回值,如
Method *methodList = class_copyMethodList([self class], &count); //获取方法列表,作为返回值
method_exchangeImplementations(systemMethod, swizzMethod); //方法交换,作为参数
class结构体的定义
objc.h中定义了Class(结构体指针类型),其结构体的实现在runtime.h中,如下
struct objc_class {
Class isa; //Class 也有一个 isa 指针,指向其所属的元类(meta)
#if !__OBJC2__
Class super_class; //指向其超类
const char *name; //类名
long version; // 类的版本信息
long info; //类的详情
long instance_size; //该类实例对象的大小
struct objc_ivar_list *ivars; //指向该类的成员变量列表
struct objc_method_list **methodLists; //指向该类的实例方法列表,它将方法选择器和方法实现地址联系起来。methodLists 是指向 ·objc_method_list 指针的指针,也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因
struct objc_cache *cache; //Runtime 系统会把被调用的方法存到 cache 中,下次查找的时候效率更高
struct objc_protocol_list *protocols; //指向该类的协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
Runtime的常用方法及实际应用
获取属性列表(注属性property = ivar + getter + setter,ivar实例变量是用@synthesize声明的)
objc_property_t *propertyList = class_copyPropertyList([self class], &count); //count取地址,方法内部更改count获取属性个数
获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
实际应用
动态关联属性 / AssociatedObject(给任何NSObject对象添加自定义新属性)
NSObject+Property.h中添加新属性(newProperty),重写set方法,用objc_setAssociatedObject把属性关联给对象;重写get方法,objc_getAssociatedObject取出属性。
字典转模型 / MakeModel(将接口返回的json直接快速转化成mode,MJExtension)
NSObject添加扩展方法,modelWithDict,参数为传入字典dict和需要改变的属性名键值对字典updateDict,class_copyIvarList获取Ivar数组,循环,ivar_getName转字符串(注意成员变量默认带_,应从下标为1开始取),若源dic中无此属性值,则通过updateDict赋值,然后setValue ForKeyPath。
对象归解档 / ObjectArchive(存储属性较多的model对象时快速归解档)
model实现
方法交换 / MethodSwizzling(一般用于将系统类中的某个方法替换成自定义方法执行,如dealloc统一输出、全局埋点等)
class_getClassMethod获取当前类待替换的oriMehtod和自定义的cusMethod,然后执行method_exchangeImplementations。(直接执行前提是默认已知oriMehtod是类已经实现的方法)
方法拦截并替换 / 方法增加额外功能(如交换sendAction:to:forEvent:记录按钮点击)
class_getInstanceMethod获取两个方法oriMehtod、cusMethod(可以是不同类的),使用class_addMethod给源方法oriMehtod添加新的实现method_getImplementation(cusMethod)添加成功,class_replaceMethod替换为新方法的实现;添加失败,说明已有实现,则直接执行method_exchangeImplementations方法交换。
注:method_exchangeImplementations是当前类层级的,即交换类方法的IMP指针,而class_replaceMethod是跨类的,可以修改类。比如,如果源方法是在父类中实现的(UIButton的事件是在UIControl里实现的),则要通过addMethod添加新实现来替换。