clang
Clang是一个C语言、C++、Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。
Clang是一个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器
2013年4月,Clang已经全面支持C++11标准,并开始实现C++1y特性(也就是C++14,这是 C++的下一个小更新版本)。Clang将支持其普通lambda表达式、返回类型的简化处理以及更 好的处理constexpr关键字。 [2]
Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
C++文件
我们通过clang命令,可以把我们OC中.m文件转化成.cpp文件,里面可以看到OC底层的C++实现,不过这个也不是100%准确,但是给我们的学习可以提供很好的参考。
我们首先创建一个对象SJPerson
,添加一个属性sjName
。
命令行进入到
main.m
目录下,执行clang -rewrite-objc main.m -o main.cpp
,我们就可以生成对应的C++文件,拖到工程中查看了。
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
(手机)
对象的本质
全局搜索SJPerson,找到最关键信息
/// 对象底层就是结构体指针
struct SJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_sjName;
};
再找一下NSObject_IMPL
struct NSObject_IMPL {
Class isa;
};
这下我们就可以看到SJPerson
在底层是个结构体,结构体有isa
指针和我们写的属性对应的成员变量_sjName
。因为iOS中大部分类都继承自NSObject
,所以这些类都有isa指针。
再看两个细节:
typedef struct objc_object SJPerson;
为什么SJPerson
的类型是objc_object
,因为NSObject
下层类型就是objc_object
。
typedef struct objc_class *Class;
Class
下层的类型就是objc_class
。Class就是一个结构体指针。
typedef struct objc_object *id;
我们看到了id
类型,iOS中id
可以指向任何类型,并且不用加*号的原因就在这里了~
接下来我们再看下sjName
的get和set方法
/// sjName的get方法
static NSString * _I_SJPerson_sjName(SJPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SJPerson$_sjName)); }
/// sjName的set方法
static void _I_SJPerson_setSjName_(SJPerson * self, SEL _cmd, NSString *sjName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SJPerson, _sjName), (id)sjName, 0, 1); }
我们看到这两个方法里面都有两个参数self
和_cmd
,但是我们在OC中并没有看到这两个参数,这两个就是隐藏参数,OC中每个方法都有这两个参数。
为什么使用self + OBJC_IVAR_$_SJPerson$_sjName
能拿到当前的成员变量呢,因为self
就是当前结构体首地址,OBJC_IVAR_$_SJPerson$_sjName
是sjName
在结构体中相对于首地址的偏移量,所以这样写可以拿到对应的成员变量。
isa探索
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in 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
指针使用的共用体和位域技术。里面有两个成员bits
和cls
,使用共用体可以大大节约内存空间。
isa
里面是怎么存放数据的呢,根据经验,我们找到里面的结构体,不出意外结构体应该是这个共用体每位数据的说明
struct {
ISA_BITFIELD; // defined in isa.h
};
ISA_BITFIELD
是个宏定义,再看下:
/// 真机
# if __arm64__
# 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
/// 模拟器
# elif __x86_64__
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# endif
每项数据说明:
- nonpointer:指针是否进行优化,0:未优化,是个单纯的指针;1:优化的指针,指针里面存储着类相关的信息
- has_assoc:是否有关联对象,如果没有,释放更快
- has_cxx_dtor:是否有C++析构函数,如果没有,释放更快
- shiftcls:类(元类)地址
- magic:调试时分辨对象是否未完成初始化
- weakly_referenced:是否被弱引用指向,如果没有,释放更快
- unused:是否正在被释放
- extra_rc:引用计数减1
- has_sidetable_rc:引用计数过大无法存在
isa
中,值为1时,引用计数会存在SideTable
中
isa的赋值过程
我们知道对象初始化最后会走到initIsa
方法来初始化,其中的nonpointer
参数的值为true
,证明初始化这个isa
是优化过的。
isa
经过newisa(0)
初始化后,每一位数据都为0。
然后进行赋值
image
赋值后的数据:
image
我们再看下一setClass
方法
给
shiftcls
赋值的时候往右移了3位,因为上面说到isa
存储类信息是从第4位开始的。
总结:对象的本质就是含有isa
指针的结构体,arm64
后,isa
利用位域技术存储了很多关于对象的信息。
网友评论