对象的本质
OC对象在底层到底是以什么样的形式存在的呢?我们借助Clang把代码编译成c++代码,更利于我们学习。
Clang
Clang是一个C、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下一个小更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。
Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
-
Clang简单命令clang -rewrite-objc main.m -o main.cpp把目标文件编译成c++文件UIKit报错问题
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,要更好用一些
-
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp(模拟器) -
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp(手机)
Clang的使用
-
新建一个
Mac工程,在main.m中新建一个简单的LRPerson类,选中main.m,然后Show in Finder
image.png 打开终端,
cd进入当前目录终端输入
clang -rewrite-objc main.m -o main.cpp回车-
在
Finder查看当前目录,发现多了一个main.cpp文件
image.png
- 双击打开
main.cpp文件,搜索LRPerson,找到如图位置
image.png
这个就是LRPerson类编译后的代码
如若不信的话,我们给LRPerson类加一个name属性,再重新生成一次。

看这两个对比图,我们发现结构体
struct里面确实多了一个成员变量_name,下面还多了setter和getter方法。由此可见,对象在底层会编译成结构体。上面的struct NSObject_IMPL NSObject_IVARS;其实就是isa。
拓展 objc_setProperty
通过clang编译后,所有的setter方法到底层都是通过objc_setProperty实现的。我们来看一下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);
}
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
联合体 位域
写一个简单的联合体(union)
@interface LRPerson : NSObject
{
//联合体
union {
char bits;
//位域
struct { //0000 0000 bits总共一个字节 8位
char front : 1; //0000 000X中,X位置存储front数据,数字1 表示front 占用1位
char back : 1; //0000 00X0中,X位置存储back数据
char left : 1;
char right : 1;
};
} _direction;
}
@end
- 联合体简单赋值与取值
#define LRDirectionFrontMask (1 << 0)
#define LRDirectionBackMask (1 << 1)
#define LRDirectionLeftMask (1 << 2)
#define LRDirectionRightMask (1 << 3)
- (instancetype)init {
if (self = [super init]) {
_direction.bits = 0b00000000;
}
return self;
}
//0000 0000
//0000 0001
//或运算
//0000 0001
//
//0000 0000
//1111 1110
//与运算
//0000 0000
- (void)setFront:(BOOL)isFront {
if (isFront) {
_direction.bits |= LRDirectionFrontMask;
}else{
_direction.bits &= ~LRDirectionFrontMask;
}
NSLog(@"%s",__func__);
}
- (BOOL)isFront {
return _direction.front;
}
- (void)setRight:(BOOL)isRight{
_direction.right = isRight;
NSLog(@"%s",__func__);
}
- (BOOL)isRight {
return _direction.right;
}
setter方法可以直接设置_direction.right,也可以设置 _direction.bits。
结构体(struct)中所有变量是“共存”的,优点是“有容乃大”,全面;缺点是struct内存空间的分配是粗放的,不管用不用,全分配。
联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 但优点是内存使用更为精细灵活,也节省了内存空间
Isa的结构信息
在之前的源码探索:alloc & init & new 源码分析有涉及到initIsa函数,我们在这里详细分析一下isa的结构。
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#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
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// 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的初始化分为两种情况
-
nonpointer为true时,用else里面的办法初始化,先new一个isa_t,接着设置它的bits、has_cxx_dtor、shiftcls等参数 -
nonpointer为false时,直接用上面isa_t((uintptr_t)cls);函数初始化
接着我们看一下isa_t的源码
isa_t
宏定义ISA_BITFIELD只截取了我们主要分析的__arm64__的环境。
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
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),有两个isa_t()的初始化函数,还有cls和bits两个成员变量,他们是互斥的。isa_t初始化的时候会初始化bits或者是cls。一般我们自己定义的类都是nonpointer isa,初始化bits。
-
bits下面的宏定义就是位域,即是各个变量所占的位置和大小。
各个变量所表示的含义为:
nonpointer表示是否对isa指针开启指针优化,0:纯isa指针;1:不只是类对象地址,isa包含了类信息、对象的引用计数等 (0号位置)has_assoc关联对象标志位,0 没有;1 存在(1号位置)has_cxx_dtor该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象 (2号位置)shiftcls存储类指针的值。开启指针优化的情况下,在arm64架构中有33位用来存储类指针。(3-35号位置)magic用于调试器判断当前对象是真的对象还是没有初始化的空间 (36-41号位置)weakly_referenced标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。(42号位置)deallocating标志对象是否正在释放内存 (43号位置)has_sidetable_rc当对象引用计数大于 10 时,则需要借用该变量存储进位 (44号位置)extra_rc表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么extra_rc为 9。如果引用计数大于 10, 则需要使用到has_sidetable_rc。 (45-63号位置)
这里拓展一下dealloc源码,看看对象在释放的时候对ias做了什么。
dealloc
搜索dealloc {

通过_objc_rootDealloc跳转到objc_object::rootDealloc函数
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
//是TaggedPointer直接返回
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && //nonpointer isa
!isa.weakly_referenced && //没有弱引用
!isa.has_assoc && //没有关联对象
!isa.has_cxx_dtor && //没有c++析构函数
!isa.has_sidetable_rc)) //没有用到变量存储进位,引用计数不大于10
{
assert(!sidetable_present());
free(this); //满足上述条件的,直接释放
}
else {
object_dispose((id)this); //else 调用object_dispose函数
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj); //调用objc_destructInstance函数
free(obj); //释放对象
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj); //有c++析构方法的,先调用析构方法
if (assoc) _object_remove_assocations(obj); //有关联对象的,移除关联对象
obj->clearDeallocating();
}
return obj;
}
对象是否能被释放的信息,都被存储在这些标志位里面。在对象被释放的时候只要获取ias里面的信息即可知道该对象能不能被释放。
isa关联类信息
我们在objc_object::initIsa函数中通过断点分析,isa的初始化过程。
-
在
initIsa函数中打上断点,等LRPerson类(自定义类)进来之后,我们开始分析。
image.png -
查看另外的两个参数
nonpointer和hasCxxDtor,皆为true
image.png

-
nonpointer为true,会调用else里面的初始化方法。
image.png -
断点在
2号位置时,打印查看newisa
image.png
刚刚初始化的newisa,里面标志位都为0。 断点在
3号位置时,打印查看newisa

newisa的bits用ISA_MAGIC_VALUE赋值后的数据,探索用的工程是Mac工程,附上相应的宏定义
# elif __x86_64__
# 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)
ISA_MAGIC_VALUE的值是0x001d800000000001ULL,控制台打印p 0x001d800000000001ULL输出结果正是上图中的bits

再打印出2进制形式结果为
0b0000000000011101100000000000000000000000000000000000000000000001

最后一位的1,正是
nonpointer,与前面结果相符合
magic对应的6位是111011,二进制换算成10进制,即为59,与结果符合
- 断点在
4号位置时
image.png
给has_cxx_dtor赋值为true后,上面的二进制0b0000000000011101100000000000000000000000000000000000000000000001变为0b0000000000011101100000000000000000000000000000000000000000000101倒数第三位改变
打印成整型为8303511812964357,与上图bits一致。

- 断点在
5号位置时,shiftcls被赋值-
首先二进制打印
cls,LRPerson结果为0b0000000000000000000000000000000100000000000000000010010011010000
image.png -
右移3位
0b0000000000000000000000000000000000100000000000000000010010011010
image.png
将shiftcls赋值之后,newisa的值为0b0000000000011101100000000000000100000000000000000010010011010101(实际就是将$23的后44位赋值给newisa的18到61位) -
将结果打印为整型为
8303516107941077
-
打印查看
newisa,同上面计算一致;cls也被赋值为了LRPerson,对象的isa与类关联起来了
image.png
-











