iOS,关于runtime

0623432517cbf2e1508634.jpg

好久没有写文章了,在这2016的尾声,写个自己欠下来的文章,runtime。
也为自己的2017 预热一下

介绍

runtime 运行时,这个几乎面试都会问而平时写代码用的不是很多的东西。同样可以看出,不仅仅runtime 其他的语言和技术也是,越是深度,用的越少。
runtime 都能干什么呢? 这里我简单的终结了一下
1.发送消息 objc_msgSend
2.交互方法 method_exchangeImplementations
3.添加方法 resolveInstanceMethod
4.分类添加属性 objc_setAssociatedObject
5.字典边model class_copyIvarList ,这里加个setValue forKey
6.kvo 实现原理----这个面试问的多 willChangeValueForKey:didChangeValueForKey:
.....
这也是我目前知道的,有补充的欢迎留言

目前国内背景

在前面说的例子,基本是目前runtime 我们能用的比较多的,baidu 一下能get 到现成代码,到项目中改一改就能用的。
所以现在runtime 在业内用的还是比较多的

开始

那这里我们反思一下,为什么有runtime ,或者没有他行不行
这里就要说起Object-C的消息机制了。消息机制是OC 比较独特的地方。
要实现的是对于class 或者 instance 发送一个SEL 在一个hash 表中或者代码块中寻找对应的imp 这个过程,可能使superClass 加入进来,也可能不是自己的class 里面的方法,可能是消息转发。等等一系列未知的情况。那么runtime 就这样诞生了。
这也是为什么说OC 是一门动态的语言。因为没有到最后谁也不知道sel 到底是和哪个imp 勾搭在一起了。

这里详细讨论一下 最有名的objc_msgSend
我们每天都在用,可是没有了解它太多

内部结构

怎么能开始调用obj_msgSend
在NSObject.mm中可以看到

+ (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)((id)self, sel);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)((id)self, sel, obj);
}

+ (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id, id))objc_msgSend)((id)self, sel, obj1, obj2);
}

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}


这一大片,随便用一个就ok。

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */
#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

到这里,好像已经到了尽头,因为文档告诉你,这是个私有方法。
但是可以换个途径

调用 objc_msgSend 时,传入了 self 以及 SEL 参数。
既然要执行对应的方法,肯定要寻找选择子对应的实现。
在 objc-runtime-new.mm 文件中有一个函数 lookUpImpOrForward ,这个函数的作用就是查找方法的实现,于是运行程序,激活 lookUpImpOrForward 函数中的断点.
可以清楚看到调用的堆栈

QQ20161228-0@2x.png

为什么有uncached 这个我还是没有搞懂,因为我看调用的是objc_msgSend 可能是内部定义的吧

lookUpImpOrForward 返回的值是IMP
通过SEL 找到IMP 看来全靠他了


/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held to make method-lookup + cache-fill atomic 
    // with respect to method addition. Otherwise, a category could 
    // be added but ignored indefinitely because the cache was re-filled 
    // with the old value after the cache flush on behalf of the category.
 retry:
    runtimeLock.read();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.

    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }

    // Try superclass caches and method lists.

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (imp) {
            if (imp != (IMP)_objc_msgForward_impcache) {
                // Found the method in a superclass. Cache it in this class.
                log_and_fill_cache(cls, imp, sel, inst, curClass);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break;
            }
        }

        // Superclass method list.
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
            imp = meth->imp;
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

这里面一共分为以下几步

  1. Optimistic cache lookup
  2. Try this class's cache.
  3. Try this class's method lists
  4. Superclass method list
  5. No implementation found. Try method resolver once
  6. No implementation found, and method resolver didn't help

这样可以看出,对于一个方法的处理逻辑
在类中寻找,父类中寻找,找到了元类。还没有找到 那么给你一次机会 resolveMethod 是否有对这个sel 的实现
(BOOL)resolveInstanceMethod:(SEL)sel
如没有判断是否有转发
这个转发有很多种
-(id)forwardingTargetForSelector 这个返回的是转发的对象,显然这个更要灵活

关于源代码

obj_send_msg 这个方法虽然没有对方公开,objc_msgSend 在检查缓存。如果没有缓存会调用 lookupImpOrForward 进行方法查找。
网上大部分说是汇编写的,为了提高oc 的消息发送效率。这个因为没有看到代码。也不敢确定,不过通过各种实验可以确定。汇编可以搞定。具体的可以参考
https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html

这里有端代码

 id objc_msgSend(id self, SEL _cmd, ...)
    {
        Class c = object_getClass(self);
        IMP imp = cache_lookup(c, _cmd);
        if(!imp)
            imp = class_getMethodImplementation(c, _cmd);
        return imp(self, _cmd, ...);
    }

后期可以继续对内部进行分析,不过到这里已经是段落了。对于动态语言也会有个大概的了解,不仅仅是oc 。比如最近流行的Python,小程序的JS 。 js 是个万能的语言,更多的是我们需要一个万能的语言。
http://blog.csdn.net/liqingxu2005/article/details/41865821?locationNum=6

给我的技术启示

大概的runtime 框架以一个消息接受和发送的方法,简单叙述了一下,其他方法,各位可以多多探索,相互交流。
对于功能的实现是最基本的,公司不同,逻辑不用,业务不同。深度的东西不会改变这也是以后研究的方向。

关于面试

面试中,问的runtime感觉问与不问关系不大。因为现在网上的资料实在是太多了。这样是为什么你打开拉钩,基本找不到更高的待遇的原因。
技术不是赚钱的工具,是自我认知的工具。

结语

最后的最后,祝福大家2017 有好的技术启示,寻找到适合自己的技术方向。

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 727评论 0 2
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,129评论 0 9
  • 人的一生中,总是在不断的选择,那些选择正确那些选择错误,也许到老也无法明了,根本没有去考虑选择背后的心理因素,我的...
    傲立梅枝阅读 700评论 0 0
  • 练笔作业:改编小红帽。 于是,我把它改成了一个悲伤的童话故事…… 从前,有一个可爱的小姑娘,谁见了都喜欢。但最喜欢...
    我从彩虹那边来阅读 771评论 1 4