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);
}
这里分为两种情况:
- 传入的是obj类 元类VS当前类 || 根元类(元类的父类) VS 当前类 || NSObject VS当前类;tip:这种情况的对比链其实就是类和元类的继承链
- 传入的是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
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值得到
这个值与上面的
bucket_t*的值一致,说明在cache_t里面第一个成员变量存储的是bucket_t内存的首地址,我们可以通过平移的方式得到不同的bucket_t拿到他们的sel和imp
_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:消息接收者是否存在
补充
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是对空间和时间效率的一个平衡选择。具体的可以看哈希链表