前言
- 对象在底层的本质就是结构体
Clang简介
Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器。
Clang语法
clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件
如遇到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
案例1-OC本质
首先,我们建立一个person类,定义两个属性
@interface RCPerson : NSObject
@property (nonatomic, strong) NSString * name;
@property (nonatomic, strong) NSString * hobby;
@end
@implementation RCPerson
@end
通过clang编译,输出一个.cpp文件
clang -rewrite-objc main.m -o main.cpp
在.cpp文件中,我们找到了RCPerson_IMPL,它是一个结构体类型,有两个成员变量,这时我们可以再增加一个属性重新编译,发现对应的结构体中成员变量同样增加一个_变量,所以RCPerson_IMPL即我们的RCPerson对象底层,是一个结构体。
#ifndef _REWRITER_typedef_RCPerson
#define _REWRITER_typedef_RCPerson
typedef struct objc_object RCPerson;
typedef struct {} _objc_exc_RCPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_RCPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_RCPerson$_hobby;
struct RCPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_hobby;
};
我们发现RCPerson_IMPL继承于NSObject_IMPL结构体,这里的NSObject_IMPL是什么呢,搜索查看,原来就是ISA结构体
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
此外,我们还发现,平时常用的对象NSArray、NSData、NSDictionary、NSURL等,底层都是objc_object结构体
// @class NSData;
#ifndef _REWRITER_typedef_NSData
#define _REWRITER_typedef_NSData
typedef struct objc_object NSData;
typedef struct {} _objc_exc_NSData;
#endif
#ifndef _REWRITER_typedef_NSArray
#define _REWRITER_typedef_NSArray
typedef struct objc_object NSArray;
typedef struct {} _objc_exc_NSArray;
#endif
#ifndef _REWRITER_typedef_NSDictionary
#define _REWRITER_typedef_NSDictionary
typedef struct objc_object NSDictionary;
typedef struct {} _objc_exc_NSDictionary;
#endif
#ifndef _REWRITER_typedef_NSCharacterSet
#define _REWRITER_typedef_NSCharacterSet
typedef struct objc_object NSCharacterSet;
typedef struct {} _objc_exc_NSCharacterSet;
#endif
#ifndef _REWRITER_typedef_NSURL
#define _REWRITER_typedef_NSURL
typedef struct objc_object NSURL;
typedef struct {} _objc_exc_NSURL;
而Class,id,SEL这些都是结构体指针,所以我们使用时不加*, 此外他们的结构体类型不相同。
typedef struct objc_class *Class;
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
这里对应的就是属性name、hobby的set和get方法,OC方法默认有self , _cmd两个隐藏参数,get方法本质的就是通过self指针地址加上属性偏移值计算得到一个地址,再取出这个地址的值,set方法本质就是将值赋给计算得到的地址。
static NSString * _I_RCPerson_name(RCPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RCPerson$_name)); }
static void _I_RCPerson_setName_(RCPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_RCPerson$_name)) = name; }
static NSString * _I_RCPerson_hobby(RCPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RCPerson$_hobby)); }
static void _I_RCPerson_setHobby_(RCPerson * self, SEL _cmd, NSString *hobby) { (*(NSString **)((char *)self + OBJC_IVAR_$_RCPerson$_hobby)) = hobby; }
总结
- 类的定义在底层会变编译成结构体,第一个成员变量为
isa - 对象的本质为
struct objc_object类型的结构体指针 - id、SEL、Class底层为结构体指针,其它(NSString 等)为结构体
- 隐藏参数,id self 和 SEL _cmd
- 可以通过对象首地址+偏移量,做地址平移,进行set和get方法
isa探索
- 在
arm64之前的armv7、armv7s架构中,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址 - 从
arm64架构开始,对isa进行了优化,变成了一个共用体union(互斥)结构,还使用位域来存储更多的信息 - 共用体可以多个成员,其大小由最大的成员的大小决定,所有成员共享同一块大小的内存,一次只能使用其中的一个成员,对某一个成员赋值,会覆盖其他成员的值(但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对
char成员赋值就不会把整个int成员覆盖掉,因为char只占一个字节,而int占四个字节),union量的存放顺序是所有成员都从低地址开始存放的。 - 位域,sizeOf只占一个字节,这里1代表位
struct Car2 {
Bool front: 1;
Bool back : 1;
Bool left : 1;
Bool right: 1;
}
在对象本质文章中,我们知道了alloc流程:
alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> (instanceSize、calloc、initInstanceISA或initIsa)
其中obj->initIsa(cls)或initInstanceISA都会调用isa的初始化方法,我们来看看其源码流程
isa初始化方法调用如下
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
接着进入objc_object::initIsa函数中,这里new了一个isa_t类型的数据结构,然后设置关联class,再设置一些成员变量,最后赋值给isa返回
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
查看isa_t,其本质是联合体,联合体的变量是“互斥”的,所有变量共用一个地址,同一时间一个变量有效,其它数据为脏数据。
union isa_t {
isa_t() { } // 构造函数
isa_t(uintptr_t value) : bits(value) { } // 析构函数
uintptr_t bits;
private:
// 访问该类需要自定义ptrauth操作,所以
// 强制用户端执行setClass/getClass
// 私有方法
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // 定义在isa.h中
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
isa_t中有一个结构体ISA_BITFIELD,这里定义了位域信息,以arm64真机为例,掩码地址0x00007ffffffffff8ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
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。- iOS为小端模式,从低地址到高地址排列,排列结果如下(原秋大神的图)
验证isa与class关系的两种方式
- 通过mask计算,mask就是定义的面具,isa地址 & Mask得到类地址
- 通过左移、右移计算,比如x_86.64架构中,上图shiftCls为我们需要的类,右侧有3位,左侧有17位,既可以先>>3 再<<20 再>>17,得到类地址
总结
- isa_t是联合体,兼容了纯isa和非纯isa两种情况。
- init只是提供接口,便于扩展,返回的是object