与类相关的概念有,对象,类,元类
对象由类生成,类由元类生成
对象可以有多个,类都是单例。
苹果官方isa走位图和继承图
isa流程图.png由探索Objective-C中的class和对象在C++中的原理
typedef struct objc_class * Class // 可知Class 是一个struct objc_class结构的指针
在objc源码中搜索 struct objc_class { 发现:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE; //OBJC2 不可用
可以发现一个定义,但是有注释,objc2不可用。继续探索,搜索struct objc_class
发现有挺多的,在objc-runtime-old和objc-runtime-new.h里都有定义,猜测应该是objc-runtime-new.h这里的
如图发现继承objc_object
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
那么objc_class的存储结构是
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom
}
isa 组成
验证isa走位图和继承图
运行objc源码,然后lldb调试如下
//isa 走位验证
(lldb) p teacher
(LGTeacher *) $0 = 0x000000010223a070
(lldb) x/4gx 0x000000010223a070
0x10223a070: 0x011d800100008369 0x0000000000000000
0x10223a080: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008369 & 0x00007ffffffffff8 //LGTeacher对象的isa指向
(long) $1 = 0x0000000100008368
(lldb) po 0x0000000100008368
LGTeacher
(lldb) x/4gx 0x0000000100008368 //LGTeacher 类的内存
0x100008368: 0x0000000100008390 0x00000001000083b8
0x100008378: 0x000000010034f390 0x0000802800000000
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8 //LGTeacher类的isa指向
(long) $3 = 0x0000000100008390
(lldb) po 0x0000000100008390
LGTeacher
(lldb) po 0x00000001000083b8
LGPerson
(lldb) x/4gx 0x0000000100008390 // LGTeacher 元类的内存
0x100008390: 0x00000001003570f0 0x00000001000083e0
0x1000083a0: 0x00000001018499b0 0x0001e03100000007
(lldb) p/x 0x00000001003570f0 & 0x00007ffffffffff8 // LGTeacher 元类的isa指向
(long) $6 = 0x00000001003570f0
(lldb) po 0x00000001003570f0
NSObject
(lldb) x/4gx 0x00000001003570f0 // NSObject元类的isa 指向等于自己
0x1003570f0: 0x00000001003570f0 0x0000000100357140
0x100357100: 0x0000000101849d60 0x0004e03100000007
//isa 继承
(lldb) p teacher
(LGTeacher *) $0 = 0x000000010223a070
(lldb) x/4gx 0x000000010223a070
0x10223a070: 0x011d800100008369 0x0000000000000000
0x10223a080: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d800100008369 & 0x00007ffffffffff8
(long) $1 = 0x0000000100008368
(lldb) po 0x0000000100008368
LGTeacher
(lldb) x/4gx 0x0000000100008368 //LGTeacher 类的内存
0x100008368: 0x0000000100008390 0x00000001000083b8
0x100008378: 0x000000010034f390 0x0000802800000000
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8
(long) $3 = 0x0000000100008390
(lldb) po 0x0000000100008390
LGTeacher
(lldb) po 0x00000001000083b8 //查看LGTeacher继承的类
LGPerson
(lldb) x/4gx 0x00000001000083b8 //LGPerson类的内存
0x1000083b8: 0x00000001000083e0 0x0000000100357140
0x1000083c8: 0x000000010034f390 0x0000802800000000
(lldb) po 0x0000000100357140 //查看LGPerson继承的类
NSObject
...
如上验证,虽然不全,但是都通过了,我们对objc_class的存储结构和isa走位图及继承图有了更加深刻的了解。
探索类的cache_t
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__ // 判断是否是64位机器
uint16_t _flags; // 2
#endif
uint16_t _occupied; // 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
};
}
可以看出cache_t 结构大小为 8 + 8 = 16字节
lldb调试查看cache_t结构
(lldb) p/x [LGTeacher class]
(Class) $1 = 0x00000001000084e0 LGTeacher
(lldb) x/4gx 0x00000001000084e0
0x1000084e0: 0x0000000100008508 0x0000000100008530
0x1000084f0: 0x00000001007bb360 0x0001804000000003
(lldb) p (cache_t *)0x1000084f0 //转换成cache_t 赋值给$3
(cache_t *) $3 = 0x00000001000084f0
(lldb) p *$3 //*$3 查看$3内容
(cache_t) $4 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4303074144
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 3
}
}
_flags = 32832
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001804000000003
}
}
}
}
(lldb) p $4.buckets() //深看cache_t结构,发现了buckets方法,可以返回buckets指针
(bucket_t *) $7 = 0x00000001007bb360
(lldb) p *$7
(bucket_t) $8 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = (null)
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p *($7+1)
(bucket_t) $9 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 49040
}
}
}
(lldb) p *($7+1)
(bucket_t) $12 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 49040
}
}
}
(lldb) p $12.sel()
(SEL) $13 = "saySomething"
(lldb) po $12.imp(nil, $1)
(KCObjcBuild`-[LGPerson saySomething])
可以看到cache_t中缓存了父类LGPerson的saySomething方法的sel和imp
结构代码转换调试方法
通过上面源码和探索,可以声明一个类似的结构来探索,代码如下
#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct kc_bucket_t {
SEL _sel;
IMP _imp;
};
struct kc_cache_t {
struct kc_bucket_t *_bukets; // 8
mask_t _maybeMask; // 4
uint16_t _flags; // 2
uint16_t _occupied; // 2
};
struct kc_class_data_bits_t {
uintptr_t bits;
};
// cache class
struct kc_objc_class {
Class isa;
Class superclass;
struct kc_cache_t cache; // formerly cache pointer and vtable
struct kc_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
Class pClass = p.class; // objc_clas
[p say1];
[p say2];
[p say3];
[p say4];
[p say1];
[p say2];
// [p say3];
[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);
// 0 - 8136976 count
// 1 - 3
// 1: 源码无法调试
// 2: LLDB
// 3: 小规模取样
// 底层原理
// a: 1-3 -> 1 - 7
// b: (null) - 0x0 方法去哪???
// c: 2 - 7 + say4 - 0xb850 + 没有类方法
// d: NSObject 父类
for (mask_t i = 0; i<kc_class->cache._maybeMask; i++) {
struct kc_bucket_t bucket = kc_class->cache._bukets[i];
NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
cache_t 源码探究
首先找到插入方法,因为如果需要缓存的话,就需要先插入
void insert(SEL sel, IMP imp, id receiver);
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
初始化的缓存大小4
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
否则使用的内存加1小于等于开辟空间的3/4或者7/8时,正常插入(和架构有关)
#if CACHE_ALLOW_FULL_UTILIZATION //缓存允许100%使用时
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
仔细看变量,CACHE_ALLOW_FULL_UTILIZATION = 1,FULL_UTILIZATION_CACHE_SIZE = 8,就是说否则在缓存小于8的时候是允许存放满的
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
这里扩大内存,重新开启一块双倍内存,原来存的内容不再拷贝过来,最大缓存1 << 16
void cache_t::incrementOccupied()
{
_occupied++;
}
// 每次添加_occupied + 1
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
// objc_msgSend uses mask and buckets with no locks.
// It is safe for objc_msgSend to see new buckets but old mask.
// (It will get a cache miss but not overrun the buckets' bounds).
// It is unsafe for objc_msgSend to see old buckets and new mask.
// Therefore we write new buckets, wait a lot, then write new mask.
// objc_msgSend reads mask first, then buckets.
#ifdef __arm__
// ensure other threads see buckets contents before buckets pointer
mega_barrier();
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed);
// ensure other threads see new buckets before new mask
mega_barrier();
_maybeMask.store(newMask, memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
// ensure other threads see buckets contents before buckets pointer
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
// ensure other threads see new buckets before new mask
_maybeMask.store(newMask, memory_order_release);
_occupied = 0; //归零
#else
#error Don't know how to do setBucketsAndMask on this architecture.
#endif
}
从上面代码看出,扩大空间的时候_occupied 归0. _maybeMask赋值newCapacity - 1
- cache_t 缓存初始化的缓存大小4
- 开辟空间的3/4时正常插入
- CACHE_ALLOW_FULL_UTILIZATION = 1时,缓存小于8的时候是允许存放满的
- 在不满足上面条件的时候,重新开启一块双倍内存,原来存的内容不再拷贝过来,最大缓存1 << 16
- 扩大空间的时候_occupied 归0. _maybeMask赋值newCapacity - 1
- 每次存储新的bucket, _occupied + 1
得知cache_t的含义:
_occupied : 缓存的个数
_maybeMask:开辟的空间
_bucketsAndMaybeMask: buckets的首地址,所以也可以通过bucketsAndMaybeMask直接得到
// Sign newImp, with &_imp, newSel, and cls as modifiers.
uintptr_t encodeImp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, IMP newImp, UNUSED_WITHOUT_PTRAUTH SEL newSel, Class cls) const {
if (!newImp) return 0;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
return (uintptr_t)
ptrauth_auth_and_resign(newImp,
ptrauth_key_function_pointer, 0,
ptrauth_key_process_dependent_code,
modifierForSEL(base, newSel, cls));
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (uintptr_t)newImp ^ (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (uintptr_t)newImp;
#else
#error Unknown method cache IMP encoding.
#endif
}
imp的存储方式
探索方法调用到cache缓存的过程
断点可知
image.png
由此有调用链:_objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> cache::insert
image.png汇编正向可以看出调用了objc_msgSend,搜索objc_msgSend
image.png
找到调用_objc_msgSend_uncached,补全调用链
objc_msgSend -> _objc_msgSend_uncached -> lookUpImpOrForward -> log_and_fill_cache -> cache::insert
网友评论