Runtime

作者: ANN_12 | 来源:发表于2021-05-14 09:35 被阅读0次

runtime介绍:

runtime 叫运行时, 是一套底层C语言的API。我们平时编写的OC代码都是基于runtime实现的。

因为我们程序在编译时期无法完成全部操作(方法的调用,类的创建),需要一个运行时的库,所以出现了runtime。

OC在编译时期不能决定真正调用那个函数,只有在运行的时候才会根据函数名找到对应的函数来调用

所以在编译阶段:OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。

Runtime的作用:

作用一:runtime消息传递

可以先看一下 实例对象、类对象、方法在runtime底层的表达形式:

//实例对象

struct objc_object {

Class isa;

};

//类对象

struct objc_class {

Class isa;

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;

struct objc_cache *cache;

struct objc_protocol_list *protocols;

endif

} ;

//方法列表

struct objc_method_list {

struct objc_method_list *obsolete;

int method_count;

ifdef LP64

int space;

endif

/* variable length structure */

struct objc_method method_list[1];

};

//方法

struct objc_method {

SEL method_name;

char *method_types;

IMP method_imp;

}

实例对象有isa指针 类对象有isa指针和superClass指针

对象调用方法 [obj foo] 的流程是:

底层运行时会被编译器转化为:objc_msgSend(obj, foo)

如果obj是实例对象.

第一步:obj通过isa指针找到Class

第二步:在Class的实例方法列表objc_method_list中查找有没有foo

第三步:如果没有,就在Class的父类superClass中找。

第四步:一直找到根类RootClass。

如果obj是类对象

第一步: obj通过isa指针找到自己的元类meta-class

第二步:在meta-class的类方法类别中查找有没有foo

第三步:如果没有,就在meta-superClass中找

第四步:一直找到meta-RootClass。

如果每一次调用方法 都这么查找的话,涉及到效率问题。

所以出现了 objc_cache。 记录缓存。这样可以先在缓存中找。

CategoryRuntime 中是用结构体 category_t 来表示的,

typedef struct category_t {

const char *name;//主类名字

classref_t cls;//类

struct method_list_t *instanceMethods;//实例方法的列表

struct method_list_t *classMethods;//类方法的列表

struct protocol_list_t *protocols;//所有协议的列表

struct property_list_t *instanceProperties;//添加的所有属性

} category_t;

也有很多人是这样说的

typedef struct objc_category *Category;

struct objc_category {

char * _Nonnull category_name;

char * _Nonnull class_name;

struct objc_method_list * _Nullable instance_methods;

struct objc_method_list * _Nullable class_methods;

struct objc_protocol_list * _Nullable;

} ;

对比objc_class 可以发现objc_category中少了

struct objc_ivar_list * _Nullable ivars

也就是说没有ivars数组.

变相的解释了,分类为什么不能添加成员。

可以添加属性,但是这个属性需要动态绑定,并且没有生成对应成员变量。

关于分类的问题:

为什么分类的方法优先级高于主类的方法?

因为category_t中的方法列表是插入到主类方法列表前面。所以先执行分类中的方法, 找到了对应的方法,就会停止查找,这里就造成了一种覆盖主类方法的假象。

category实现原理:

我们都知道OC所有的对象在运行时都是用结构体表示的。 category也是,用category_t表示。

1、在编译时期,将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t 。然后通过这些结构体生成一个结构体 category_t 。

2、然后将结构体 category_t 保存下来。

3、在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t。

4、然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中。

5、将结构体 category_t 中的类方法列表 添加到主类的 metaClass 中

引申:可以给协议protocol添加属性

需要在遵守协议的类中,实现属性的setter,getter方法。

作用二:Runtime消息转发

https://www.jianshu.com/p/6ebda3cd8052

如果消息传递,在方法列表中找不到对应的方法。那么就会进入消息转发。

从消息转发到程序报错前有三个机会:

1、动态方法解析

2、备用接收者(快速转发)

3、完整消息转发

动态方法解析

实现这两个方法+ (BOOL)resolveInstanceMethod:(SEL)sel;和 + (BOOL)resolveClassMethod:(SEL)sel;

  • (BOOL)resolveInstanceMethod:(SEL)sel {

if (sel == @selector(foo:)) {

class_addMethod([self class], sel, (IMP)fooMethod, "v@:");

return YES;

}

if (sel == @selector(fuu)) {

// 获取 class

Class predicateMetaClass = objc_getClass([NSStringFromClass(self) UTF8String]);

// 根据 class 获取方法的实现

IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(hahaha));

// 获取实例方法

Method predicateMethod = class_getInstanceMethod(predicateMetaClass, @selector(hahaha));

const char *encoding = method_getTypeEncoding(predicateMethod);

class_addMethod(predicateMetaClass, sel, impletor, encoding);

}

return [super resolveInstanceMethod:sel];

}

这里第一字符v代表函数返回类型void,第二个字符@代表self的类型id,第三个字符:代表_cmd的类型SEL。

void fooMethod(id obj, SEL _cmd) {

NSLog(@"Doing foo");//新的foo函数

}

  • (BOOL)resolveClassMethod:(SEL)sel {

if (sel == @selector(methodResolve)) {

// 获取 MetaClass

Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);

// 根据 metaClass 获取方法的实现

IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(proxyMethod));

// 获取类方法

Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(proxyMethod));

const char *encoding = method_getTypeEncoding(predicateMethod);

// 动态添加类方法

class_addMethod(predicateMetaClass, sel, impletor, encoding);

return YES;

}

return [super resolveClassMethod:sel];

}

  • (void)proxyMethod {

NSLog(@"---veryitman--- +proxyMethod of class's method for OC.");

}

备用接收者(快速转发)

  • (id)forwardingTargetForSelector:(SEL)aSelector {

if (aSelector == @selector(foo)) {

return [Person new];//返回Person对象,让Person对象接收这个消息

}

return [super forwardingTargetForSelector:aSelector];

}

通过创建一个新的对象,让这个新的对象去实现方法。

完整消息转发

首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。

如果-methodSignatureForSelector:返回nil ,

Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了。

如果返回了一个函数签名,Runtime就会创建一个NSInvocation 对象

并发送-forwardInvocation:消息给目标对象。

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {

return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation

}

return [super methodSignatureForSelector:aSelector];

}

  • (void)forwardInvocation:(NSInvocation *)anInvocation {

SEL sel = anInvocation.selector;

Person *p = [Person new];

if([p respondsToSelector:sel]) {

[anInvocation invokeWithTarget:p];

}

else {

[self doesNotRecognizeSelector:sel];

}

}

第二步跟第三步的区别:

1、需要重载的API方法的用法不同

前者只需要重载一个API即可,后者需要重载两个API。

前者只需在API方法里面返回一个新对象即可,

后者需要对被转发的消息进行重签并手动转发给新对象(利用 invokeWithTarget:)

2、转发给新对象的个数不同

前者只能转发一个对象,后者可以连续转发给多个对象。

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

if (aSelector==@selector(run)) {

return [NSMethodSignature signatureWithObjCTypes:"v@:"];

}

return [super methodSignatureForSelector: aSelector];

}

-(void)forwardInvocation:(NSInvocation *)anInvocation

{

SEL selector =[anInvocation selector];

RunPerson *RP1=[RunPerson new];

RunPerson *RP2=[RunPerson new];

if ([RP1 respondsToSelector:selector]) {

[anInvocation invokeWithTarget:RP1];

}

if ([RP2 respondsToSelector:selector]) {

[anInvocation invokeWithTarget:RP2];

}

}

Runtime在实际项目中的应用

1、关联对象 : 给分类添加属性

2、添加方法,交换方法 :KVO实现

3、消息转发:热更新JSPatch

4、实现NSCoding的自动归档和自动解档

5、实现字典和模型的自动转换

KVO的”isa-swizzling”技术,就是将指针原来指向本类,改成了指向中间类。

就是通过这个方法。

object_setClass(self, [SimpleKVO_Dog class]);

将self 改成 SimpleKVO_Dog类。这样isa指针指向SimpleKVO_Dog。

相关文章

网友评论

      本文标题:Runtime

      本文链接:https://www.haomeiwen.com/subject/hqafjltx.html