OC 对象本质(isa)

450 阅读4分钟

前言:前面我们一直都在探索什么是对象,了解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理解:

image.png

因为上面我们已经分析出,对象都有有一个Class isa的成员变量,既然isa 指向的对象所属类的信息的话,那我们来分析下isa

1.2 对isa进行分析

我们在之前分析alloc 源码过程中,_class_createInstanceFromZone中有一个 obj->initIsa(cls); 点击进入后会发现 isa 是由 isa_t 初始化出来的 isa_t是一个联合体

image.png

因为isa 是一个指针占8字节,一个字节占8位,一共有64位,如果将64位只用来存储类地址肯定是有点浪费的。所以还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 __arm64__(对应ios 移动端) 和 __x86_64__(对应macOS),以下是它们的一些宏定义,如下图所示

image.png

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_rc9(这个仅为举例说明),实际上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:如果有哪里说的不对的地方,希望大家多提提建议和意见。我来修正,大家一起学习一起进步。