We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
clang -rewrite-objc OC源文件 -o 输出的CPP文件
在OC中的定义
@interface Student : NSObject { @public int _no; int _age; } @end
转成C++之后的定义
struct Student_IMPL { struct NSObject_IMPL NSObject_IVARS; int _no; int _age; }; struct NSObject_IMPL { Class isa; }; //其实就是 struct Student_IMPL { Class isa; //8字节 int _no; //4字节 int _age; //4字节 };
对于结构体来说,和数组一样。其第一个成员的地址,即为结构体对象的地址。 所以一个OC对象的地址,实际上就是其isa指针的地址。
而这个isa是指向objc_class结构体的指针
isa
objc_class
指针
// 指针 typedef struct objc_class *Class;
Class对象其实是一个指向objc_class结构体的指针。因此我们可以说类对象或元类对象在内存中其实就是objc_class结构体。而一个指针在64位系统中所占的内存为8字节。
所以一个OC对象所占的内存至少为8字节
objc源代码
struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // 方法缓存 class_data_bits_t bits; // 具体的类信息 class_rw_t *data() { return bits.data(); } void setData(class_rw_t *newData) { bits.setData(newData); } ... } // bits.data()的实现部分: // class_rw_t是通过bits调用data方法得来的,data函数内部仅仅对bits进行&FAST_DATA_MASK操作 public: class_rw_t* data() { return (class_rw_t *)(bits & FAST_DATA_MASK); }
我们发现这个结构体继承 objc_object 并且结构体内有一些函数,因为这是c++结构体,在c上做了扩展,因此结构体中可以包含函数。我们来到objc_object内,截取部分代码
我们发现objc_object中有一个isa指针,那么objc_class继承objc_object,也就同样拥有一个isa指针,这个isa_t后面再详细描述。
isa_t
在class_data_bits_t(类信息列表)内部,还保存着class_rw_t(可读写信息列表),这些信息是可以动态修改的
class_data_bits_t
class_rw_t
struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; // 标记 uint32_t version; // 版本信息 const class_ro_t *ro; method_array_t methods; // 方法列表 property_array_t properties; // 属性列表 protocol_array_t protocols; // 协议列表 Class firstSubclass; Class nextSiblingClass; char *demangledName; #if SUPPORT_INDEXED_ISA uint32_t index; #endif ... }
在class_rw_t(可读写信息列表)内部,还保存着class_ro_t(不可变信息列表),保存着类加载进内存时就需要确定的信息
class_ro_t
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; //实例对象所占内存大小 #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; //类名 method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; //成员变量列表 const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } };
不只是 实例对象 会包含一个 isa 结构体,所有的 类对象 都包含一个 isa。在 ObjC 中 Class 的定义也是一个名为 objc_class 的结构体,到这里,我们就可以得出一个结论:Objective-C 中类也是一个对象。所以一个OC对象的本质实际上是一个包含了所有父类成员变量+自身成员变量的结构体
ObjC
Class
OC对象的本质
所有父类成员变量
自身成员变量
以 x86_64 架构下为例,可以在 ObjC 源码中可以看到这样的定义:
# define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if defined(ISA_BITFIELD) struct { ISA_BITFIELD; // defined in isa.h }; #endif };
isa_t 是一个 union 的结构体,也就是说其中的 cls 和 bits 共用同一块内存地址空间,而 isa 总共会占据 64 位的内存空间。
union
cls
bits
结构体(struct)和联合体(union)的区别 结构体struct 各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。 联合体union 各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。不允许对联合体变量名U2直接赋值或其他操作。
结构体(struct)和联合体(union)的区别
结构体struct
各成员各自拥有自己的内存,各自使用互不干涉,同时存在的,遵循内存对齐原则。一个struct变量的总长度等于所有成员的长度之和。
联合体union
各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。因而,联合体比结构体更节约内存。一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。不允许对联合体变量名U2直接赋值或其他操作。
以下是 isa 64位 bits 所代表的意思:
nonpointer: 表示是否对 isa 指针开启指针优化 0: 纯isa指针, 1: 不止是类对象地址,isa 中包含了类信息、对象的引用计数等。 has_assoc: 关联对象标志位, 0: 没有, 1: 存在。 has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有, 则可以更快的释放对象。 shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33位用来存储类指针。 magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。 weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量, 没有弱引用的对象可以更快释放。 deallocating: 标志对象是否正在释放内存。 has_sidetable_rc: 当对象引用计数大于 10 时,则需要借用该变量存储进位。 extra_rc: 当表示该对象的引用计数值,实际上是引用计数值 减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到上面的 has_sidetable_rc。
nonpointer: 表示是否对 isa 指针开启指针优化
has_assoc: 关联对象标志位,
has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有, 则可以更快的释放对象。
C++
Objc
shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33位用来存储类指针。
arm64
33
magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间。
weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量, 没有弱引用的对象可以更快释放。
ARC
deallocating: 标志对象是否正在释放内存。
has_sidetable_rc: 当对象引用计数大于 10 时,则需要借用该变量存储进位。
10
extra_rc: 当表示该对象的引用计数值,实际上是引用计数值 减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到上面的 has_sidetable_rc。
has_sidetable_rc
通过 isa 初始化的方法 initIsa 来初步了解这 64 位的 bits 的作用:
initIsa
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) { initIsa(cls, true, hasCxxDtor); } inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { if (!nonpointer) { isa = isa_t((uintptr_t)cls); } else { isa_t newisa(0); newisa.bits = ISA_MAGIC_VALUE; // 0x001d800000000001ULL newisa.has_cxx_dtor = hasCxxDtor; newisa.shiftcls = (uintptr_t)cls >> 3; isa = newisa; } }
当我们对一个 ObjC 对象分配内存时,其方法调用栈中包含了上述的两个方法,这里关注的重点是 initIsa 方法,由于在 initInstanceIsa 方法中传入了 nonpointer = true。对整个 isa 的值 bits 进行设置,传入 ISA_MAGIC_VALUE:0x001d800000000001ULL
initInstanceIsa
nonpointer = true
ISA_MAGIC_VALUE
在使用 ISA_MAGIC_VALUE 设置 isa_t 结构体之后,实际上只是设置了 nonpointer 以及 magic 这两部分的值
nonpointer
magic
其中 nonpointer 表示 isa_t 的类型
raw isa
shiftcls
在设置 nonpointer 和 magic 值之后,会设置 isa 的 has_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC的析构器(destructor),如果没有析构器就会快速释放内存。
has_cxx_dtor
newisa.has_cxx_dtor = hasCxxDtor;
在为 nonpointer、 magic 和 has_cxx_dtor 设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。
newisa.shiftcls = (uintptr_t)cls >> 3;
将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除进而减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
(lldb) po obj <Person: 0x101121230> (lldb) x/4gx 0x101121230 0x101121230: 0x001d8001000020e9 0x0000000000000000 0x101121240: 0x726573554b575b2d 0x7320747069726353 (lldb) po 0x001d8001000020e9 & 0x00007ffffffffff8ULL Person (lldb)
我们将 isa & ISA_MASK 得到了我们的 Class。这也就验证了我们之前对于初始化 isa 时对 initIsa 方法的分析是正确的。它设置了 nonpointer、magic 以及 shiftcls。
ISA_MASK
Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } inline Class objc_object::getIsa() { return ISA(); } inline Class objc_object::ISA() { return (Class)(isa.bits & ISA_MASK); }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
了解 isa 是什么
将Objective-C代码转换为C\C++代码
clang -rewrite-objc OC源文件 -o 输出的CPP文件
NSObject的OC与C++定义
在OC中的定义
转成C++之后的定义
对于结构体来说,和数组一样。其第一个成员的地址,即为结构体对象的地址。 所以一个OC对象的地址,实际上就是其isa指针的地址。
而这个
isa
是指向objc_class
结构体的指针
Class对象其实是一个指向objc_class结构体的指针。因此我们可以说类对象或元类对象在内存中其实就是objc_class结构体。而一个指针在64位系统中所占的内存为8字节。
所以一个OC对象所占的内存至少为8字节
通过
objc源代码
中去查找objc_class结构体的内容我们发现这个结构体继承 objc_object 并且结构体内有一些函数,因为这是c++结构体,在c上做了扩展,因此结构体中可以包含函数。我们来到objc_object内,截取部分代码
我们发现objc_object中有一个isa指针,那么objc_class继承objc_object,也就同样拥有一个isa指针,这个
isa_t
后面再详细描述。在
class_data_bits_t
(类信息列表)内部,还保存着class_rw_t
(可读写信息列表),这些信息是可以动态修改的最后用一张图来总结
特殊结构体 isa_t
以 x86_64 架构下为例,可以在
ObjC
源码中可以看到这样的定义:isa_t
是一个union
的结构体,也就是说其中的cls
和bits
共用同一块内存地址空间,而isa
总共会占据 64 位的内存空间。以下是
isa
64位bits
所代表的意思:isa 的初始化
通过
isa
初始化的方法initIsa
来初步了解这 64 位的 bits 的作用:nonpointer 和 magic
当我们对一个
ObjC
对象分配内存时,其方法调用栈中包含了上述的两个方法,这里关注的重点是initIsa
方法,由于在initInstanceIsa
方法中传入了nonpointer = true
。对整个isa
的值bits
进行设置,传入ISA_MAGIC_VALUE
:0x001d800000000001ULL在使用
ISA_MAGIC_VALUE
设置isa_t
结构体之后,实际上只是设置了nonpointer
以及magic
这两部分的值其中
nonpointer
表示isa_t
的类型raw isa
,也就是没有结构体的部分,访问对象的isa
会直接返回一个指向cls
的指针,也就是在 iPhone 迁移到 64 位系统之前时isa
的类型。isa
不是指针,但是其中也有cls
的信息,只是其中关于类的指针都是保存在shiftcls
中。has_cxx_dtor
在设置
nonpointer
和magic
值之后,会设置isa
的has_cxx_dtor
,这一位表示当前对象有C++
或者ObjC
的析构器(destructor),如果没有析构器就会快速释放内存。shiftcls
在为
nonpointer
、magic
和has_cxx_dtor
设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除进而减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
我们将
isa
&ISA_MASK
得到了我们的Class
。这也就验证了我们之前对于初始化isa
时对initIsa
方法的分析是正确的。它设置了nonpointer
、magic
以及shiftcls
。getIsa() 方法
The text was updated successfully, but these errors were encountered: