文章目录
- 遗漏知识补充
- 一. 面试题
- 1、isKindOfClass 与isMemberOfClass 底层探索
- 二.Cache_t 底层探索
- cache_t 底层结构
- cache_t LLDB调试
- cache_t 脱离源码调试技巧。
- cache_t 底层深入分析。
- 遗漏
遗漏知识补充
1. LLDB调试,发现 对象的 isa 和类的 isa 不一样, 而类的 isa 与元类的一样, 那是因为对象的 isa 中不仅包含了存储类, 还包含了 其他的值,如 引用计数, 是否正在释放,weak 等。
一. 面试题
1、isKindOfClass 与isMemberOfClass 底层探索
做个测试:
void lgKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
NSLog(@" \\n re1 :%hhd\\n re2 :%hhd\\n re3 :%hhd\\n re4 :%hhd\\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
NSLog(@" \\n re5 :%hhd\\n re6 :%hhd\\n re7 :%hhd\\n re8 :%hhd\\n",re5,re6,re7,re8);
}
可能会对上面的打印结果懵逼,进入底层看看,就会非常清晰,下面展示下 isMemberOfClass ,isKindOfClass的源码。
📢:打开汇编,查看真正走的isKindOfClass的源码为: objc_opt_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);
}
为了加深印象,请看iOS 对实例、类对象、元类、根元类验证
后续需要自己去玩一下!!!
二.Cache_t 底层探索
cache_t 底层结构
cache_t LLDB调试
-
通过lldb调试下底层的 cache_t是怎么存储方法的。 注意看注释
哈希结构,方便存储也方便插入、删除,结合了数组和链表的一些优点。
-
接上面的操作继续, 拿到了 $14对象,(cache_t对象),我们现在去看看它的内部,有什么方法是可以直接打印出方法名(SEL)以及实现(IMP).
inline SEL sel() const { return _sel.load(memory_order_relaxed); }
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
SEL sel = _sel.load(memory_order_relaxed);
return (IMP)
}
- LLDB的操作,接第1步,继续操作。
(lldb) p $14.sel()
(SEL) $15 = "saySomething"
(lldb) p $14.imp(nil, pClass)
(IMP) $16 = 0x0000000100003c20 (KCObjcBuild`-[LGPerson saySomething])
cache_t 脱离源码调试技巧。
- 把底层的数据结构,复制出来,自定义一个对象与系统一致,然后通过强转层我们自己定义的对象, 然后通过log日志去调试。
==来自大神的代码: ==
typedef uint32_t mask_t;
struct kc_bucket_t {
SEL _sel;
IMP _imp;
};
struct kc_cache_t {
struct kc_bucket_t *_bukets;
mask_t _maybeMask;
uint16_t _flags;
uint16_t _occupied;
};
struct kc_class_data_bits_t {
uintptr_t bits;
};
struct kc_objc_class {
Class isa;
Class superclass;
struct kc_cache_t cache;
struct kc_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
Class pClass = p.class;
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
[pClass sayHappy];
struct kc_objc_class *kc_class = (__bridge struct kc_objc_class *)(pClass);
NSLog(@"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
for (mask_t i &#61; 0; i<kc_class->cache._maybeMask; i&#43;&#43;) {
struct kc_bucket_t bucket &#61; kc_class->cache._bukets[i];
NSLog(&#64;"%&#64; - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(&#64;"Hello, World!");
}
return 0;
}
cache_t 底层深入分析。
- 找切入点。 通过插入函数&#xff0c;来分析&#xff0c;到底cache 是怎么工作的。
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
_maybeMask 的含义为总大小个数&#xff0c; _occupied的含义为当前占用的个数。
用上面的脱机代码来跑个测试&#xff0c;看看代码&#xff1a;
LGPerson *p &#61; [LGPerson alloc];
Class pClass &#61; p.class;
[p say1];
[p say2];
[pClass sayHappy];
struct kc_objc_class *kc_class &#61; (__bridge struct kc_objc_class *)(pClass);
NSLog(&#64;"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
LGPerson *p &#61; [LGPerson alloc];
Class pClass &#61; p.class;
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
[p say3];
[pClass sayHappy];
struct kc_objc_class *kc_class &#61; (__bridge struct kc_objc_class *)(pClass);
NSLog(&#64;"%hu - %u",kc_class->cache._occupied,kc_class->cache._maybeMask);
- 通过上面的代码&#xff0c;就很奇怪&#xff0c;为什么总大小会自动变&#xff0c;系统是用了什么策略进行扩容的。 接下来来探索下 objc源码。
下面是cache_t的内部结构源码&#xff0c;找到 insert犯法
- 进入方法内部
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
if (slowpath(isConstantEmptyCache())) {
if (!capacity) capacity &#61; INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, false);
}
else if (fastpath(newOccupied &#43; CACHE_END_MARKER <&#61; cache_fill_ratio(capacity))) {
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <&#61; FULL_UTILIZATION_CACHE_SIZE && newOccupied &#43; CACHE_END_MARKER <&#61; capacity) {
}
#endif
else {
capacity &#61; capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity &#61; MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
}
疑问1&#xff1a; 扩容是8个&#xff0c;为啥上面调试打印为7呢&#xff0c;继续走入 reallocate方法。
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets &#61; buckets();
bucket_t *newBuckets &#61; allocateBuckets(newCapacity);
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) &#61;&#61; newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}
遗漏
Cache_t 原理图后续补上&#xff0c;几天没睡好了&#xff0c;去补一觉