美文网首页
十五、runtime面试题

十五、runtime面试题

作者: Mjs | 来源:发表于2020-10-23 18:03 被阅读0次

    1、类的方法 - 分类的方法重名,谁先调用

    一般情况下分类先调用,load是先主类再分类

    void
    load_images(const char *path __unused, const struct mach_header *mh)
    {
    ...
        // Discover load methods
        //发现load方法
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        call_load_methods();
    }
    
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    //加载非懒加载类
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));
        }
    //加载分类的所有非懒加载
        category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            
            if (!cls) continue;  // category for ignored weak-linked class
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class extensions and categories on Swift "
                            "classes are not allowed to have +load methods");
            }
            realizeClassWithoutSwift(cls, nil);
            ASSERT(cls->ISA()->isRealized());
            add_category_to_loadable_list(cat);
        }
    }
    
    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        ASSERT(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // Ensure superclass-first ordering
        schedule_class_load(cls->superclass);//寻找父类
    
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    
    }
    
    void add_class_to_loadable_list(Class cls)
    {
        IMP method;
    
        loadMethodLock.assertLocked();
    
        method = cls->getLoadMethod();
        if (!method) return;  // Don't bother if cls has no +load method
        
        if (PrintLoading) {
            _objc_inform("LOAD: class '%s' scheduled for +load", 
                         cls->nameForLogging());
        }
        
        if (loadable_classes_used == loadable_classes_allocated) {//扩容
            loadable_classes_allocated = loadable_classes_allocated*2 + 16;
            loadable_classes = (struct loadable_class *)
                realloc(loadable_classes,
                                  loadable_classes_allocated *
                                  sizeof(struct loadable_class));
        }
        
        loadable_classes[loadable_classes_used].cls = cls;
        loadable_classes[loadable_classes_used].method = method;//记录下来
        loadable_classes_used++;
    }
    

    找到所有的load方法,然后就是call_load_methods

    
    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    2.runtime是什么

    runtime 是由C 和C++ 汇编 实现的⼀套API,为OC语⾔加⼊了⾯向对象,运⾏时的功能。
    运⾏时(Runtime)是指将数据类型的确定由编译时推迟到了运⾏时 -
    举例⼦: extension - category 的区别。
    平时编写的OC代码,在程序运⾏过程中,其实最终会转换成Runtime的C语⾔代码,RuntimeObject-C 的幕后⼯作者

    3.⽅法的本质,sel是什么?IMP是什么?两者之间的关系⼜是什么?

    ⽅法的本质:发送消息 , 消息会有以下⼏个流程
    1:快速查找 (objc_msgSend)~ cache_t 缓存消息
    2:慢速查找~ 递归⾃⼰| ⽗类 ~ lookUpImpOrForward
    3:查找不到消息: 动态⽅法解析 ~ resolveInstanceMethod
    4:消息快速转发~ forwardingTargetForSelector
    5:消息慢速转发~ methodSignatureForSelector & forwardInvocation
    sel 是⽅法编号 ~ 在read_images 期间就编译进⼊了内存
    imp 就是我们函数实现指针 ,找imp 就是找函数的过程
    sel 就相当于书本的⽬录 tittle
    imp 就是书本的⻚码
    查找具体的函数就是想看这本书⾥⾯具体篇章的内容
    1:我们⾸先知道想看什么 ~ tittle (sel)
    2:根据⽬录对应的⻚码 (imp) 3:翻到具体的内容

    4.[self class]和[super class]的区别以及原理分析

    • [self class] 就是发送消息objc_msgSend,消息接受者是 self ⽅法编号:class
    • [super class] 本质就是objc_msgSendSuper, 消息的接受者还是
      self ⽅法编号:class
    注:只是objc_msgSendSuper 会更快 直接跳过 self 的查找
    ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))
    objc_msgSendSuper(
            {(id)self, (id)class_getSuperclass(objc_getClass("LGTeacher"))},
                                    sel_registerName("class")));
    

    5.内存平移问题

    @interface LGPerson : NSObject
    @property (nonatomic, copy) NSString* kc_name;
    @property (nonatomic, copy) NSString *kc_hobby;  // 12
    - (void)saySomething;
    @end
    
    @implementation LGPerson
    - (void)saySomething{ // self 消息接受者 - LGPerson: 0x7ffee8a7c0e8
        NSLog(@"%s - %@",__func__,self.kc_name);
    }
    @end
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Class cls = [LGPerson class];
        void  *kc = &cls;  // 
        LGPerson *person = [LGPerson alloc];
        [(__bridge id)kc saySomething]; 
        [person saySomething]; // self.kc_name = nil - (null)
    
    }
    

    打印结果为

    -[LGPerson saySomething] - <ViewController: 0x7fd98dd06fe0>
    -[LGPerson saySomething] - (null)
    
    内存平移.png
    kc_nameperson首地址平移8字节获取到的,kc平移8地址是什么呢?
    首先栈内存是从高地址到低地址分配,参数会从前往后一直压入,而结构体是后面的先插入。所以一开始插入的是self,_cmd,然后调用 [super viewDidLoad]super在上面已经说过是个objc_msgSendSuper,这里传入了一个结构体{(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))},所以后面是(id)class_getSuperclass(objc_getClass("ViewController")),在后面是self,cls,kc,而kc是指向cls地址.我们可以打印下
        void *sp  = (void *)&self;
        void *end = (void *)&kc;
        long count = (sp - end) / 0x8;
        
        for (long i = 0; i<count; i++) {
            void *address = sp - 0x8 * i;
            if ( i == 1) {
                NSLog(@"%p : %s",address, *(char **)address);
            }else{
                NSLog(@"%p : %@",address, *(void **)address);
            }
        }
    

    打印结果

    2020-10-23 17:57:09.267540+0800 004-内存平移问题[6622:370762] 0x7ffee3040098 - 0x7ffee30400a8
    2020-10-23 17:57:09.267699+0800 004-内存平移问题[6622:370762] 0x7ffee30400c8 : <ViewController: 0x7f807a308fd0>
    2020-10-23 17:57:09.267789+0800 004-内存平移问题[6622:370762] 0x7ffee30400c0 : viewDidLoad
    2020-10-23 17:57:09.267883+0800 004-内存平移问题[6622:370762] 0x7ffee30400b8 : ViewController
    2020-10-23 17:57:09.267977+0800 004-内存平移问题[6622:370762] 0x7ffee30400b0 : <ViewController: 0x7f807a308fd0>
    2020-10-23 17:57:09.268061+0800 004-内存平移问题[6622:370762] 0x7ffee30400a8 : LGPerson
    

    所以kc中的self.kc_name指向到的就是self(ViewController)

    6.Method-Swizzling注意事项

    我们平时最常用的runtime方法就是methodSwizzling,但是这里却有很多坑点。
    我们为了一次性处理方法交换,基本都是放在load方法中,但我们前文中就说过,这回加载整个类,阻碍启动,而且也可以手动调用load方法。所以我们可以放在initial中,只有第一次就收消息的时候才会处理。
    我们在交换方法的时候也要检测下时候有交换方法的实现

    + (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
        
        if (!cls) NSLog(@"传入的交换类不能为空");
        
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
        // 交换自己没有实现的方法:
        //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
        //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL 
        //oriSEL:personInstanceMethod
        BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        if (didAddMethod) {
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{
            method_exchangeImplementations(oriMethod, swiMethod);
        }
    }
    

    class_addMethod:如果发现方法已经存在,会失败返回,也可以用来做检查用,我们这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现

    1. 如果返回成功:则说明被替换方法没有存在.也就是被替换的方法没有被实现,我们需要先把这个方法实现,然后再执行我们想要的效果,用我们自定义的方法去替换被替换的方法. 这里使用到的是class_replaceMethod这个方法.class_replaceMethod本身会尝试调用class_addMethodmethod_setImplementation,所以直接调用class_replaceMethod就可以了)。class_replaceMethod是替换某个类的方法的实现,功能上可以替代class_addMethod, 但是class_addMethod只能在SEL没有IMP指向时才可以添加成功,而class_replaceMethod不管SEL 有没有IMP实现,都可以添加成功。
    2. 如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换即

    但这种时候,如果oriMethod没有实现,调用方法的时候就会死循环,所以我们必须价格判断,给他一个空的实现。

    + (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
        
        if (!cls) NSLog(@"传入的交换类不能为空");
        
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        if (!oriMethod) { // 避免动作没有意义
            // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
            class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
            method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
                NSLog(@"来了一个空的 imp");
            }));
        }
        
        // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
        // 交换自己没有实现的方法:
        //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
        //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL
        //oriSEL:personInstanceMethod
    
        BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        
        if (didAddMethod) {
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{
            method_exchangeImplementations(oriMethod, swiMethod);
        }
        
    }
    

    相关文章

      网友评论

          本文标题:十五、runtime面试题

          本文链接:https://www.haomeiwen.com/subject/kidvmktx.html