iOSer总要了解Runtime的

如果别人问你什么是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实现协议中的initWithCoder和encodeWithCoder,class_copyIvarList遍历属性,ivar_getName转字符串,decodeObjectForKey解档转化成model,encodeObject: forkey归档成NSData,NSKeyedArchiver调用archivedDataWithRootObject:model归档,unarchiveObjectWithData:data解档。

方法交换 / 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添加新实现来替换。

你可能感兴趣的:(iOSer总要了解Runtime的)