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
类型的结构体指针
isa探索
- 在
arm64
之前的armv7、armv7s
架构中,isa
就是一个普通的指针,存储着Class、Meta-Class
对象的内存地址 - 从
arm64
架构开始,对isa
进行了优化,变成了一个共用体union
结构,还使用位域来存储更多的信息 - 共用体可以多个成员,其大小由最大的成员的大小决定,所有成员共享同一块大小的内存,一次只能使用其中的一个成员,对某一个成员赋值,会覆盖其他成员的值(但前提是成员所占字节数相同,当成员所占字节数不同时只会覆盖相应字节上的值,比如对
char
成员赋值就不会把整个int
成员覆盖掉,因为char
只占一个字节,而int
占四个字节),union
量的存放顺序是所有成员都从低地址开始存放的。
在对象本质文章中,我们知道了alloc
流程:
alloc -> _objc_rootAlloc -> callAlloc -> _objc_rootAllocWithZone -> _class_createInstanceFromZone -> (instanceSize、calloc、initInstanceISA或initIsa)
其中obj->initIsa(cls)
为初始化isa方法,我们来看看其源码流程
objc_object::initIsa
函数中,isa_t是核心
isa_t是一个联合体,
————–未完待续—————————
isa_t是联合体,兼容了纯isa和非纯isa两种情况。
验证isa与class关系的两种方式
1.通过mask计算,mask就是定义的面具,isa地址 & Mask得到类地址
2.通过左移、右移计算,比如x_86.64架构中,先>>3 再<<20 再>>17
3.init只是提供接口,便于扩展,返回的是object