iOS-运行时-runtime

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。它是 Objective-C 面向对象和动态机制的基石。

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。

消息传递机制

Alan Kay 曾多次强调 Smalltalk 的核心不是面向对象,面向对象只是the lesser ideas,消息传递才是the big idea

在很多语言,比如 C ,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo]语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

事实上,在编译时你写的 Objective-C 函数调用的语法都会被翻译成一个 C 的函数调用 -objc_msgSend()。比如,下面两行代码就是等价的:

[array insertObject:foo atIndex:5];

objc_msgSend(array,@selector(insertObject:atIndex:),foo,5);

消息传递的关键藏于objc_object中的 isa 指针和objc_class中的 class dispatch table。调度表。

在Objective-C中,类、对象和方法都是一个C的结构体,从objc/objc.h,头文件中。

isa指针:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。


struct objc_object {

Class isa  OBJC_ISA_AVAILABILITY;

};

struct  objc_class {

Class isa  OBJC_ISA_AVAILABILITY;

#if!__OBJC2__Class super_class;

constchar*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;/* a string representing argument/return types */

IMP method_imp;

};

objc_method_list 本质是一个有objc_method 元素的可变长度数组。一个 objc_method机构体中有函数名,也就是SEL,有表示函数类型的字符串,以及函数的实现IMP。

IMP:IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时(Objective-C runtime)寻找匹配此消息的IMP,然后调用它;但有些时候我们希望获取到IMP进行直接调用。

由于Method的内部结构不可见,所以不能通过method->method_name的方式访问其内部属性,只能Objective-C运行时提供的函数获取。

SEL method_getName(Method method);

IMP method_getImplementation(Method method);

const char * ivar_getTypeEncoding(Ivar ivar);

从这些定义中可以看出发送一条消息也就是objc_msgSend做了什么,例如:objc_msgsend(obj,foo):

1、首先,通过obj的isa找到它的class;

2、在class的method list 中找到 foo;

3、如果class中没有foo,继续往它的superClass中找;

4、一旦找到foo函数,就去执行他的IMP。

但是这种实现又个问题就是效率低,一个class中往往只有20%的函数会经常被调用,可能占总调用次数的80%。每个消息都要遍历一边,objc_method_list并不合理。如果把经常被调用的函数缓存下来,那就可以大大提升函数查询的效率。这就是objc_class 中另一个重要成员objc_cache做的事情。在找到foo之后,把method_name作为key,method_IMP作为value给存起来。当再次收到foo消息时候,可以直接在cache中找到,避免去遍历objc_method_list;

动态方法解析和转发

在上面的例子中,如果foo没有找到会发生什么?通常情况下,程序会在运行时crash,并抛出,unrecognized selector sent to ..异常,但是在抛出异常前,Object-c的运行时会给你三次拯救程序的机会:

1、Method resolution

2、Fast forwarding

3、Normal forwarding

Method resolution

首先Object-C运行时会调用 +resolveInstanceMehtod:或者+resolveClassMethod:,让你有机会提供一个函数的实现。如果你添加了函数并返回YES,那么运行时就会重新启动一次消息发送的过程。还是以foo为例,你可以这么实现:

void fooMethod(idobj, SEL _cmd){

NSLog(@"Doing foo");

}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL{

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

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

returnYES;

}

return [super resolveInstanceMethod];

}

PS:iOS4.3 加入很多新的runtime方法,主要都是以imp为前缀的方法,比如:imp_implementationWithBlock() 用block 快速创建一个imp。上面的例子可以重写成:

IMP fooIMP = imp_implementationWithBlack( ^(id _self)){

NSLog(@"Doing foo");

});

class_addMehtod([self class],aSEL,fooIMP,"v@:");

Core Data 有用到这个方法。NSManageObjects 中的properties 的getter和setter 就是在运行时动态添加的。

Message Forwarding

如果resolve方法饭回NO,运行时就会移到下一步:消息转发(Message Forwarding)

FastForwarding

如果目标对象实现了 -fowardingTargetForSelector:,Runtime这时候就会调用这个方法,给你一个这个消息转发给其他对象的机会。

- (id)forwardingTargetForSelector:(SEL)aSelector{

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

return alternateObject;

}

return [super forwardingTargetForSelector:aSelector];

}

只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Forwarding。这里叫Fast,只是为了区别下一步转发机制。因为这一步不会创建任何心的对象,但下一步转发会创建一个NSInvocatioin对象,所以相对更快一些。

Normal Forwarding

这一步是 Runtime 最后一次给你挽救的机会。首先它会发送

- methodSignatureForSelector:消息获得函数的参数和返回值类型。如果返回 nil,Runtime 则会发出

- doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送 -fowardInvocation:消息给目标对象。

NSInvocation 实际上就是对一个消息的描述,包括selector 以及参数等信息。所以你可以在- forwardInvocation:里修改传进来的NSInvocation对象

-(void)forwardInvocation:(NSInvocation *)invocation{

SEL sel = invocation.selector;

if (alternateObject respondsToSelector:sel){

[invocation invokeWithTarget:alternateObject]

}else{

[self doesNotRecognizeSelector:sel];

}

}

总结

Objective-C 中给一个对象发送消息会经过以下几个步骤

1、在对象类的dispatch table中尝试找到该消息。如果找到了,跳到相应的函数IMP去执行实现代码;

2、如果没有找到,Runtime 会发送+resolveInstanceMethod:或者+resolveClassMethod:尝试去 resolve 这个消息

3、如果 resolve 方法返回 NO,Runtime 就发送-forwardingTargetForSelector:允许你把这个消息转发给另一个对象。

4、如果没有新的目标对象返回, Runtime 就会发送-methodSignatureForSelector:和-forwardInvocation:消息。你可以发送-invokeWithTarget:消息来手动转发消息或者发送-doesNotRecognizeSelector:抛出异常

参考了:http://tech.glowing.com/cn/objective-c-runtime/

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

推荐阅读更多精彩内容