前言:前面我们一直都在探索什么是对象,了解alloc,了解内存大小是如何计算,字节是如何对齐,并没有实实在在的了解到OC对象到底是什么?今天我们来分析下
分析OC对象
1.1 创建OC对象转成底层代码.cpp进行分析
创建一个LGPerson对象并通过clang 编译器将.m 文件转成.cpp文件来进行分析
注:
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
main.mcode:
#import <Foundation/Foundation.h>
//main 文件内创建LGPerson类
@interface LGPerson :NSObject
@property(nonatomic,strong)NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
//创建实例对象
LGPerson *p =[[LGPerson alloc]init];
p.name=@"lb";
return 0;
}
通过clang 编译后main.m->main.cpp
编译.cpp命令:clang -rewrite-objc main.m -o main.cpp
code:
//代码有11万行左右 我们只分析部分关键代码
//找到LGPerson
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
分析代码:LGPerson在底层就是一个结构体 疑问?为什么多了一个struct NSObject_IMPL NSObject_IVARS;属性,接着分析 NSObject_IVARS 在.cpp文件中找到
struct NSObject_IMPL {
Class isa;
};
这个可以理解为是NSObject在底层的代码
结论
LGPerson继承于NSObject所以不难理解NSObject_IVARS可以理解为继承了NSObject的成员变量 Class isa通过这么一分析,对象在底层就是一个结构体,并且都会有一个Class isa的成员变量
分析isa
分析
isa之前 先了解下什么是联合体(union)
联合体也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉
- 缺点:包容性弱
- 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间
1.1 什么是isa?
其实我们通过查看文档,苹果官方是这么说的
A pointer to the class definition of which this object is an instance.翻译过来就是一个指向当前实例对象所属的类的指针这么说不好理解 看code理解:
因为上面我们已经分析出,对象都有有一个Class isa的成员变量,既然isa 指向的对象所属类的信息的话,那我们来分析下isa
1.2 对isa进行分析
我们在之前分析alloc 源码过程中,
_class_createInstanceFromZone中有一个obj->initIsa(cls);点击进入后会发现 isa 是由 isa_t 初始化出来的 isa_t是一个联合体
因为isa 是一个指针占8字节,一个字节占8位,一共有64位,如果将64位只用来存储类地址肯定是有点浪费的。所以还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 __arm64__(对应ios 移动端) 和 __x86_64__(对应macOS),以下是它们的一些宏定义,如下图所示
1.3 isa-64位信息描述
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位来存储引用计数的
1.4 验证isa是否含指向该实例属实类
我们通过lldb来进行验证 验证x86_64模拟器情况下 能否获取到该实例的类
error: 'plugin load' requires one argument
(lldb) p/x LGPerson.class // 先输出LGPerson地址
(Class) $0 = 0x0000000100008170 LGPerson
(lldb) x/4gx p //找到实例对象的isa地址
0x100605130: 0x001d800100008175 0x0000000100004010
0x100605140: 0x0000000000000002 0x00000001002a5b98
(lldb) p/x 0x001d800100008175 >> 3 //将isa 地址右移3位
(long) $2 = 0x0003b0002000102e
(lldb) p/x $2 << 20 //将isa 地址左移20位
(long) $3 = 0x0002000102e00000
(lldb) p/x $3 >> 17 //将isa 地址右移17位
(long) $4 = 0x0000000100008170 //地址与类所在地址一致
(lldb) po $4 //输出看是否为该实例的类
LGPerson //验证是正确的
作者言:
Ps:如果有哪里说的不对的地方,希望大家多提提建议和意见。我来修正,大家一起学习一起进步。