Runtime

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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容