对象的本质
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
-