美文网首页@IT·互联网
RunTime的应用及相关的面试问题

RunTime的应用及相关的面试问题

作者: 黑炭长 | 来源:发表于2024-03-10 20:39 被阅读0次

    本次主要记录几个问题
    1、runtime是什么
    2、method-swizzling使用及使用过程中的坑点
    3、isKinkOfClass和isMemmberOfClass的使用
    4、[self class]和[super class]的区别以及原理分析
    5、runtime Associate方法的关联对象,及什么时间释放
    6、__weak对象的存储及释放

    1、runtime是什么

    这个就说的比较宽泛一些,runtime是一套由C & C++编写的一套api,给我们OC提供运行时的功能,研究runtime对其他底层的学习会有一个比较有利的帮助

    2、method-swizzling使用及使用过程中的坑点

    Class class = [self class];
    
    // 原方法名和替换方法名
    SEL originalSelector = @selector(isEqualToString:);
    SEL swizzledSelector = @selector(swizzle_IsEqualToString:);
    
    // 原方法和替换方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    // 如果当前类没有原方法的实现IMP,先调用class_addMethod来给原方法添加实现
    BOOL didAddMethod = class_addMethod(class,
                                        originalSelector,
                                      method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {// 添加方法实现IMP成功后,替换方法实现
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else { // 有原方法,交换两个方法的实现
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    

    1、方法交换,一般我们是在+ (void)load方法中进行方法交换,为什么
    1)执行比较早,在main函数之前就已经执行
    2)自动执行,不需要手动调用,这里的调用 不是通过objc_msgSend调用而是直接通过指针找到相应的imp调用
    3)唯一性,调用顺序为 先 父类 -> 子类 -> 分类
    》这里有一个优化点,App启动的时候 main函数之前 若是当前类 实现了load方法 就会调用该方法,若是load中执行了耗时操作,就会延长App的启动时间,所以 我们减少App的启动时间的一个优化点就是 不要在load方法中进行耗时操作,也不要随便重写load方法
    》还有一个面试题是 load方法是否可以被交换即被hook
    答案是可以的,但是成本会比较高
    我们看load方法的调用堆栈


    load方法调用堆栈

    动态链接器dyld,完成对二进制文件(动态库、可执行文件)的初始化后通过回调函数_dyld_objc_notify_register,调用load_images和call_load_method实现load方法的调用,

    void
    load_images(const char *path __unused, const struct mach_header *mh)
    {
        // Return without taking locks if there are no +load methods here.
        if (!hasLoadMethods((const headerType *)mh)) return;
    
        recursive_mutex_locker_t lock(loadMethodLock);
    
        // Discover load methods
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);
        }
    
        // Call +load methods (without runtimeLock - re-entrant)
        call_load_methods();
    }
    

    load_images通过prepare_load_methods将所有类的load方法加入到list中,包括父类和分类,这里记录的是所有有load方法的类 和 load方法的imp,之后通过call_load_methods调用所有的load方法
    有上述问题 若是我们需要hook A的load方法 必须在A的load方法被加载进入load方法列表之前进行hook,即我们把hook A的load方法的代码放在B中,然后保证B动态库在A之前被链接,这样就可以进行hook了

    isKinkOfClass和isMemmberOfClass的使用

    对象、类对象、元类关系图

    这里首先我们看一下下面代码的打印,先看实例方法,这个比较简单

    - (void)test4 {
        
        NSObject *object = [[NSObject alloc] init];
        Person *person = [[Person alloc] init];
        
        bool o1 = [object isKindOfClass:[NSObject class]];
        bool o2 = [object isMemberOfClass:[NSObject class]];
        bool p1 = [person isKindOfClass:[Person class]];
        bool p2 = [person isMemberOfClass:[Person class]];
        // 1-1-1-1
        NSLog(@"%d-%d-%d-%d",o1,o2,p1,p2);
        
    }
    
    

    -isKindOfClass

    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    • 我们可以看到,对象方法的 for循环 初始值 变成了 [self class],也就是从当前类开始找superclass继承链。

    • 所以 [(id)[NSObject alloc] isKindOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isKindOfClass:[DZPerson class]] 都为 YES

    -isMemberOfClass

    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    
    • -isMemberOfClass 对象方法更是简单了,直接就是判断当前类和传入类是否相等。
    • [(id)[NSObject alloc] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isMemberOfClass:[DZPerson class]] 自然都是 YES。

    再看类方法

    - (void)test5 {
        
        Class object = [NSObject class];
        Class person = [Person class];
        
        bool o1 = [object isKindOfClass:[NSObject class]];
        bool o2 = [object isMemberOfClass:[NSObject class]];
        bool p1 = [person isKindOfClass:[Person class]];
        bool p2 = [person isMemberOfClass:[Person class]];
        
        // 1-0-0-0
        NSLog(@"===class=%d-%d-%d-%d",o1,o2,p1,p2);
        
    }
    

    为什么呢?我们看一下isKindOfClass的源码

    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    

    我们可以看出

    • Class tcls = object_getClass((id)self);

      从源码可以看到,self 是类本身,object_getClass((id)self) 则是获取 isa,而 isa 是指向元类的,所以 tcls 实际上是当前类的元类。
      for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass)

    • for循环实际上就是从当前类的元类开始,沿着继承链中的 superclass 一直向上循环,在如下 isa指向图 中标注部分,NSObject元类 的父类是 NSObject。所以在第二次循环的时候,NSObject元类 的 superclass 是本身NSObject。
    • 但是 DZPerson元类 的继承链是DZPerson元类 -> NSObject元类 -> NSObject,所以在 DZPerson元类 的继承链上永远不会有自身DZPerson。
    • 因此 [(id)[NSObject class] isKindOfClass:[NSObject class]] = YES ,而 [(id)[DZPerson class] isKindOfClass:[DZPerson class]] == NO。

    再看 isMemberOfClass的源码

    + (BOOL)isMemberOfClass:(Class)cls {   
        return object_getClass((id)self) == cls;
    }
    
    
    • 从源码中可以看到,代码是直接判断当前类的元类是否等于传入类。
    • 所以 [(id)[NSObject class] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson class] isMemberOfClass:[DZPerson class]]中,NSObject元类 不等于 NSObject,DZPerson元类 也不等于 DZPerson,结果自然都是 NO。

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

    [self class]

    - (Class)class {
        return object_getClass(self);
    }
    ///
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    

    也就是获取当前类的isa指向 实例的isa指向类
    [super class]
    本质上是调用objc_msgSendSuper

    OBJC_EXPORT void
    objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    第一个参数为一个objc_super的结构体
    struct objc_super {
        __unsafe_unretained _Nonnull id receiver;
        __unsafe_unretained _Nonnull Class super_class;
    };
    那么[super class]就等价于
    struct objc_super lg_super = {
    //我们这里研究对象为当前对象所以消息接收这位self
       self,
       class_getSuperclass([self class]),
    };
    objc_msgSendSuper(&lg_super,@selector(class))
    

    消息接受者 还是self 即 还是会打印实例的类

    • [self class] 就是发送消息objc_msgSend,消息接受者是 self 方法编号:class

    • [super class] 本质就是objc_msgSendSuper, 消息的接受者还是 self 方法编号:class 只是objc_msgSendSuper 会更快 直接跳过 self 的查找

    不能向编译后的类中添加实例变量,但是可以向运行时创建的类中添加实例变量

    • 类编译后只读结构体class_ro_t就被确定了,运行时不可修改
    • ro结构体中的iver_list也是不可修改的,并且instanceSize决定了创建对象时需要的空间大小
    • 运行时添加实例变量 必须在objc_allocateClassPair和objc_registerClassPair之间调用class_addIver

    runtime Associate方法的关联对象,及什么时间释放

    • 需要调用objc_setAssociatedObject,询问是否存在全局的AssociationsManager有就获取,没有就创建,然后跟拒当前类获取 ObjectAssociationMap,没有就创建一个,存入我们的关联对象
    • _object_get_associative_reference,获取也是同样的逻辑 全局AssociationsManager,根据当前类获取ObjectAssociationMap,再根据关联对象的key获取值
    • 删除关联对象 ,在主类dealloc时调用析构函数 objc_destructInstance,然后看当前类是否存在析构,存在的话调用_object_remove_assocations,删除析构

    __weak对象的存储及释放

    image.png
    • 从全局的SideTables中,利用对象本身的地址取得该对象的弱引用表weak_table
    • 如果有分配新值,则检查新值对应的类是否进行过初始化,如果没有则就地初始化weak_register_no_lock
    • 若是有旧值,则需要把旧值对应的弱引用表进行注销weak_unregister_no_lock
    • 将新值注册到对应的弱引用表中,将isa.weakly_referenced设置为true,表示该类有弱引用变量,释放时需要清空弱引用表

    链接弱引用表

    id 
    weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                          id *referrer_id, bool crashIfDeallocating)
    {
        objc_object *referent = (objc_object *)referent_id;// 被引用的对象
        objc_object **referrer = (objc_object **)referrer_id;// 弱引用变量
    
        if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    
        // 确保弱引用对象是否可行
        // ensure that the referenced object is viable
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            BOOL (*allowsWeakReference)(objc_object *, SEL) = 
                (BOOL(*)(objc_object *, SEL))
                object_getMethodImplementation((id)referent, 
                                               SEL_allowsWeakReference);
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
                ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
        }
        // 如果正在释放中,则根据 crashIfDeallocating 判断是否触发 crash
        if (deallocating) {
            if (crashIfDeallocating) {
                _objc_fatal("Cannot form weak reference to instance (%p) of "
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent));
            } else {
                return nil;
            }
        }
    
        // now remember it and where it is being stored
        // 每个对象对应的一个弱引用记录
        weak_entry_t *entry;
        
        // 如果当前表中有该对象的记录则直接加入该 weak 表中对应记录
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            append_referrer(entry, referrer);
        } 
        else {
            
            // 没有在 weak 表中找到对应记录,则新建一个记录
            weak_entry_t new_entry(referent, referrer);
            
            // 查看是否需要扩容
            weak_grow_maybe(weak_table);
            
            // 将记录插入 weak 表中
            weak_entry_insert(weak_table, &new_entry);
        }
    
        // Do not set *referrer. objc_storeWeak() requires that the 
        // value not change.
    
        return referent_id;
    }
    
    

    上述代码主要功能如下:

    • 判断被指向对象是否可行,也就是判断其是否正在释放,并且会根据crashIfDeallocating判断是否触发crash。
    • 在weak_table中检测是否有被指向对象的entry,如果有的话,直接将该弱引用变量指针加入到该entry中
    • 如果没有找到对应的entry,新建一个entry,并将弱引用变量指针地址加入entry,同时检查weaktable是否扩容。

    移除

    void
    weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, 
                            id *referrer_id)
    {
        objc_object *referent = (objc_object *)referent_id;
        objc_object **referrer = (objc_object **)referrer_id;
    
        weak_entry_t *entry;
        // 指向对象为空直接返回
        if (!referent) return;
    
        // 在weak表中查找
        if ((entry = weak_entry_for_referent(weak_table, referent))) {
            
            // 找到相应记录后,将该引用从记录中移除。
            remove_referrer(entry, referrer);
            // 移除后检查该记录是否为空
            bool empty = true;
            if (entry->out_of_line()  &&  entry->num_refs != 0) {
                // 不为空 将标记记录为false
                empty = false;
            }
            else {
                // 对比到记录的每一行
                for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
                    if (entry->inline_referrers[i]) {
                        empty = false; 
                        break;
                    }
                }
            }
            // 如果当前记录为空则移除记录
    
            if (empty) {
                weak_entry_remove(weak_table, entry);
            }
        }
    
        // Do not set *referrer = nil. objc_storeWeak() requires that the 
        // value not change.
    }
    
    
    

    这段代码的主要流程

    • 从weak_table中根据找到被引用对象对应的entry,然后将弱引用变量指针referrer从entry中移除。
    • 移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table中移除
    static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
    {
        // 释放 entry 中的所有弱引用
        if (entry->out_of_line()) free(entry->referrers);
        // 置空指针
        bzero(entry, sizeof(*entry));
        // 更新 weak_table 对象数量,并检查是否可以缩减表容量
        weak_table->num_entries--;
        weak_compact_maybe(weak_table);
    }
    
    
    • 释放entry和其中的弱引用变量。
    • 更新 weak_table 对象数量,并检查是否可以缩减表容量

    entry 和 referrer
    entry以及比较熟悉了,一个对象的弱引用记录,referrer则是代表弱引用变量,每次被弱引用时,都会将弱引用变量指针referrer加入entry中,而当原对象被释放时,会将entry清空并移除

    从entry移除referrer的步骤:

    out_of_line为false时,从有序数组inline_referrers中查找并移除。
    out_of_line为true时,从哈希表中查找并移除

    dealloc
    当被引用的对象被释放后,会去检查isa.weakly_referenced标志位,每个被弱引用的对象weakly_referenced标志位都为true。

    NEVER_INLINE void
    objc_object::clearDeallocating_slow()
    {
        assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
        // 根据指针获取对应 Sidetable
        SideTable& table = SideTables()[this];
        table.lock();
        if (isa.weakly_referenced) {
            // 存在弱引用
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        if (isa.has_sidetable_rc) {
            table.refcnts.erase(this);
        }
        table.unlock();
    }
    
    
    

    从上面的代码可以看出,在对象释执行dealloc函数时,会检查isa.weakly_referenced标志位,然后判断是否要清理weak_table中的entry。

    这最后还是走到了前面的remove。

    相关文章

      网友评论

        本文标题:RunTime的应用及相关的面试问题

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