iOS 底层原理:isa结构分析
OC对象本质
在探索oc对象本质前,先了解一个编译器:clang
Clang
- Clang是一个由Apple主导编写,基于LLVM的
C/C++/Objective-C
的轻量级编译器
。 - 源代码发布于
BSD协议下
。 - Clang将支持其
普通lambda表达式
、返回类型的简化处理
以及更好的处理constexpr关键字
。 - 主要是用于底层编译,将一些文件
输出成c++文件
,例如main.m 输出成main.cpp,其目的是为了更好的观察底层
的一些结构
及实现
的逻辑,方便理解底层原理。
探索对象本质
- 在
main.m
中自定义一个LGPerson类对象,拥有一个name
属性。
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation LGPerson
@end
- 通过终端,利用
clang
将main.m
编译成main.cpp
,有以下几种编译命令,这里使用的是第一种
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
// 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
- 打开编译好的
main.cpp
,找到LGPerson
的定义,发现LGPerson
在底层会被编译成struct
结构体-
LGPerson_IMPL
中的第一个属性其实就是isa
,是继承自NSObject
,属于伪继承
,伪继承的方式是直接将NSObject
结构体定义为LGPerson
中的第一个属性
,意味着LGPerson 拥有 NSObject中的所有成员变量
。 -
LGPerson
中的第一个属性NSObject_IVARS
等效于NSObject
中的isa
。
-
//NSObject的定义
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
//NSObject 的底层编译
struct NSObject_IMPL {
Class isa;
};
//LGPerson的底层编译
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 等效于 Class isa;
NSString *_name;
};
如下图所示
总结
-
OC对象的本质
其实就是结构体
-
LGPerson中
的isa
是继承自NSObject
中的isa
cls 与 类 的关联原理
构造数据类型的方式有以下两种:
-
结构体
(struct) -
联合体
(union,也称为共用体)
结构体
结构体
是指把不同的数据组合成一个整
体,其变量是共存的
,变量不管是否使用,都会分配内存。
- 缺点:所有属性都分配内存,比较
浪费内存
,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费 - 优点:
存储容量较大
,包容性强
,且成员之间不会相互影响
联合体
联合体也是由不同的数据类型组成
,但其变量是互斥的
,所有的成员共占一段内存
。而且共用体采用了内存覆盖技术
,同一时刻只能保存一个成员的值
,如果对新的成员赋值
,就会将原来成员的值覆盖掉
。
- 缺点:
包容性弱
。 - 优点:所有成员
共用一段内存
,使内存的使用更为精细灵活,同时也节省了内存空间
。
isa的类型 isa_t
以下是isa指针的类型isa_t
的定义,从定义中可以看出是通过联合体(union)
定义的。
union isa_t { //联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa_t
类型使用联合体
的原因也是基于内存优化
的考虑,这里的内存优化是指在isa指针中通过char + 位域
(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针
占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。
从isa_t
的定义中可以看出:
-
提供了两个成员,
cls
和bits
,由联合体的定义所知,这两个成员是互斥
的,也就意味着,当初始化isa指针时,有两种初始化方式- 通过
cls
初始化,bits无默认值
- 通过
bits
初始化,cls有默认值
- 通过
-
还提供了一个结构体定义的
位域
,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD
,这是一个宏定义
,有两个版本__arm64__
(对应ios 移动端) 和__x86_64__
(对应macOS),以下是它们的一些宏定义,如下图所示
-
nonpointer
有两个值,表示自定义的类等,占1
位-
0
:纯isa指针
-
1
:不只是类对象地址
,isa中包含了类信息
、对象的引用计数
等
-
-
has_assoc
表示关联对象标志位,占1
位-
0
:没有关联对象
-
1
:存在关联对象
-
-
has_cxx_dtor
表示该对象是否有C++/OC的析构器
(类似于dealloc),占1
位- 如果
有
析构函数,则需要做析构
逻辑 - 如果
没有
,则可以更快的释放
对象
- 如果
-
magic
用于调试器判断当前对象是真的对象
还是没有初始化的空间
,占6
位 -
weakly_refrenced
是指对象是否被指向
或者曾经指向一个ARC的弱变量
- 没有弱引用的对象可以更快释放
-
deallocating
标志对象是是否正在释放
内存 -
has_sidetable_r
c表示 当对象引用计数大于10
时,则需要借用该变量存储进位
-
extra_rc
(额外的引用计数) --- 导尿管表示该对象的引用计数值
,实际上是引用计数值减1
- 如果对象的
引用计数为10
,那么extra_rc
为9, - 如果对象的
引用计数大于10
,则需要使用到下面的has_sidetable_rc
- 如果对象的
以arm64
为例,其isa的存储情况如图所示