iOS基础(八) - runtime之消息传递

前言:本来是想整理一下runtime相关的知识的,谁知道,越陷越深,一个知识点连着一个知识点,我怕以后忘记,所以,先记下来了,谁叫我比较懒呢。😄

1.iOS里面的函数调用其实就是消息转发的过程。(How to prove?)

先新建一个继承NSObject的类ClassA,里面有一个实例方法printStr:方法和类方法classMethod:,如下:

@implementation ClassA

+ (void)classMethod: (NSString *)str {
    NSLog(@"%@", str);
}

- (void)printSomething: (NSString *)str {
    NSLog(@"%@", str);
}

@end

//main.m 在main里调用
[ClassA classMethod: @"classMethod"];
ClassA *classA = [ClassA new];
[classA printStr: @"instanceMethod"];

将main.m编译成C++源文件

clang -rewrite-objc main.m

编译结果如下:

//类方法调用
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("classMethod:"), (NSString *)&__NSConstantStringImpl__var_folders_pn_xqk2zmnx559bmvnwm35rzb4h0000gn_T_main_2b6623_mi_0);

//实例方法调用
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)classA, sel_registerName("printStr:"), (NSString *)&__NSConstantStringImpl__var_folders_pn_xqk2zmnx559bmvnwm35rzb4h0000gn_T_main_2b6623_mi_1);

可以看出来,调用类方法和实例方法最后都会转换成objc_msgSend方法的调用,所以,objc_msgSend是何方神圣?别急,先找到objc/message.h头文件,如下:

/** 
 * Sends a message with a simple return value to an instance of a class.
 * 
 * @param self A pointer to the instance of the class that is to receive the message.
 * @param op The selector of the method that handles the message.
 * @param ... 
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method.
 * 
 * @note When it encounters a method call, the compiler generates a call to one of the
 *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
 *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
 *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
 *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
 */
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

可以看的出来,实例方法转换成了相应的消息传递。那么类方法呢?还记得我写的上一篇文章《聊一聊NSObject对象模型》里面的那张instance-class-metaClass之间的关系不?里面instance的isa指针指向的是class,而class的isa指针指向的是metaClass,我的理解就是可以把class看作metaClass的instance,那么类方法和实例方法的调用完全一致了。

2.消息传递的过程

根据苹果官方文档,消息转发的过程如下(这里说的是实例方法的调用,类方法类似):

1.通过实例的isa指针找到相应的class。
2.在cache(这里存放的都是函数实现,一旦函数被调用就会保存在缓存中)中以selector为key查找相应的函数imp,若找到则直接调用,否则进行下一步。
3.在table(这里存放的是类的所有的函数列表)中继续寻找相应的函数实现,若找到则直接调用,否则进行下一步。
4.通过class的superclass指针找到父类,回到第2步。
5.直到找到根类,还是没有找到相应的函数实现,则开始进行消息转发。

我们来看一下苹果官方文档的说明:

When a message is sent to an object, the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the dispatch table.
If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class.
Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.

To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used.
There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class.
Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again).
If the method selector is in the cache, messaging is only slightly slower than a function call.
Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.

第一段,主要讲了消息转发的整个过程,其中dispatch table就是一个函数分发列表,整个过程如下图:


messaging.png

第二段,主要讲的为了加速函数访问,类里面的函数一旦被调过之后会存进缓存里面,这样,访问函数先访问缓存,就可以提高访问速度。

3.消息的转发机制

在第二部的消息传递如果没有找到合适的对象处理当前的消息,则开始消息转发流程,如下:

消息转发.png

消息动态处理(resolveInstanceMethod)

void dynamicMethod(id self, SEL aSelector, id argument) {
    NSLog(@"%@", argument);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test:)) {
        class_addMethod([self class], sel, (IMP)dynamicMethod, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod: sel];
}
//参数:v -> void @ -> id  : -> selector
//也就是说dynamicMethod返回值为空,参数为对象的函数
//更多的资料请查询苹果官方文档《Type Encodings》

假如消息动态处理返回NO,则进入消息转发流程,响应目标转移(forwardingTargetForSelector)

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([ProxyObject instanceMethodForSelector: aSelector]) {
        return [ProxyObject new];
    }
    return [super forwardingTargetForSelector: aSelector];
}
//当前对象不能响应转发的消息,系统会寻找代替的对象响应改消息

假如消息响应目标返回nil,则系统会生成函数的签名,进行最后的处理(forwardInvocation)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([ProxyObject instancesRespondToSelector: aSelector]) {
        return [ProxyObject instanceMethodSignatureForSelector: aSelector];
    }
    return [super methodSignatureForSelector: aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([ProxyObject instancesRespondToSelector: anInvocation.selector]) {
        [anInvocation invokeWithTarget: [ProxyObject new]];
    } else {
        [super forwardInvocation: anInvocation];
    }
}
//方法签名为nil,则不会调forwardInvocation函数,直接调doesNotRecognizeSelector函数,报异常
//方法签名不为nil,则会调用forwardInvocation函数,进行最后的处理

直到最后消息都没有被处理,则调用doesNotRecognizeSelector函数,报异常

- (void)doesNotRecognizeSelector:(SEL)aSelector {
  ...
}

Tips:
1.消息转发的方法,默认会调父类响应的方法。
2.函数前面如果返回nil,则不会调用forwardInvocation方法,直接报异常。
3. resolveInstanceMethod方法,会在组装函数签名时重新被调用;如果函数签名返回nil,则不会被调用。

总结

这里都是一些关于runtime消息传递的比较浅显的解释,如果感兴趣想要更深入研究,推荐一篇文章《神经病院Objective-C Runtime住院第二天——消息发送与转发》里面讲的非常的详细,深入,不过,看懂的话有点难度,需要懂一点点汇编。

参考:

Objective-C Runtime Programming Guide
iOS runtime 之消息转发
Objective-C Runtime 运行时之三:方法与消息
iOS开发之Runtime运行时机制

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 2.Runtime储备知识 2.1.前言 在体验中,我写过这样一句话“并且我认为这个技术算是高阶开发里面一个...
    2897275c8a00阅读 574评论 1 2
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • 刮台风,但在办公室什么都不知道。 没有什么是过不去的,咬牙切齿的走下去吧。
    理想是不要加班阅读 137评论 0 0
  • 你远远地看着 看他人哭笑 看悲欢离合 黑暗的窥视 隐藏你的颜色 你知道你一直 都是那个卑微的旁观者 你在别人的故事...
    一玖酒八阅读 385评论 0 7