一、先了解一下Clang
clang是一个由Apple主导编写,基于LLVM的C/C++/OC的编译器。
主要是用于底层编译,将一些oc文件输出成c++文件,例如main.m 输出成main.cpp,其目的是为了更好的观察底层的一些结构 及 实现的逻辑,方便理解底层原理。上代码:
cd 含有main.m的文件路径
//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
报错问题
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 (⼿机)
二、初识isa
对象的本质是结构体
因为OC 是 C 与 C++ 的超集。一个对象可以有多种不同数据类型的属性,那可以容纳不同数据类型的复杂结构,当然是结构体了。
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
我们看到,对象就是这样一个结构体,且被typedef为 id 。在Foundation层 id 就表示一个对象 。
在面向对象编程中,对象是由类创建的,对象可以通过 isa 变量找到自己所属的类。
那为什么对象需要知道自己的类呢?这主要是因为对象的信息是存储在该对象所属的类中的。
这也很容易理解,一个类可以有多个对象,如果每个对象的信息都存储在各自的本身,那随着对象的不断创建,对于内存来说是灾难级的。既然对象的 isa 指针指向了类,那不妨也看看类的结构:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
Class _Nullable super_class
.......
.......
}
类里面有个 super_class ,指向了类的父类; 同时类也有一个 isa 指针,那类的 isa 指针指向了哪里呢?
对象是按照 类 所定义的各个属性和方法“生产”的,类 作为对象的模板,也可看成是对象。正如工厂里面的模子也是要专门制作模子的机器生产。元类 (meta class) 就是设计、管理类(class)的模板。对象是 类 的实例,类是 元类 的实例。
所以类的 isa 指针指向了 元类。
Objective-C语言的设计者已经考虑到了这个问题,所有元类的 isa 都指向 根元类(meta Root Class)。关于实例对象、类、元类之间的关系,苹果官方给了一张图,非常清晰的表明了三者的关系:
![](https://img.haomeiwen.com/i4023915/57bd966aa87f1bcd.png)
实线是 super_class 指针,虚线是 isa 指针。
1、Root class(class) 通常是 NSObject,NSObject 是没有超类的,所以 Root class(class)的 superclass 指向 nil。
2、每个 Class 都有一个 isa 指针指向唯一的 Meta class
3、Root class(meta)的 superclass 指向 Root class(class),也就是 NSObject,形成一个回路。
4、每个 Meta class 的 isa 指针都指向 Root class(meta)。
5、isa 指针链以一个环结束:实例指向类-指向元类-指向根元类-到自身。元类的 isa 指针并不重要,因为在现实世界中,没人会向元类对象发送消息。
扩展:
在 objc2.0 中,所有的对象都会包含一个 isa_t 类型的结构体。同时,因为 objc_class 继承自 objc_object,所以所有的类也包含这样一个 isa。在优化之前,isa 只是一个指向类或元类的指针,而优化之后,采取了联合体结构,同样是占用8字节空间,但存储了更多的内容。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
二、深入了解isa
结构体(struct)中所有变量是“共存”的——优点是“有容乃⼤”,
全⾯;缺点是struct内存空间的分配是粗放的,不管⽤不⽤,全分配。
联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”; 逻辑教育
但优点是内存使⽤更为精细灵活,也节省了内存空间
我们以 arm64 架构为例,则 isa_t 可以表示成如下所示的代码,如下:
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
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 deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19
}
};
isa_t 是一个联合体,这里所占空间为 8字节,共64位。
nonpointer(存储在第0字节) : 是否为优化isa标志。0代表是优化前的isa,一个纯指向类或元类的指针;1表示优化后的isa,不止是一个指针,isa中包含类信息、对象的引用计数等。现在基本上都是优化后的isa。
has_assoc (存储在第1个字节): 关联对象标志位。对象含有或者曾经含有关联引用,0表示没有,1表示有,没有关联引用的可以更快地释放内存(dealloc的底层代码有体现)。
has_cxx_dtor(存储在第2个字节): 析构函数标志位,如果有析构函数,则需进行析构逻辑,如果没有,则可以更快速地释放对象(dealloc的底层代码有体现)。
shiftcls (存储在第3-35字节)存储类的指针,其实就是优化之前 isa 指向的内容。在arm64架构中有33位用来存储类指针。x86_64架构有44位。
magic(存储在第36-41字节):判断对象是否初始化完成, 是调试器判断当前对象是真的对象还是没有初始化的空间。
weakly_referenced(存储在第42字节):对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放(dealloc的底层代码有体现)。
deallocating(存储在第43字节):标志对象是否正在释放内存。
has_sidetable_rc(存储在第44字节):判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储
网友评论