OC是一门面向对象的语言,万物皆对象。比如一个我们非常熟悉的NSObject,那我们如何去研究那个对象的底层实现呢?
1.通过Clang将OC文件重写成C++文件(clang -rewrite-objc xx.c -o xx.cpp)
通过Clang命令重写成Cpp文件
可以看到LGPerson 底层实现是一个 objc_object的结构体,对应的实现是LGPerson_IMPL,里面有两个成员 NSObject_IVARS,因为LGPerson继承NSObject,因此就继承了NSObject的成员,然后就是我们添加的name属性,也就是生成了_name成员变量。还有对应成员的get/set方法。
可以看出get方法中将self指针转换为char*类型 然后通过指针偏移(指针的加法)得到name成员所在的位置。
创建对象后我们得到一个结构体指针, 也就是当前这个对象的首地址,我们也知道对象成员的类型(也就是知道所占字节大小),我们就可以通过将首地址指针偏移,拿到我们想要的字段数据。
然后偏移得到了name成员的数据, 里面存放的是一个字符串指针,所以将偏移后的地址转换为NSString **,二级指针,然后*取地址得到name的内容。
set方法同理
那NSObject 存放了什么呢?
其实也一样,是objc_object的结构体。可以NSObject_IMPL中有一个isa成员,是一个Class类型,其实是一个结构体指针typedef struct objc_class *Class;
isa分析
我们知道可以isa 找到对象关联的class。在OC底层原理-alloc流程
中,我们知道对象创建的流程分3步:
1.cls->instanceSize 计算对象所需要的的内存大小
2.cls->calloc 申请开辟内存
3.cls->initInstanceIsa 通过isa,将对象于类关联
我们现在就看看是如何关联isa:
nline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
复制代码
先初始化了isa_t newisa(0);
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
复制代码
isa_t是一个共用体。共用体的特性:多种类型的共用一块内存区域,内存大小由成员中所占字节最大的类型决定。同一时间只存在一个有效的成员。
isa_t中定义了3个成员:
-
uintptr_t bits
-
Class cls
-
struct {
ISA_BITFIELD; // defined in isa.h
};
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
复制代码
**ISA_BITFIELD ** 使用了位域,减少存储空间的浪费。 uintptr_t nonpointer : 1;
代表nonpointer数据使用一个bit存储,
nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等
has_assoc:关联对象标志位,0没有,1存在
has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位,x86_64 有44位用来存储类指针
magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced:标志对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放
deallocating:标志对象是否正在释放内存
has_sidetable_rc:当对象引用计数大于 10 时,则需要借用该变量存储进位
extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到has_sidetable_rc。
我们直接讨论isa流程,初始化**isa_t newisa(0)**后,直接就bits初始化为0
当是nonpointer,设置bits,newisa.bits = ISA_MAGIC_VALUE;
define ISA_MAGIC_VALUE 0x001d800000000001ULL
10机制为:8303511812964353
此时nonpointer = 1,因为是小端存储,nonpointer所在的就是第0位,就是1
magic = 59同理, magic是从47位开始,占6位
newisa.setClass(cls, this);
是将newCls指针右移3位
取出cls指针中shiftcls复制到newCls中。
newisa.extra_rc = 1;
isa = newisa;
复制代码
然后将引用计数赋值为1,将newisa赋值给isa
总结
我们通过clang对OC文件重写为C++文件,了解到对象的本质就是一个objc_object结构体,通过对自定义对象的结构体分析,我们发现父类的IVARS,发现NSObject中的isa,是Class类型,它是一个objc_class结构体。
然后我们分析isa,发现了isa_t这个共用体,了解到共用体位域,以及bits中每块区域所代表的含义。在对象创建时,根据对象的class 指针,取出shiftcls对应的区域,赋值给isa,并与对象进行绑定。