甘肃省铁路投资建设集团有限公司网站,wordpress 广告代码,濮阳网络运输证,wordpress新奇插件目录 前言
方法的本质
向不同对象发送消息
发送实例方法
发送类方法
对象调用方法 实际执行是父类
向父类发送类方法
消息查找流程
开始查找
快速查找流程
慢速查找流程
动态方法决议
应用场景
优化方案
消息转发机制
快速转发流程
应用场景
慢速转发流程
应…目录 前言
方法的本质
向不同对象发送消息
发送实例方法
发送类方法
对象调用方法 实际执行是父类
向父类发送类方法
消息查找流程
开始查找
快速查找流程
慢速查找流程
动态方法决议
应用场景
优化方案
消息转发机制
快速转发流程
应用场景
慢速转发流程
应用场景 前言
在OC底层中方法的调用实质上是通过消息的发送实现的这篇文章我们来看一看消息的发送是怎么样的
方法的本质
方法的本质就是通过objc_msgSend发送消息有两个参数第一个是id类型表示消息接受者第二个表示方法编号。 向不同对象发送消息
发送实例方法
消息接收者是实例对象
发送类方法
本质上是向类对象发送消息 objc_getClass得到的是类对象
对象调用方法 实际执行是父类
Runtime中提供了一个接口处理这种情况父类中实现了该方法而子类没有实现该方法子类对象调用方法会执行父类中实现符合继承的特性
这个接口是objc_msgSendSuper使用时还需要用到objc_super结构体并给结构体赋值receiver、super_class 该结构体中receiver表示接收消息的实例对象super_class表示父类类对象根据这个赋值 可以看到这两种方式都是执行父类的实现因此可以推断方法调用首先在类中查找如果找不到就到父类中查找
向父类发送类方法
上面向父类发送实例方法时receiver表示实例对象super_class表示父类类对象。而如果向父类发送类方法reciever表示类对象super_class表示父类元类对象 消息查找流程
消息查找的流程就是通过上层的sel发送消息objc_msgSend找到底层具体imp的实现的过程objc_msgSend是用汇编写的而不是用C语言
开始查找 在开始objc_msgSend之后 首先会判断消息接受者是否为空为空就直接返回 然后会判断是否为小对象也就是是否为tagged_pointers 之后取对象中的isa存到寄存器p13中根据isa进行mask地址偏移来得到对应的上级对象类、元类
取得了上级对象之后就可以开始快速查找流程了也就是在缓存中找imp的过程
快速查找流程 首先通过类的首地址偏移16字节找到cache的地址cache离首地址16字节isa占8字节superclass占8字节cache高16位存mask低48位存buckets 然后从cache中分别取出buckets和mask根据mask通过哈希算法算出哈希下标根据哈希下标和bukets首地址来得到对应的bucketbucket中存放着imp和sel 那么怎么确定找到的imp和sel就是要找的那个呢主要是通过两层循环 第一层循环比较bucket中的sel和objc_msgSend中第二个参数_cmd是否相等如果相等就直接跳转到CacheHit即缓存命中返回imp如果不相等有三种情况 一种是一直找不到就直接跳转到CheckMiss因为参数$0是normal会跳转到__objc_msgSend_uncached看英文就能明白意思就是没找到这时就会进入慢速查找流程 第二种是如果获取到的bucket是第一个元素那么就手动把它设置为最后一个元素然后进行第二层循环 如果当前bucket不是第一个元素那就继续当前的循环 第二层循环和第一层循环基本相同只是如果bucket还是等于buckets中第一个元素就直接跳转到JumpMiss此时也会跳转到没找到__objc_msgSend_uncached进入慢速查找
慢速查找流程
慢速查找的过程分为汇编和C两个部分这里我们不纠结汇编部分汇编最后调用的是lookUpImpOrForward这是一个C实现的函数
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{// 定义的消息转发const IMP forward_imp (IMP)_objc_msgForward_impcache;IMP imp nil;Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls-isInitialized())) {// The first message sent to a class is often new or alloc, or self// which goes through objc_opt_* or various optimized entry points.//// However, the class isnt realized/initialized yet at this point,// and the optimized entry points fall down through objc_msgSend,// which ends up here.//// We really want to avoid caching these, as it can cause IMP caches// to be made with a single entry forever.//// Note that this check is racy as several threads might try to// message a given class for the first time at the same time,// in which case we might cache anyway.behavior | LOOKUP_NOCACHE;}
// runtimeLock is held during isRealized and isInitialized checking// to prevent races against concurrent realization.
// runtimeLock is held during method search 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.
//加锁目的是保证读取的线程安全runtimeLock.lock();
// We dont want people to be able to craft a binary blob that looks like// a class but really isnt one and do a CFI attack.//// To make these harder we want to make sure this is a class that was// either built into the binary or legitimately registered through// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.//判断是否是一个已知的类判断当前类是否是已经被认可的类即已经加载的类checkIsKnownClass(cls);
//判断类是否实现如果没有需要先实现此时的目的是为了确定父类链方法后续的循环cls realizeAndInitializeIfNeeded_locked(inst, cls, behavior LOOKUP_INITIALIZE);// runtimeLock may have been dropped but is now locked againruntimeLock.assertLocked();curClass cls;
// The code used to lookup the classs cache again right after// we take the lock but for the vast majority of the cases// evidence shows this is a miss most of the time, hence a time loss.//// The only codepath calling into this without having performed some// kind of cache lookup is class_getInstanceMethod().//----查找类的缓存// unreasonableClassCount -- 表示类的迭代的上限//猜测这里递归的原因是attempts在第一次循环时作了减一操作然后再次循环时,仍在上限的范围内所以可以继续递归for (unsigned attempts unreasonableClassCount();;) {if (curClass-cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHESimp cache_getImp(curClass, sel);if (imp) goto done_unlock;curClass curClass-cache.preoptFallbackClass();
#endif} else {// curClass method list.//---当前类方法列表采用二分查找算法如果找到则返回将方法缓存到cache中Method meth getMethodNoSuper_nolock(curClass, sel);if (meth) {imp meth-imp(false);goto done;}//当前类 当前类的父类并判断父类是否为nilif (slowpath((curClass curClass-getSuperclass()) nil)) {// No implementation found, and method resolver didnt help.// Use forwarding.//--未找到方法实现方法解析器也不行使用转发imp forward_imp;break;}}
// Halt if there is a cycle in the superclass chain.// 如果父类链中存在循环则停止if (slowpath(--attempts 0)) {_objc_fatal(Memory corruption in class list.);}
// Superclass cache.// --父类缓存imp cache_getImp(curClass, sel);if (slowpath(imp forward_imp)) {// Found a forward:: entry in a superclass.// Stop searching, but dont cache yet; call method// resolver for this class first.// 如果在父类中找到了forward则停止查找且不缓存首先调用此类的方法解析器break;}if (fastpath(imp)) {// Found the method in a superclass. Cache it in this class.//如果在父类中找到了此方法将其存储到cache中goto done;}}
// No implementation found. Try method resolver once.//没有找到方法实现尝试一次方法解析
if (slowpath(behavior LOOKUP_RESOLVER)) {//动态方法决议的控制条件表示流程只走一次behavior ^ LOOKUP_RESOLVER;return resolveMethod_locked(inst, sel, cls, behavior);}
done:if (fastpath((behavior LOOKUP_NOCACHE) 0)) {
#if CONFIG_USE_PREOPT_CACHESwhile (cls-cache.isConstantOptimizedCache(/* strict */true)) {cls cls-cache.preoptFallbackClass();}
#endif//存储到缓存log_and_fill_cache(cls, imp, sel, inst, curClass);}done_unlock://解锁runtimeLock.unlock();if (slowpath((behavior LOOKUP_NIL) imp forward_imp)) {return nil;}return imp;
}
上面是慢速查找的源码用自然语言来表述就是 首先进行一次快速查找也就是在cache缓存中查找找到就直接返回imp没找到就继续 先判断cls是否是已知类如果不是就报错再判断类是否实现如果没实现需要先实现这个时候实现的目的是为了确定它的父类链ro以及rw等方便之后数据读取和查找还要判断是否初始化没有就初始化 接下来进入for循环沿着类或元类的继承链进行查找 对于当前cls在方法列表中使用二分查找进行查找如果找到就进入cache写入流程并返回imp如果没找到就返回nil 当前cls赋值为父类如果父类为nilimp 消息转发并终止递归开始判断是否执行过动态方法解析 如果父类链中存在循环就报错 在父类中查找时会先在父类缓存中查找再在方法列表中查找 判断是否执行过动态方法解析如果没有就执行动态方法解析执行过一次的话就走消息转发流程 在二分查找过程中如果找到的与key的value值相等需要先排除分类方法 在进行完快速查找和慢速查找的流程之后会进入动态方法决议和消息转发流程
动态方法决议
在查找流程没找到方法时有一次机会补救就是动态方法决议以实例方法为例程序会走到resolveInstanceMethod方法 用自然语言描述如下 在发送resolveInstanceMethod消息前先查找cls中有没有这个方法的实现也就是通过lookUpImpOrNil方法进入lookUpImpOrForward慢速查找流程找这个方法 如果没找到就直接返回 如果找到了就发送resolveInstanceMethod消息 再慢速查找实例方法的实现又进行一次慢速查找
应用场景
使用动态方法决议可以解决一些方法未实现的报错重写resolveInstanceMethod类方法并在其中将其指向其他方法的实现比如有一个say666没实现但是实现了sayMaster方法 类方法同理将方法名改为resolveClassMethod即可
优化方案
在上面的场景中我们需要对每一个类的方法进行重写并且我们又知道慢速方法查找路径最后都会走到根类因此我们可以为NSObjct添加分类来统一处理 消息转发机制
如果前面的过程都没找到该方法那我也是没招了bushi那就会进行消息转发流程消息转发流程分为快速转发和慢速转发如果方法没有实现而崩溃报错在崩溃之前会调用两遍动态方法决议两遍快速转发两遍慢速转发
快速转发流程
forwardingTargetForSelector在源码中只有声明但是我们可以从帮助文档中看到有关于它的解释 该方法的返回对象是执行sel的新对象也就是自己处理不了会将消息转发给别的对象进行相关方法的处理但是不能返回self否则会一直找不到 该方法的效率较高如果不实现会走到forwardInvocation:方法进行处理 底层会调用objc_msgSend(forwardingTarget, sel, ...);来实现消息的发送 被转发消息的接受者参数、返回值等应和原方法相同
应用场景
比如TCJPerson没实现的方法转发给实现了的TCJStudent 也可以直接调用父类的该方法如果没找到的话会直接报错 慢速转发流程
methodSignatureForSelector慢速查找流程同样在帮助文档中寻找可以发现forwardInvocation和methodSignatureForSelector必须同时存在
底层会通过方法签名生成一个NSInvocation作为参数传递使用接着查找可以响应NSInvocation中编码的消息的对象找到后使用anInvocation将消息发送给该对象并且anInvocation保存结果运行时系统将提取结果并传递给原始发送者
应用场景
慢速转发的流程就是methodSignatureForSelector提供一个方法签名然后forwardInvocation通过NSInvocation来实现消息的转发 无论在forwardInvocation方法中是否处理invocation事务程序都不会崩溃
方法和消息的流程就到这里了在上面的过程中你有没有注意到动态方法决议进行了两遍这个问题它为什么会执行两遍呢
其实第二次动态方法决议是在methodSignatureForSelector 和 forwardInvocation方法之间是开始进行慢速消息转发之前再给的一次机会