• <acronym id='0jp3u'><em id='0jp3u'></em><td id='0jp3u'><div id='0jp3u'></div></td></acronym><address id='0jp3u'><big id='0jp3u'><big id='0jp3u'></big><legend id='0jp3u'></legend></big></address>

      1. <dl id='0jp3u'></dl>

      2. <i id='0jp3u'></i>

          1. <tr id='0jp3u'><strong id='0jp3u'></strong><small id='0jp3u'></small><button id='0jp3u'></button><li id='0jp3u'><noscript id='0jp3u'><big id='0jp3u'></big><dt id='0jp3u'></dt></noscript></li></tr><ol id='0jp3u'><table id='0jp3u'><blockquote id='0jp3u'><tbody id='0jp3u'></tbody></blockquote></table></ol><u id='0jp3u'></u><kbd id='0jp3u'><kbd id='0jp3u'></kbd></kbd>
          2. <span id='0jp3u'></span>

            <ins id='0jp3u'></ins>

            <code id='0jp3u'><strong id='0jp3u'></strong></code>
            <i id='0jp3u'><div id='0jp3u'><ins id='0jp3u'></ins></div></i>

            <fieldset id='0jp3u'></fieldset>

            Runtime - 方法发送机制土味讲解

            • 时间:
            • 浏览:25
            • 来源:124软件资讯网

            面试驱动手艺合集(初中级iOS开发)  ,关注堆栈  ,实时获取更新 Interview-series

            Class 结构详解

            struct objc_class : objc_object {
                Class isa;
                Class superclass;
                cache_t cache;--> 要领缓存      
                class_data_bits_t bits;  
            }
            struct cache_t {
                struct bucket_t *_buckets;//散列表
                mask_t _mask;//散列表长度-1
                mask_t _occupied;//已经缓存的要领数目
                }
            struct bucket_t {
                cache_key_t _key;//@selecter(xxx) 作为key
                MethodCacheIMP _imp;//函数的执行地址
                }

            buckets 散列表  ,是一个数组  ,数组内里的每一个元素就是一个bucket_t,bucket_t内里存放两个

            • _key SEL作为key

            • _imp 函数的内存地址

            _mask 散列表的长度

            _occupied已经缓存的要领数目

            函数挪用底层走的是objc_msgSend

            正常的流程:

            1. 工具通过isa  ,找到函数所在的类工具

            2. 这时间先做缓存查找 ,若是缓存的函数列表中没找到该要领

            3. 就去类的class_rw中的methods中找  ,若是找到了  ,挪用并缓存该要领

            4. 若是类的class_rw中没找到该要领  ,通过superclass到父类中 ,走的逻辑照旧先查缓存  ,缓存没有查类内里的要领 。

            5. 最终若是在父类中挪用到了  ,会将要领缓存到当前类的要领缓存列表中

            要领缓存

            怎样举行缓存查找->使用散列表(散列表 - 空间换时间)

            MNGirl *girl = [[MNGirl alloc]init];
            mj_objc_class *girlClass = (__bridge mj_objc_class *)[MNGirl class];
            
            [girl beauty];
            [girl rich];
            
            //遍历缓存(散列表长度 = mask + 1)
            cache_t cache = girlClass->cache;
            bucket_t *buckets = cache._buckets;
            for (int i = 0; i < cache._mask + 1; i++) {
                
                bucket_t bucket = buckets[i];
                
                NSLog(@"%s %p", bucket,bucket._imp);
            }
            
            ----------------------------------------
            2019-03-13 22:11:42.911494+0800 rich 0x100000be0
            2019-03-13 22:11:42.912946+0800 beauty 0x100000c10
            2019-03-13 22:11:42.912970+0800 (null) 0x0
            2019-03-13 22:11:42.913002+0800 init 0x7fff4f98ff4d

            发现缓存中已经有三个要领了  ,划分是初始化挪用的init ,第一次挪用的beauty和第二次挪用的rich

            散列表取要领

            [girl beauty];
            [girl rich];
            
            //遍历缓存(散列表长度 = mask + 1)
            cache_t cache = girlClass->cache;
            bucket_t *buckets = cache._buckets;
            
            bucket_t bucket = buckets[(long long)@selector(beauty) & cache._mask];
            
            NSLog(@"%s %p", bucket,bucket._imp);
            
            -----------------------------------------
            2019-03-13 22:15:00 beauty 0x100000c60

            确实是取要领的时间  ,不用遍历  ,通过@selector( ) & mask = index索引  ,数组同index就

            注重  ,纷歧定每次都能准确的index索引  ,算出来的index取出来的内容纷歧定是想要的  ,可是经常是比力靠近  ,最差的情形下  ,也只是一边的循环遍历

            索引散列表效率远高于数组  !

            要领查找的源码:  bucket_t * cache_t::find(cache_key_t k, id receiver)

            bucket_t * cache_t::find(cache_key_t k, id receiver)
            {
            assert(k != 0);
            
            bucket_t *b = buckets();
            mask_t m = mask();
            mask_t begin = cache_hash(k, m);
            mask_t i = begin;
            do {
            if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
            }
            } while ((i = cache_next(i, m)) != begin);
            
            // hack
            Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
            cache_t::bad_cache(receiver, (SEL)k, cls);
            }

            索引值 Index 的盘算

            static inline mask_t cache_hash(cache_key_t key, mask_t mask) 
            {
                return (mask_t)(key & mask);
            }
            
            mask_t begin = cache_hash(k, m);

            走的是 key & mask的要领  , A & B 一定是小于 A的

             1111 0010
            &0011 1111
            ----------
             0011 0010 <= 原来的值

            哈希表的算法也有用求余的  ,和&类似

            实现如下:

            (i = cache_next(i, m)) != begin

            查找流程梳理: 好比起始下标是4  , 总长度是6 ,目的不在列表中

            1. 取出index = 4的值  ,发现不是想要的  ,i - - 酿成3

            2. 3 依次 - - 到0  ,然后mask长度最先 = 6继续

            3. 当6 又 - - 到起始index = 4的时间  ,说明已经遍历一圈了  ,照旧没找到 ,要领缓存查找竣事

            OC的新闻机制

            三个阶段

            • 新闻发送

            • 动态要领剖析

            • 新闻转发

            新闻发送

            当前类查找顺序

            • 排序好的列表 ,接纳二分查找算法查找对应的执行函数

            • 未排序的列表  ,接纳一样平常遍历的要领查找工具执行函数

            父类逐级查找

            动态要领剖析

            @interface IOSer : NSObject
            
            - (void)interview;
            
            @end
            
            @implementation IOSer
            
            - (void)test{
                
                NSLog(@"%s",__func__);
                
            }
            
            + (BOOL)resolveInstanceMethod:(SEL)sel{
                if (sel == @selector(interview)) {
                    
                    Method method = class_getInstanceMethod(self, @selector(test));
                    
                    //动态添加interview要领
                    class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
                    
                    return YES;
                    
                }
                return [super resolveInstanceMethod:sel];
            }
            
            @end
            
            ----------------------------------------------
            
            //挪用
            IOSer *ios = [[IOSer alloc]init];
            [ios interview];
            
            ---------------------------------------------
            效果  ,不会crash  ,进入了动态添加的要领了
            2019-03-17 21:33:51.475717+0800 Runtime-TriedResolverDemo[11419:9277997] -[IOSer test]

            新闻转发流程

            • 新闻转发流程1:forwardingTargetForSelector

            @implementation IOSer
            
            - (void)interview{
                
                NSLog(@"%s",__func__);
            }
            @end
            
            @interface Forwarding : NSObject
            
            - (void)interview;
            
            @end
            
            @implementation Forwarding
            
            - (id)forwardingTargetForSelector:(SEL)aSelector{
                if (aSelector == @selector(interview)) {
                
                    //objc_msgSend([[IOSer alloc]init],aSelector)
                    //由IOSer作为新闻转发的吸收者
                    return [[IOSer alloc]init];
                }
                return [super forwardingTargetForSelector:aSelector];
            }
            
            @end
            
            ---------------------------------------------------------------
            挪用
            Forwarding *obj = [[Forwarding alloc]init];
            [obj interview];
            
            
            ---------------------------------------------
            效果  ,不会crash  ,进入了动态添加的要领了
            2019-03-17 22:57:45.130805+0800 Runtime-TriedResolverDemo[13776:9355195] -[IOSer interview]
            • 新闻转发流程2:forwardingTargetForSelector

            @implementation Forwarding
            
            //返回要领署名
            - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
                if (aSelector == @selector(interview)) {
                
                    //v16@0:8 = void xxx (self,_cmd)
                    return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
                }
                return [super methodSignatureForSelector:aSelector];
            }
            
            //NSInvocation - 要领挪用
            - (void)forwardInvocation:(NSInvocation *)anInvocation{
                //设置要领挪用者
                [anInvocation invokeWithTarget:[[IOSer alloc]init]];
            }
            
            @end

            NSInvocation 实在封装了一个要领挪用  ,包罗:

            • 要领名  - anInvocation.selector

            • 要领挪用 - anInvocation.target

            • 要领参数 - anInvocation getArgument: atIndex:

            冷门知识增补

            //类要领的新闻转发
            [Forwarding test];

            类要领也可以实现新闻转发  ,可是用的是+ (id)forwardingTargetForSelector:(SEL)aSelector函数