在说到对象的本质前,我们按照惯例通过一段代码来拉开我们对象的序幕。
同样是4个BOOL类型的结构体,凭什么struct1
要占3个字节,而struct2
只需要1个字节了,这是神马操作?
小伙伴,这就是位域!
位域
在内存存储过程中,有些信息(男/女)存储实际只需要1位就能解决,但使用过程,但却使用了1整个字节(1字节 = 8位),这种就是内存的浪费了。而为了避免这种内存浪费,就有了位域。
位域表示二进制位从右往左排布。比如上面struct1
和struct2
的各个变量分别的内存排布如下:
联合体
与结构体对应的数据类型就是联合体了,也叫共同体,我们下面通过一段示例了解下什么叫联合体。
从代码结果可以得到结果:
- 联合体中修改其中的某个变量会
覆盖
其他变量的值。 - 联合体所有的变量
共用一块内存
,变量之间互斥。所以联合体也叫共同体
联合体的优点就是内存更为节省,缺点是不够包容!!
对象的本质
研究本质前,我们了解下编译器Clang。
- Clang 是一个
C语言、C++、Objective-C
语言的轻量级编译器,是由Apple主导编写的 - Clang 主要用于把源文件编译成底层文件,比如把
main.m
文件编译成main.cpp
、main.o或者可执行文件。便于观察底层的逻辑结构,便于我们探究底层。
Clang
终端编译命令
//UIKit报错
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
// xcrun命令基于clang基础上进行了封装更好用
//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
复制代码
将下面代码通过Clang编译:
{
NSString * height;
}
@property(nonatomic, copy)NSString *LWname;
@property(nonatomic,assign)NSInteger age;
@end
@implementation LWPerson
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
}
return 0;
}
复制代码
编译结果:
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_LWname;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
struct LWPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
double height;
NSString *_LWname;
NSInteger _age;
};
// @property(nonatomic, copy)NSString *LWname;
// @property(nonatomic,assign)NSInteger age;
/* @end */
// @implementation LWPerson
复制代码
通过编译结果我们分析如下:
Class
类型实际是objc_class
类型的结构体指针,objc_class
是所有类的底层实现。在此我们猜测isa
可能跟类信息存在着重要的关联。NSObject
的底层实际是objc_object
,在OC中基本上所有的对象都是继承NSObject,但是真正的底层实现是objc_object
的结构体类型。而obcj_object
的成员变量结构体都是Class isa
。
所以,我们的对象实际情况:
- 对象的本质是结构体
- Person的isa是继承NSObject中的
isa
- NSObject中只有一个成员变量那就是
isa
isa
关联类
通过alloc流程 alloc
–> _objc_rootAlloc
–> callAlloc
–> _objc_rootAllocWithZone
–> _class_createInstanceFromZone
,断点在 obj->initInstanceIsa
,进入obj->initInstanceIsa
inline void 断点进入initIsa
我们发现了isa的结构类型是isa_t
,进入isa_t
从这段源码我们可以知道:
isa
的结构体是isa_t
,查看isa_t
结构体,可以看到内部实际内容存储格式是宏定义ISA_BITFIELD,继续查看:
这里我们就知道了,isa除了包含指针,还以位域的形式存储了很多其他信息,具体信息跟位域就不一一说明,可以直接查看。
nonpointer
:是否对isa指针进行优化0
表示纯isa,1
表示包含了其他信息has_accos
:是否关联对象,0
表示未关联,1
表示有关联hac_cxx_dtor
:是否有析构函数,如果有析构函数,则需要做析构逻辑,没有,则释放对象shiftcls
:类,指针地址,开启指针优化的情况下,在arm64
架构中有33
位用来存储类指针,x86_64
架构中占44
位magic
:调试器判断当前对象是真的对象
还是没有初始化
的空间weakly_referenced
:对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放unused
:是否被释放has_sidetable_rc
:是否有其他列表存储,当对象引用计数大于10时,则需要借用该变量存储进位extra_rc
:该对象的引用计数值,实际上引用计数值减1
,例如,如果对象的引用计数为10
,那么extra_rc
为9
,如果大于10
,就需要用到上面的has_sidetable_rc
isa结构总结
isa
分为nonpointer
类型和非nonpointer
。非nonpointer
类型只是一个纯指针,nonpointer
还包含了类的信息isa
是联合体+位域的方式存储信息的。采用这种方式的有点就是节省大量内存。万物皆对象,只要是对象就有isa
指针,大量的isa
就占用了很多内存,联合体公用一块内存节省了部分内存,而位域更是在节省内存的基础上存储了信息,可以说isa
指针的内存得到了充分的利用。