iOS,关于runtime

iOS,关于runtime_第1张图片
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 函数中的断点.
可以清楚看到调用的堆栈

为什么有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 有好的技术启示,寻找到适合自己的技术方向。

你可能感兴趣的:(iOS,关于runtime)