Objective-C的哪些特性利用了运行时机制

这是我遇到的一道面试题。虽然当时回答得一般,但是我很喜欢这道题目,因为它可以考察出面试者对运行时机制理解的深度和广度,所以这里想试着重新回答一下。

现在的回答可能还很浅薄,如果以后想到了什么能补充的地方也会更新这篇( ̄▽ ̄)

本文中关于runtime的源码参考自苹果开源的objc4-680


Method Swizzling



在一些静态语言比如C语言中,调用一个函数后会跳到哪个地址执行哪些指令,是在编译链接的时候确定的。而在Objective-C中,向一个对象发送消息时,具体会执行哪个方法,则是运行时系统根据selector查找对应的IMP得到的。

所以通过交换两个selector对应的IMP,可以完成对执行的具体方法的调换。


Associated Object



使用

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object)

这三个方法,可以在运行时为一个对象建立一个关联对象。程序员可以为现有的类增添关联对象,起到类似于动态添加“实例变量”的作用。

可以在苹果开源的runtime代码中找到这几个方法的实现。比如在objc-references.mm中可以找到objc_setAssociatedObject最终调用的方法:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) 

阅读这几个方法的实现,可以大概了解到关联对象的实现机制。它维护了一个全局的AssociationsHashMap对象。这是一个map对象,其中键为被关联对象的地址,值为一个指向ObjectAssociationMap对象的指针。而ObjectAssociationMap类派生自std::map,键为传入的“key”参数(是一个地址),值为一个指向ObjcAssociation对象的指针。最后,ObjcAssociation中的成员变量_value指的就是那个关联对象。


Category



在Objective-C中,因为一个类中有哪些方法是在运行时决定的,所以可以用category给任何类增加方法。

objc-runtime-new.mm中,可以看到methodizeClass方法

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{

......

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

........

}

根据注释,这个方法负责将一个类的方法列表、协议列表和属性列表整理好。在这个类的实现中,我们可以看到,相比于整理类自身的方法列表、协议列表和属性列表,将category中的内容关联到对应的类上,是之后做的事情。

查看attachCategories的方法实现,其中,category中的方法列表是这样关联到对应的类中的:

rw->methods.attachLists(mlists, mcount);

在category将方法列表关联到类的方法列表的过程中,是不会判断selector是否重名的。如果类A中已经定义了方法demoMethod,那么在A的category中定义重名的demoMethod,会导致加载完成后,类A的方法列表中有两个名为demoMethod的方法。可以做个小实验验证:

unsigned int count = 0;
Method *methodList = class_copyMethodList([A class], &count);
Method *methodHeader = methodList;
while (count--) {
    Method method = *methodHeader;
    SEL selector = method_getName(method);
    NSLog(@"sel: %@", NSStringFromSelector(selector));
    methodHeader++;
}
free(methodList);

这段代码的输出:

2016-06-26 18:31:04.251 XSQCategoryDemo[3410:5607532] sel: demoMethod
2016-06-26 18:31:04.252 XSQCategoryDemo[3410:5607532] sel: demoMethod

即表示,类A的方法列表中有两个名为demoMethod的方法。


KVO



KVO的实现原理是isa-swizzling

官方文档中解释了:当一个观察者被注册到一个对象上的时候,这个对象的isa指针会被更改,指向一个中间类而非真正的类。

如果做实验验证一下:

A *a = [[A alloc] init];
NSLog(@"%@", object_getClass(a));
[a addObserver:someObserver forKeyPath:@"someKeyPath" options:0 context:nil];
NSLog(@"%@", object_getClass(a));

这一小段代码的输出是:

2016-06-25 23:56:49.381 XSQKVODemo[88055:5252219] A
2016-06-25 23:56:52.161 XSQKVODemo[88055:5252219] NSKVONotifying_A

可以看出,在注册了观察者之后,对象指向的类发生了改变。


KVC



在Foundation的NSKeyValueCoding.h中,几个KVO方法都用详细的注释介绍了它的搜索策略。比如

- (nullable id)valueForKey:(NSString *)key;

方法是这样执行的:

  1. 搜索消息接收者所在类的accessor方法,依次寻找名字能匹配
    -get<Key>-<key>或者-is<Key>的方法,如果找到则调用;
  2. 搜索消息接收者所在类的方法,如果有能匹配NSOrderedSet的方法,则会返回一个代理对象,这个代理对象可以接收所有NSOrderedSet对象可以接收的消息;
  3. 搜索消息接收者所在类的方法,如果能匹配NSArray的方法,则返回一个可以接收所有NSArray对象可以接收的消息的代理对象;
  4. 搜索消息接收者所在类的方法,如果能匹配NSSet的方法,则返回一个可以接收所有NSSet对象可以接收的消息的代理对象;
  5. 如果消息接收者所在的类的+accessInstanceVariablesDirectly方法返回的是YES,就依次寻找名字能匹配
    _<key>_is<Key><key>或者is<Key>的实例变量;
  6. 最后,调用-valueForUndefinedKey:然后返回结果。

虽然没找到明确的文档说KVC利用了运行时机制,但类似这样对方法名、对实例变量的搜索,没有运行时系统的支持应该是无法做到的。

其实如果在Xcode中设置一个符号断点class_getInstanceMethod,也可以调试到这个方法被KVC的一些方法调用了:


参考

objc4-680
Objective-C Runtime Reference
Key-Value Observing Implementation Details
刨根问底Objective-C Runtime(3)- 消息 和 Category
iOS 程序 main 函数之前发生了什么
Objective-C Associated Objects 的实现原理
objc category的秘密
Key-Value Coding and Observing
深入理解Objective-C:Category

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

推荐阅读更多精彩内容