好久没有写文章了,在这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;
}
这里面一共分为以下几步
- Optimistic cache lookup
- Try this class's cache.
- Try this class's method lists
- Superclass method list
- No implementation found. Try method resolver once
- 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 有好的技术启示,寻找到适合自己的技术方向。