前言:前面我们一直都在探索什么是对象,了解alloc,了解内存大小是如何计算,字节是如何对齐,并没有实实在在的了解到OC对象到底是什么?今天我们来分析下
分析OC对象
1.1 创建OC对象转成底层代码.cpp
进行分析
创建一个LGPerson
对象并通过clang
编译器将.m
文件转成.cpp
文件来进行分析
注:
Clang
是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
main.m
code:
#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:如果有哪里说的不对的地方,希望大家多提提建议和意见。我来修正,大家一起学习一起进步。