OC底层原理探索之cache_t分析

200 阅读3分钟

memberOf和kindOf

打开debug源码分析发现底层kindOf走的是这个方法,其实就是isa的走位图

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

这里分为两种情况:

  1. 传入的是obj类 元类VS当前类 || 根元类(元类的父类) VS 当前类 || NSObject VS当前类;tip:这种情况的对比链其实就是类和元类的继承链
  2. 传入的是obj对象 类 VS 当前类 || 元类VS当前类 || 根元类(元类的父类) VS 当前类 || NSObject VS当前类;tip:这种情况的对比链其实就是类和类的继承链,所以是一定成立的
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

1.类方法: 当前类的元类 VS 当前的类,tips: 不一定成立,类的元类和当前的类肯定不相等 2.对象方法: 当前对象的类 VS 当前的类,tips: 一定成立,当前的对象的类肯定和当前的类是相等的

bucket_t

继续回到struct objc_class我们找到cache_t,发现里面有一个跟SEL和IMP相关的bucket_t,由之前拿到bit的函数data()同理类推,我们在这里也找到了获取bucket_t具体信息的函数structbucket_t *buckets() const image.png

inline SEL sel() const { return _sel.load(memory_order_relaxed); }
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls)

_bucketsAndMaybeMask

它是cache_t里面的第一个成员变量,上面也打印了一下有值,那么这个值是不是毫无意义呢?我们打印一下_bucketsAndMaybeMask的value值得到 image.png 这个值与上面的bucket_t*的值一致,说明在cache_t里面第一个成员变量存储的是bucket_t内存的首地址,我们可以通过平移的方式得到不同的bucket_t拿到他们的sel和imp

yuque_diagram (1).jpg

_occupied 和 _maybeMask

从上面我们发现cache_t里面_occupied =1, _maybeMask = 3,我们还是回到struct cache_t里面,发现有一个函数void insert(SEL sel, IMP imp, id receiver);

void cache_t::insert(SEL sel, IMP imp, id receiver)
{
 	...
    mask_t newOccupied = occupied() + 1; // 0+1
    unsigned oldCapacity = capacity(), capacity = oldCapacity;
    if (slowpath(isConstantEmptyCache())) {
        if (!capacity) capacity = INIT_CACHE_SIZE;//首次进入:4
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    ..
    else {// 扩容 4*2 = 8 
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        //传入true 清掉脏内存回收因为数组平移是非常消耗内存的
        reallocate(oldCapacity, capacity, true); 
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1; // 首次mask =4-1=3  第二次mask = 8-1=7
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied(); // occupied = 0+1 = 1
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());
            return;
        }
        if (b[i].sel() == sel) {
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));

    bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}

insert cache_t的闭环流程

我们在源码objc_cache.mm文件里发现了流程,发现了一个objc_msg_send

 * Cache readers (PC-checked by collecting_in_critical())
 * objc_msgSend*
 * cache_getImp
 *
 * Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
 * cache_t::copyCacheNolock    (caller must hold the lock)
 * cache_t::eraseNolock        (caller must hold the lock)
 * cache_t::collectNolock      (caller must hold the lock)
 * cache_t::insert             (acquires lock)
 * cache_t::destroy            (acquires lock)
 *
 * UNPROTECTED cache readers (NOT thread-safe; used for debug info only)
 * cache_print
 * _class_printMethodCaches
 * _class_printDuplicateCacheEntries
 * _class_printMethodCacheStatistics

objc_msg_send

我们生成.cpp文件之后,发现OC调用方法实际上是一个objc_msgSend的过程,objc_msgSend("消息的接收者",消息的主体(sel+参数))

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        LGTeacher *teach = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_hr_l_56yp8j4y11491njzqx6f880000gn_T_main_c501bc_mi_2);
    }
    return 0;
}

我们定位到源码来查看,找到这里的汇编,看到p13 & mask = p16=class,而cache_t存在于class里面,侧边也验证了对象的方法存在于类中。cmp:消息接收者是否存在 image.png

补充

1.cache_t中以3/4扩容的原因:voidinsert(SEL sel, IMP imp, id receiver)

static inline mask_t cache_fill_ratio(mask_t capacity) {
    return capacity * 3 / 4;
}

这里就要涉及到哈希链表的设计规则了,扩容后的HashMap容量是之前容量的两倍。为了解决哈希冲突,默认的负载因子0.75是对空间和时间效率的一个平衡选择。具体的可以看哈希链表