一.对象的本质
在探索oc对象本质前,我们需要先了解一下 clang
编译器。 clang
是一个由Apple主导编写,基于LLVM
的C/C++/OC的编译器
。主要是用于底层编译
,将一些 OC原文件输出c++文件
,例如main.m
输出成main.cpp
,其目的是为了更好的观察底层
的一些结构
及 实现
的逻辑,方便理解底层原理。
1.1 使用 clang
生成 cpp
文件
- 在
main
中自定义一个类YJPerson
,有一个属性name
- 通过终端,利用
clang
将main.m
编译成 `main.cpp
// 将 main.m 编译成
main.cpp clang -rewrite-objc main.m -o main.cpp
这样我们就生成了 main.cpp 文件
1.2 main.cpp 解读
- 打开编译好的main.cpp,找到
YJPerson
的定义,发现YJPerson
在底层会被编译成struct
结构体
1.2.1 YJPersion
// YJPersion 结构体声明
typedef struct objc_object YJPerson;
typedef struct {} _objc_exc_YJPerson;
// YJPersion 结构体实现
struct YJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
代码解读:
- 定义了一个
别名YJPerson
,该别名指向struct objc_object类型
; - 在结构体实现
YJPerson_IMPL
中,有一个成员变量NSObject_IVARS
,也就是isa
;另一个成员变量是_name
,也就是YJPerson
的属性,和OC
层面定义是一致的。
1.2.2 NSObject
// NSObject结构体声明
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
// NSObject实现-对象
struct NSObject_IMPL {
Class isa;
};
代码解读:
- 定义
别名NSObject
,同样指向struct objc_object类型
。 - 在
NSObject_IMPL
结构体实现中,有一个Class类型
的成员变量isa
;
1.2.3 底层结构关系
Class
的定义:
typedef struct objc_class *Class;
objc_object
定义-根类定义:
struct objc_object {
Class _Nonnull isa __attribute__ ((deprecated));
};
id
的定义指向 objc_object的指针:
typedef struct objc_object *id;
SEL
方法编号,方法选择器指针
typedef struct objc_selector *SEL;
解读代码:
OC层面
的NSObject
,在底层对应objc_object结构体
;- 子类的
isa
均继承自NSObject
,也就是来自objc_object
结构体; OC
中NSObject
是大多数类的根类
,而objc_object
可以理解为就是c\c++
层面的根类
。isa的类型为Class,被定义为指向objc_class的指针
。- 在开发中可以
用id来表示任意对象
,根本原因就是id被定义为指向objc_object的指针,也就指向NSObject的指针
。 SEL
方法选择器指针,方法编号。
结构关系总结:
1.2.4 get\set方法
YJPerson类
的属性,自动添加get\set方法
。见下面代码:
static NSString * _I_YJPerson_name(YJPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_YJPerson$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long id, bool bool);
static void _I_YJPerson_setName_(YJPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(structYJPerson, _name), (id)name, 0, 1);
}
通过以上代码可以发现,无论是get方法
还是set方法
,都会有两个隐藏参数
,self
和_cmd
,也就是方法接收者
和方法编号
。在获取属性时,采用指针偏移的方式,获取成员变量所在地址,转换后返回对应的类型。
objc_setProperty:
objc_setProperty
,在对实例变量进行设置时,会自动调用objc_setProperty方法
。该方法可以理解为set方法
的底层适配器,通过统一的封装,实现set方法
的统一入口。
在runtime源码
中,搜索objc_setProperty
,可以找到最终实现方法,见下段代码:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue); // retain新值
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
// 释放旧值
objc_release(oldValue);
}
本质是通过指针平移找到成员变量位置,然后进行新值的retain,旧值的release。
1.3 对象本质总结
通过工具clang
,编译生成的cpp文件
,我们可以发现,对象实质是一个结构体
。在OC层,NSObject是大多数类的根类,而objc_object可以理解为就是c\c++层面的根类
。NSObject
仅有一个实例变量Class isa
,Class实质上是指向objc_class的指针
。objc_class
的定义见下面的代码:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
…… 省略
}
`objc_class`继承自`objc_object`,所以万物皆对象!
二. 结构体/联合体/位域
2.1 结构体
结构体(struct)
是一种存储结构,特点是所有成员变量是“共存”的,优点是“有容乃⼤”,信息更全⾯;缺点是struct内存空间的分配是粗放的,不管⽤不⽤,全部分配,不够精细;
2.2 联合体(又称:共用体)
联合体(union)
也是一种存储结构,特点是各成员变量共用同一块内存。因为共用同一块内存,所以给其中一个成员变量赋值后,其它成员变量会同步该成员变量的值,如下图:
赋值 age2,age1 和 age3 也同步了age2 的值
2.3 位域(又称:位段/位字段)
位域
是一种常用于结构体中的存储技术,一般情况下结构体信息的存取以字节为单位,实际上有时存储信息只用一个字节中的几个byte位
即可,这种情况下可以使用位域
存储技术。
三. isa 探索
3.1 isa_t联合体
通过上面的说明,认识到了联合体与结构体的区别,同时了解到位域在节省内存方面的优势。而isa
,就是采用联合体结合位域,对数据进行了封装。见下面源码:
// isa 联合体
union isa_t {
// 构造方法
isa_t() { }
// 带参数构造方法
isa_t(uintptr_t value) : bits(value) { }
// bits
uintptr_t bits;
// class
Class cls;
#if defined(ISA_BITFIELD)
// 位域
struct {
ISA_BITFIELD; // defined in isa.h
};
... 无关内容,省略...
#endif
};
isa_t是一个联合体
,有三个成员: uintptr_t bits;
、Class cls;
、结构体位域:struct { ISA_BITFIELD; };
,三个成员共用一份内存,占用8
个字节内存空间。
Class cls;
非nonpointer isa时,没有对指针进行优化,直接指向类,typedef struct objc_class *Class;
uintptr_t bits;
nonpointer isa时,使用了结构体位域,针对arm64架构
和x86架构
提供了不同的位域设置规则
。
ISA_BITFIELD
定义:
#if SUPPORT_PACKED_ISA
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
// arm64 模拟器
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t weakly_referenced : 1;
uintptr_t shiftcls_and_sig : 52;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else // arm64 真机
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t unused : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
# 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
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
ISA_BITFIELD
解读:
-
nonpointer
表示自定义的类,占1
位0
:纯isa指针
1
:不只是类对象地址
,isa中包含了类信息
、对象的引用计数
等
-
has_assoc
表示关联对象标志
位,占1
位0
:没有关联
对象1
:存在关联
对象
-
has_cxx_dtor
表示该对象是否有C++/OC的析构器
(类似于dealloc
),占1
位- 如果
有
析构函数,则需要做析构
逻辑 - 如果
没有
,则可以更快的释放
对象
- 如果
-
shiftcls
表示存储类的指针的值
(类的地址), 即类信息arm64
中占33
位,开启指针优化的情况下,在arm64架构中有33
位用来存储类指针x86_64
中占44
位
-
magic
用于调试器判断当前对象是真的对象
还是没有初始化的空间
,占6
位 -
weakly_refrenced
是 指对象是否被指向
或者曾经指向一个ARC的弱变量
- 没有弱引用的对象可以更快释放
-
deallocating
标志对象是是否正在释放
内存 -
has_sidetable_rc
表示 当对象引用计数大于10
时,则需要借用该变量存储进位
-
extra_rc
(额外的引用计数) ,表示该对象的引用计数值
,实际上是引用计数值减1- 如果对象的
引用计数为10
,那么extra_rc
为9(这个仅为举例说明),实际上iPhone 真机上的extra_rc
是使用 19位来存储引用计数的
- 如果对象的
3.2 nonpointer isa初始化
在对象进行初始化过程中,_class_createInstanceFromZone
中三个重要的初始化流程:
cls->instanceSize
,计算要开辟的内存大小,16字节对齐原则
;obj = (id)calloc(1, size);
,内存空间开辟;obj->initInstanceIsa
,isa
初始化过程。
- 重点
nonpointer isa
的初始化流程!
设置断点,运行程序,过滤出我们所需要研究的GFPerson类
的初始化流程。见下图所示:
ISA_MAGIC_VALUE
的定义(这里用的是电脑,所以是 x86_64):
继续运行代码,bits
赋值ISA_MAGIC_VALUE
,赋值后,联合体 newisa 各成员的值见下图:
联合体共用一块内存,所以 bits
和 cls
值一样,那么结构体位域
呢?
继续运行代码,走到了 setClass:
进入 setClass:
setCalls 中将类的地址
右移3位
赋值给了 shiftcls
,为何要右移三位呢?因为shiftcls
前面还有3位存储着nonpointer
、has_assoc
、has_cxx_dtor
。
到这里 isa
初始化流程结束。
3.3 通过person实例
反推isa
指向
3.4 获取对象所属类
平常获取对象的类
会直接调用class方法
,那么class方法
内部实现是怎样的?见下面源码:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil
}
inline Class
objc_object::getIsa()
{
// 当前不是taggedPointer,而是nonpointor isa, 直接返回ISA()
if (fastpath(!isTaggedPointer())) return ISA(/*authenticated*/true);
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
// ISA——返回:return (Class)(isa.bits & ISA_MASK);
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
通过解读上面的代码,发现获取对象的类,最终是实现代码是(Class)(isa.bits & ISA_MASK);
,是通过对象isa & ISA_MASK
。ISA_MASK
是什么呢?见下图:
在计算器中可以发现,该掩码低3位
和高17位
全部是0
,通过对象isa & ISA_MASK
运算,会将对象isa
的低3位
和高17位
全部抹零
,等价于上面的右移3位
,左移20位
,再右移17位
操作流程。 验证一下:
两种方式结果 一模模一样样