isa与cls的关联探索
联合体
联合体(共用体):一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。
- 所有成员占用同一段内存,修改一个成员会影响其余所有成员。
共用体使用了内存覆盖技术
,同一时刻只能保存一个成员
的值,如果对新的成员赋值,就会把原来成员的值覆盖掉
。 - 各变量是
互斥
的——缺点就是不够包容
; 但优点是内存使用更为精细灵活
,也节省
了内存空间。 - 共用体占用的内存等于
最长的成员占用的内存
。
Clang
-
Clang
是一个由Apple主导编写,基于LLVM
的C/C++/Objective-C
编译器 -
作用:借助
Clang
可以将oc
文件输出成C++
文件,方便探究其底层的一些结构、逻辑、底层的实现原理等。
简单的使用命令
//把目标文件编译成c++文件
clang -rewrite-objc main.m -o main.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
`xcode`安装的时候顺带安装了`xcrun`命令,`xcrun`命令在`clang`的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp (手机)
接下来借助Clang
来简单的探索一下OC的对象。
oc对象探索
- 准备
oc
对象LGPerson
,并添加属性name
。此时LGPeson
应该有name
和isa
两个成员。
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@property (copy, nonatomic) NSString *name;
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
- 转化输出
C++
文件,找到LGPerson
对象
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
// @property (copy, nonatomic) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1); }
// @end
- 从输出的
C++
文件可以看到LGPerson
是objc_object
的结构体。
可以看到定义的属性_name
,但是没有发现isa
。反而有一个struct NSObject_IMPL NSObject_IVARS;
。
在c++
文件中搜索NSObject_IMPL {
会发现缺少的isa
在里面。但是却变成了Class
类型。
struct NSObject_IMPL {
Class isa;
};
我们在探索alloc &init时,有initInstanceIsa
是将isa
与cls
相关联。那么应该是在关联这里做什么。下面进入源码探索一下关联的操作。
isa关联cls
-
isa
和cls
的源码关联操作
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
关联操作
可以看到isa
和cls
的关联,其实就是将cls
向右移动了3位,赋值
保存在isa
的shiftcls
的区域中。因此LGPerson
其实就保存在isa
中。
那么通过object_getClass
获取Class
时,源码底层肯定是从isa
中通过某种操作取出我们需要的Class
。
进入object_getClass
源码
可以发现object_getClass
方法就是获取isa
。
最终发现是isa
的bits
成员或者&ISA_MASK
的结果在转成Class
返回。
isa是如何存储cls
-
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的定义有Class cls
和一个uintptr_t bits
互斥的成员及结构体中宏定义的位域ISA_BITFIELD
-
nonpointer
:表示是否对isa指针
开启指针优化
0:纯isa指针
1:不止是类对象地址,isa
中包含了类信息、对象的引用计数
等 -
has_assoc
:关联对象标志位,0没有,1存在 -
has_cxx_dtor
:该对象是否有C++
或者Objc
的析构器
,如果有析构函数
,则需要做析构逻辑
, 如果没有
,则可以更快的释放对象
。 -
shiftcls
:存储类指针的值。开启指针优化
的情况下,在arm64
架构中有33位
用来存储类指针。 -
magic
:用于调试器判断当前对象是真的对象
还是没有初始化的空间
-
weakly_referenced
:对象是否被指向或者曾经指向
一个ARC
的弱变量,没有弱引用的对象可以更快释放
-
deallocating
:标志对象是否正在释放内存 -
has_sidetable_rc
:当对象引用技术大于10 时,则需要借用该变量存储进位 -
extra_rc
:当表示该对象的引用计数值
,实际上是引用计数值减 1
.
例如,如果对象的引用计数为 10,那么extra_rc
为 9。如果引用计数大于 10, 则需要使用到下面的has_sidetable_rc
arm64
下ISA_BITFIELD
位域的存储的信息
位域中shiftcls
位置的33
存储类的指针的值。那么取出isa
存储的位域中的shiftcls
的值,应该是我们的存储的Class
。
x86_64
下存储cls
的流程
- 断点
cls
为LGPerson
时进入isa
源码
isa源码
断点newisa
的初始化isa_t newisa(0);
赋初值的操作newisa.bits = ISA_MAGIC_VALUE;
分别输出:
isa初始化、赋初值
从结果可以看到cls和bits
赋值后,位域中存储的信息,也发生了变化。nonpointer
代表对isa
开启指针优化。magic
值成了59
。因为这里只是进行了赋初值那么这59
肯定来自初始值ISA_MAGIC_VALUE
。
从x86_64
结构图中看magic
是从47位
开始的占6位
。将初始值ISA_MAGIC_VALUE
输出二进制进行查看。看到从magic
位置的二进制111011
是十进制的59
。
-
cls
的赋值newisa.shiftcls = (uintptr_t)cls >> 3;
将cls
转化成uintptr_t
右移3位存储在shiftcls
区域。
存储cls
了解了cls
的存储的位置及操作。那么通过存储的逆向操作应该能从isa
中取出LGPerson
。
- 从
isa
中获取LGPerson
移动操作
通过反向对isa
的反向操作确实获得了LGPerson
。
但是通过object_getClass
获取Class
时是isa.bits& ISA_MASK
操作。
return (Class)(isa.bits & ISA_MASK);
ISA_MASK = 0x00007ffffffffff8ULL
其实就是上面的移动抹0
操作的位与运算
。遮住不需要的信息,保留需要的信息。
通过isa
的与cls
的关联存储及object_getClass
获取源码也就可以理解c++
文件中isa
是Class
类型了。
struct NSObject_IMPL {
Class isa;
};