isa地址指向验证
全文围绕下面这样一段代码展开分析:
@protocol PersonProtocal <NSObject> @optional - (void)protocalInstanceMethod; + (void)protocalClassMethod; @end @interface Person : NSObject<PersonProtocal> { int _age; } @property(assign, nonatomic) int height; - (void)instanceMethod; + (void)classMethod; @end @implementation Person - (void)instanceMethod {} + (void)classMethod {} @end int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [Person new]; Class personCls = [person class]; Class personMetaClass = object_getClass(personCls); NSLog(@"%p - %p - %p", person, personCls, personMetaClass); } return 0; }
上文(03-isa指针 & superclass指针)我们分析到实例对象的isa指向类对象,接下来我们调试一下:
(lldb) p/x (long)person->isa (long) $0 = 0x011d800100008579 (lldb) p/x personCls (Class) $1 = 0x0000000100008578 Person (lldb)
我们会发现person的isa地址和类对象的地址并不相同,具体是什么原因产生的?我们直接分析objc源码,我们会看到下面这样一段获取真实的isa,真实的isa要对象的地址需要&上ISA_MASK的值,才是真正的isa的值;
ISA_MASK的值:
# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL #endif
再来分析一下isa的值,会发现结论和我们前文所分析的一致,直接看图:
objc_class分析
objc_class结构
首先看一下Class的类型,为objc_class结构体指针;
typedef struct objc_class *Class;
通过objc源码再来看一下objc_class结构体,发现objc_class继承自objc_object:
struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // 方法缓存 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() const { class_rw_t *_data = bits.data(); //(bits & FAST_DATA_MASK) return _data; } ... };
再看一下objc_object,里面只有一个isa成员(其他成员已废弃、这里没有贴出来):
struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; } OBJC2_UNAVAILABLE;
- superclass:Class类型,代表指向父类的指针,占用8个字节,前文已经分析过它的作用;
- cache_t:通过以下代码分析,我们可以知道cache_t一共占用了16个字节;
#if __LP64__ typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits #else typedef uint16_t mask_t; #endif struct cache_t { private: explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8个字节 union { struct { explicit_atomic<mask_t> _maybeMask; //uint16_t 2个字节 uint32_t 4个字节 #if __LP64__ uint16_t _flags; // 2个字节 #endif uint16_t _occupied; // 2个字节 }; explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8个字节 }; ... }
- bits:我们可以看到通过调用bits的data函数,我们可以得到一个class_rw_t类型的一个结构体指针,并且bits的值是通过平移上述8(isa指针)+8+16=32个字节得到;
class_rw_t
如下摘取了里面存储的一些主要信息:class_ro_t、methods(方法列表)、properties(属性列表)、protocols(协议列表);其中class_rw_t中还会存储分类里面的内容;
struct class_rw_t { uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA uint16_t index; #endif explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; const class_ro_t *ro(); const method_array_t methods(); //方法列表 const property_array_t properties(); //属性列表 const protocol_array_t protocols(); //协议列表 }; • methods:是一个二维数组,里面放着很多个method_list_t(里面放着类本身的方法以及分类的方法),method_list_t中放着很多个method_t,method_t的结构如下: struct method_t { SEL name; //函数名 const char *types; //编码 MethodListIMP imp; //using MethodListIMP = IMP; 函数指针 };
SEL:代表方法、函数名,底层结构跟char *类似;可以通过@selector()和sel_registerName()获得;通过sel_getName()和NSStringFromSelector()转成字符串;不同类中相同名字的方法,所对应的方法选择器是相同的;
typedefstructobjc_selector *SEL;
types:包含了函数的返回值、参数的编码的字符串,可以利用@encode()返回编码该类型的字符串,具体可以参考Apple官网的映射表:
typedefid _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
接下来通过lldb调试分析一下实例方法的存储内容,类方法可以直接修改第一步p/x personMetaClass即可:
(lldb) p/x personCls (Class) $0 = 0x0000000100008658 Person (lldb) p/x 0x0000000100008658 + 0x20 //此处是因为向右平移了32个字节 (long) $1 = 0x0000000100008678 (lldb) p (class_data_bits_t *)0x0000000100008678 (class_data_bits_t *) $2 = 0x0000000100008678 (lldb) p $2->data() (class_rw_t *) $3 = 0x0000000101263340 (lldb) p $3->methods() (const method_array_t) $4 = { list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = { = { list = { ptr = 0x00000001000084e0 } arrayAndFlag = 4295001312 } } } (lldb) p $4.list (const method_list_t_authed_ptr<method_list_t>) $5 = { ptr = 0x00000001000084e0 } (lldb) p $5.ptr (method_list_t *const) $6 = 0x00000001000084e0 (lldb) p *$6 (method_list_t) $7 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3) } (lldb) p $7.get(0).big() (method_t::big) $8 = { name = "instanceMethod" types = 0x0000000100003d2e "v16@0:8" imp = 0x00000001000039d0 (KCObjcBuild`-[Person instanceMethod] at main.mm:25) } (lldb) p $7.get(1).big() //big函数返回method_t里面存储这方法名、类型等信息 (method_t::big) $9 = { name = "setHeight:" types = 0x0000000100003d40 "v20@0:8i16" imp = 0x0000000100003a00 (KCObjcBuild`-[Person setHeight:] at main.mm:19) } (lldb) p $7.get(2).big() (method_t::big) $10 = { name = "height" types = 0x0000000100003d38 "i16@0:8" imp = 0x00000001000039e0 (KCObjcBuild`-[Person height] at main.mm:19) } (lldb) p $7.get(3).big() Assertion failed: (i < count), function get, file , line 624. error: Execution was interrupted, reason: signal SIGABRT. The process has been returned to the state before expression evaluation. (lldb)
- protocols:存储着协议相关信息;
- properties:可以看到里面存储着类的属性信息,但并不包括成员变量信息;调试信息如下:
(lldb) p/x personCls (Class) $0 = 0x0000000100008660 Person (lldb) p/x 0x0000000100008660+0x20 (long) $1 = 0x0000000100008680 (lldb) p (class_data_bits_t *)0x0000000100008680 (class_data_bits_t *) $2 = 0x0000000100008680 (lldb) p $2->data() (class_rw_t *) $3 = 0x000000010144d230 (lldb) p *$3 (class_rw_t) $4 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic<unsigned long> = { Value = 4295001240 } } firstSubclass = nil nextSiblingClass = NSUUID } (lldb) p $4.properties() (const property_array_t) $5 = { list_array_tt<property_t, property_list_t, RawPtr> = { = { list = { ptr = 0x0000000100008578 } arrayAndFlag = 4295001464 } } } (lldb) p $5.list (const RawPtr<property_list_t>) $6 = { ptr = 0x0000000100008578 } (lldb) p $6.ptr (property_list_t *const) $7 = 0x0000000100008578 (lldb) p *$7 (property_list_t) $8 = { entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 5) } (lldb) p $8.get(0) (property_t) $9 = (name = "height", attributes = "Ti,N,V_height") (lldb) p $8.get(1) (property_t) $10 = (name = "hash", attributes = "TQ,R") (lldb) p $8.get(2) (property_t) $11 = (name = "superclass", attributes = "T#,R") (lldb) p $8.get(3) (property_t) $12 = (name = "description", attributes = "T@\"NSString\",R,C") (lldb) p $8.get(4) (property_t) $13 = (name = "debugDescription", attributes = "T@\"NSString\",R,C") (lldb) p $8.get(5) Assertion failed: (i < count), function get, file . error: Execution was interrupted, reason: signal SIGABRT. The process has been returned to the state before expression evaluation. (lldb)
class_ro_t
class_ro_t为class_rw_t内的一个成员,存储着编译期已经确定的协议、属性、方法、成员变量,不包含分类的信息;
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif explicit_atomic<const char *> name; protocol_list_t * baseProtocols; const ivar_list_t * ivars; //成员变量列表 const uint8_t * weakIvarLayout; property_list_t *baseProperties; const char *getName() ; method_list_t *baseMethods(); };
主要看一下成员变量的存储:
(lldb) p/x personCls (Class) $0 = 0x0000000100008660 Person (lldb) p/x 0x0000000100008660+0x20 (long) $1 = 0x0000000100008680 (lldb) p (class_data_bits_t *)0x0000000100008680 (class_data_bits_t *) $2 = 0x0000000100008680 (lldb) p $2->data() (class_rw_t *) $3 = 0x000000010152a830 (lldb) p *$3 (class_rw_t) $4 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic<unsigned long> = { Value = 4295001104 } } firstSubclass = nil nextSiblingClass = NSUUID } (lldb) p $4.ro() (const class_ro_t *) $6 = 0x0000000100008410 (lldb) p *$6 (const class_ro_t) $7 = { flags = 128 instanceStart = 8 instanceSize = 16 reserved = 0 = { ivarLayout = 0x0000000000000000 nonMetaclass = nil } name = { std::__1::atomic<const char *> = "Person" { Value = 0x0000000100003c74 "Person" } } baseMethodList = 0x0000000100008458 baseProtocols = 0x00000001000083f8 ivars = 0x00000001000084a8 weakIvarLayout = 0x0000000000000000 baseProperties = 0x00000001000084f0 _swiftMetadataInitializer_NEVER_USE = {} } (lldb) p $7.ivars (const ivar_list_t *const) $8 = 0x00000001000084a8 (lldb) p *$8 (const ivar_list_t) $9 = { entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 2) } (lldb) p $9.get(0) (ivar_t) $10 = { offset = 0x0000000100008628 name = 0x0000000100003f22 "_age" type = 0x0000000100003d3a "i" alignment_raw = 2 size = 4 } (lldb) p $9.get(1) (ivar_t) $11 = { offset = 0x0000000100008630 name = 0x0000000100003f27 "_height" type = 0x0000000100003d3a "i" alignment_raw = 2 size = 4 } (lldb) p $9.get(2) Assertion failed: (i < count), function get, file , line 624. error: Execution was interrupted, reason: signal SIGABRT. The process has been returned to the state before expression evaluation. (lldb)
总结
Class与objc_class以及objc_object的关系?
Class为为objc_class结构体指针,objc_class继承自objc_object,objc_object里面只有一个isa成员;
class_rw_t和class_ro_t的区别?
class_ro_t为class_rw_t内的一个成员,存储着编译期已经确定的类里面的协议、属性、方法、成员变量信息,不包含分类的信息;
class_rw_t里面主要存储:class_ro_t、methods(方法列表)、properties(属性列表)、protocols(协议列表)、以及分类里面的内容,不包含成员变量信息;
运行时会将class_ro_t里面内容合并到class_rw_t中(具体是在realizeClass函数中,可参考源码),之后又将分类里面的内容也合并到class_rw_t中;本文部分内容来可能来源于网络,发布的内容如果侵犯了您的权益,请联系我们尽快删除!