美文网首页
十五、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