runtime 结构模型
本文完整版共三篇:
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 上)
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 中)
- 阿里、字节:一套高效的iOS面试题(一 – runtime 结构模型 – 下)
题目来自 阿里、字节:一套高效的iOS面试题
1 介绍下runtime的内存模型(isa、方针、类、metaclass、结构体的存储信息等)
概括
NSObject 内存仅有一个 Class isa 的成员变量。Class 是一个 objc_class,它承继自 objc_object,内部存储着 superclass、缓存 cache、数据 bits。重点是 bits,其内部存储一个 class_rw_t,rw 指向 class_rw_ext_t 或 class_ro_t。class_ro_t 保存着编译器就确认的成员变量,特点,办法,协议,成员变量布局,weak 成员变量布局。class_rw_rxt_t 内部存有有一个指向 class_ro_t 的指针,还有一些运转时参加的特点,办法,协议等。
isa:是一个联合体。内存存储着 non-pointer(1)、has_assoc(1)、has_cxx_dtor(1)、shiftcls(33 superclass)、magic(6)、weakly_referenced(1)、has_sidetable_rc(1)、extra_rc(19)
描绘
@interface NSObject <NSObject> {
Class isa;
}
@end
typedef objc_class * Class;
typedef objc_object * id;
struct objc_object {
char isa_storage[sizeof(isa_t)];
}
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
struct class_data_bits_t {
uintptr_t bits;
}
union isa_t {
uintptr_t bits;
private:
Class cls;
public:
struct {
/// 0 代表一般的 isa 指针,存储 Class、MetaClass
/// 1 代表优化过的指针,可存储更多信息:如引证计数
/// 64 位机型下均是 1
uintptr_t nonpointer : 1;
/// 是否存在相关方针,若没有能够更快开释
uintptr_t has_assoc : 1;
/// 是否存在 C++ 析构器,若没有能够更快开释
uintptr_t has_cxx_dtor : 1;
/// 存储类型
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
/// 在 DEBUG 环境下是否完结初始化
uintptr_t magic : 6;
/// 是否被弱引证过,若没有能够更快开释
uintptr_t weakly_referenced : 1;
/// 是否正在开释
uintptr_t deallocating : 1;
/// 是否存在额定的引证计数记载
/// 当引证记载过大无法存储在 isa.extra_rc 时,引证计数则会存储在一个 sidetable 中
uintptr_t has_sidetable_rc : 1;
/// 额定的引证计数值
uintptr_t extra_rc : 19
};
};
2 为什么要规划metaclass
为了保持类的共同性。inst.isa = class, class.isa = metaclass, metaclass.isas = rootclass, rootclass.isa = rootclass
- 调用实例办法时在 inst.isa,也便是类的办法列表中查找
- 调用类办法时在 cls.isa,也便是在元类的办法列表中查找
3 class_copyIvarList & class_copyPropertyList差异
- class_copyIvarList 拷贝一切的成员变量,包含特点对应的成员变量
- clsss_copyPropertyList 仅拷贝特点,不过包含分类中的特点
4 class_rw_t 和 class_ro_t 的差异
- class_ro_t 只读,代表在编译期间就已确认的成员变量,特点,办法,契合的协议等
- class_rw_t 可读可写,包含该类包含在分类中的一切成员变量,特点,办法,协议。内部存在一个 class_ro_t 指针。(新版本内是一个指向 class_rw_ext_t 或 class_ro_t 的指针)
- class_rw_ext_t 可读可写,同老版 class_rw_t
5 category 怎么被加载的,两个 category 的 load 办法的加载次序,两个 category 的同名办法的加载次序
- category 在
load_images
将didInitialAttachCategories
设置为 true 时加载- 若方针类已完结,则直接附加到方针类中
- 若方针类未完结,则增加至 unattachedCategories 中
- category 的 load 办法在一切非延时类完结之后,假如方针类还未完结则完结
- 不同的 category 的 load 办法加载次序取决于其在 Compile Sources 中的次序
- category 中的办法是增加在办法列表的头部的。所以,假如方针类是同一个,后加载的 category 中的办法会“掩盖” 先加载的同名办法的(不止能“掩盖” category 中的同名办法,也能“掩盖”类自身的同名办法)
6 category & extension 差异,能给 NSObject增加 Extension 吗,结果怎么
- category 能够在恣意当地,其增加的特点是没有实例变量的
- extension 是隐藏在类自身 .m 文件中的。人如其名,是扩展,其增加的特点是有成员变量的
关于体系类来说,咱们是拿不到 .m 文件的,所以给体系类增加的 extension 相当于 category,没有意义
7 音讯转发机制,音讯转发机制和其他言语的音讯机制好坏对比
- objc_msgSend
- 办法调用
- receiver 的 NilOrTaggedTest
- 接受者的 nil tagged 监测
- CacheLookUp
- 缓存查找
- MethodTableLookUp
- 办法列表查找
- lookUpImpOrForward
- 查找办法的 IMP
- resolveMethod:
- 动态派发
- forwardingTargetForSelector
- 音讯转发
- forwardInvocation
- 音讯转发
- 长处
- 灵活:编译期间并不确认真正的函数调用地址,仅仅把办法调用重写为
objc_msgSend
。在运转期间才会经过上述操作确认函数地址
- 灵活:编译期间并不确认真正的函数调用地址,仅仅把办法调用重写为
- 缺陷
- 功率略低:需求查找,需求更多的时间。办法 cache 便是为了提高功率而规划的,但仍然比不上直接调用来得快
8 在办法调用的时分,办法查询-> 动态解析-> 音讯转发 之前做了什么
办法调用时,办法查询之前。所以跟编译期间没有任何关系。所以这儿的答案是:
- 检测 receiver 状况:nil 或 tagged
- 在缓存中查找完结
9 IMP、SEL、Method 的差异和运用场景
struct method_t {
SEL name;
const char *types;
IMP imp;
}
是什么 | 运用场景 | |
---|---|---|
IMP | 办法的详细完结,相当于 C 函数,能够调用 | 为类增加办法,替换办法完结 |
SEL | 选择子,仅仅个音讯称号,能够经过 SEL 找到 IMP 或 Method | 日常办法调用,及相关判别 |
Method | 一个保存了办法完整信息的结构体:办法名 name、办法类型 types、详细完结 IMP | 办法交流 method Swizzling |
10 load、initialize 办法的差异什么?在承继关系中他们有什么差异
load | initialize | |
---|---|---|
调用机遇 | main 函数之前 | 榜首次接收到音讯时 |
父类的 load 会先于子类被调用 | 父类的 +initialize 会先于子类被调用 | |
是否需求手动 super | 不需求(看上一条) | 不需求(看上一条) |
加载次序 | 取决于在 Compile Sources 中的次序 | 取决于在 Compile Sources 中的次序 |
承继关系 | 彼此独立,也会独立功效 | 彼此独立,但后增加的 initialize 会 “掩盖” 先增加的 |
11 说说音讯转发机制的好坏
看第 8 个问题
深化一下~
源码来自 objc-866.9
runtime
,运转时,是 Objective-C 的中心。正是由于 runtime
的存在,OC 才能称为一门动态言语。
先来看看 官方说明:
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language.
Objective-C 运转时是一个为 Objective-C 言语供给动态特点的运转时库。
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically.
Objective-C 言语尽或许多地将决策从编译时和链接时推迟到运转时。只需有或许,它都动态地履行操作。
This means that the language requires not just a compiler, but also a runtime system to execute the compiled code.
这意味着 Objective-C 不止需求一个编译器,还需求一个运转时体系来履行编译后的代码。
The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
这个运转时体系就相当于 Objective 言语的操作体系,这也正是 Objective-C 言语动态的原理。
关于静态言语来说,编译期就能确认每个函数的真正调用地址。而关于动态言语来说,办法调用时一个动态的进程,编译期只能确认办法的接受者 receiver
和音讯自身 _cmd
。
在运用 OC 编程的时分,能够给恣意方针发音讯, receiver
针对这条音讯的呼应跟处理操作都是在运转时才决议的。
与 runtime
进行交互的方式有三种,便是网上流传甚广的这张图:
- OC 源码(
Objective-C Code
)
只需咱们是运用 OC 在写代码,便是在与 runtime 体系交互了。就比如这个:
NSObject *obj = [[NSObject alloc] init];
- NSObject 界说办法(
Framework & Service
)
在 OC 的世界里,其实只要两个类:NSObject
与 NSProxy
,两者都完结了 NSObject 协议。前者便是咱们日常运用的;后者是是一个虚基类,专门用于完结音讯代理,咱们能够子类化并完结 forwardInvocation:
与 methodSignatureForSelector:
来处理自己没有完结的音讯。
在 NSObject 协议中,声明晰一切 OC 方针的公共办法。其间,存在五个能够从 runtime 获取信息的 方针自省办法:
/// 回来当时方针的类
- (Class)class;
/// 是否是特定类(承继链检查)
- (BOOL)isKindOfClass:(Class)aClass;
/// 是否是特定类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
/// 是否契合指定协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
/// 是否能呼应指定音讯
- (BOOL)respondsToSelector:(SEL)aSelector;
- 直接调用 API(
Runtime API
)
首先能够在 这儿 检查 runtime API 的详细文档。
不过,从 Xcode 5 之后,Apple 不建议咱们手动调用 runtime API,所以默许封闭了 runtime API 的代码提示,咱们能够在这儿进行设置:
默许值为 YES,改为 NO 即可。
1、一切从 NSObject
开端
先看一下界说:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
1.1 创立一个 NSObject
咱们先来看一下 NSObject
的创立进程,先简略地看一下 alloc
:
方针创立 – alloc
打上断点:
运转程序,进入断点后翻开汇编(勾选 “Xcode 菜单中的【Debug > Debug Workflow > Always Show Disassembly】”)
咱们到 NSObject.mm
找到 objc_alloc
:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
持续,看看 callAlloc
:
/// 调用时传入 checkNil = true, allocWithZone = false
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
#if __OBJC2__
/// 判别 Class cls 是否存在,不重要
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
/// 音讯转发流程,调用 alloc
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
slowpath
与fastpath
不用介意,这是为了告诉编译器这个判别逻辑的大概率倾向,当做不存在即可
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
首先,这儿判别 !cls->ISA()->hasCustomAWZ())
,咱们一个个点进去看一下 hasCustomAWZ()
到底是何方神圣?
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
bool getBit(uint16_t flags) const {
return _flags & flags;
}
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
///+ 类或其父类存在默许的 alloc/allocWithZone: 完结,存储在元类中
#define FAST_CACHE_HAS_DEFAULT_AWZ (1<<14)
翻译一下:类或许父类是否存在默许的 alloc / allocWithZone:
完结。存储元类 metaclass
中(这个先不着急)
先持续往下看
- 假如 默许的
alloc / allocWithZone:
完结 存在:
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
///+ allocWithZone 在 __OBJC2__ 环境下疏忽 zone 参数
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
这个分支暂时就先看到这儿,_class_createInstanceFromZone()
一瞬间再说!!!
- 假如 默许的
alloc / allocWithZone:
完结 不存在 :
此刻进入下边的分支:
/// 调用时传入 checkNil = true, allocWithZone = false
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
///... 这儿此刻不重要,由于调用时 allocWithZone 为 false
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
/// 音讯转发流程,调用 alloc
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
此刻应该履行这一句 ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc))
,究竟 allocWithZone = false
。也就相当于 [cls alloc]
:
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
又到了 callAlloc
, 只不过这次传入的参数变成了 checkNil: false, allocWithZone: true :
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
好,那就看看 +[cls allocWithZone:]
:
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
///+ allocWithZone 在 __OBJC2__ 环境下疏忽 zone 参数
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
有没有一点眼熟???仍是 _class_createInstanceFromZone()
!!!梅开二度,那就盘它:
虽然在调用
_objc_rootAllocWithZone()
时的第二个参数一个是nil
,一个是zone
,但是请注意_objc_rootAllocWithZone()
的内部完结中,有这样一句备注// allocWithZone under OBJC2 ignores the zone parameter
在 Objc2 中 zone 是被疏忽的,所以两者完全相同,
完结上也是如此,第三个参数传入的 nil
/// cls = cls
/// extraBytes = 0
/// zone = nil
/// construct_flags = OBJECT_CONSTRUCT_CALL_BADALLOC = 2
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
/// 是否存在 C++ 结构器
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
/// 是否存在 C++ 析构器
bool hasCxxDtor = cls->hasCxxDtor();
/// 是否能够快速 alloc()
bool fast = cls->canAllocNonpointer();
size_t size;
/// 获取类实例所占空间
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
/// 请求空间
if (zone) {
/// 这个分支暂时不用看,由于调用时没有传入 zone
/// 其实是用于 copy 办法
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
/// 空间请求失利
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
/// 初始化 ISA
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);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
/// 依据承继链结构 C++ 成员
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
从注释能够看出来流程,重点看一下 初始化 isa,先看一下 initInstanceIsa
:
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
也调用到了 initIsa
办法,这个先按下不表,一瞬间再说。
看一下 object_cxxConstructFromClass
办法:
id
object_cxxConstructFromClass(id obj, Class cls, int flags)
{
id (*ctor)(id);
Class supercls;
supercls = cls->superclass;
/// 假如父类存在 C++ 结构办法,递归调用
/// 若在某一层构建失利,则抛弃构建,alloc 就失利了
if (supercls && supercls->hasCxxCtor()) {
bool ok = object_cxxConstructFromClass(obj, supercls, flags);
if (slowpath(!ok)) return nil; // some superclass's ctor failed - give up
}
// Find this class's ctor, if any.
/// 在当时类的办法列表中查找 C++ 结构办法
/// 只在当时类的办法列表中查找,若找不到,则缓存并回来 objc_*msgForward(* _objc_*msgForward_impcache)*
ctor = (id(*)(id))lookupMethodInClassAndLoadCache(cls, SEL_cxx_construct);
if (ctor == (id(*)(id))_objc_msgForward_impcache) return obj; // no ctor - ok
/// 履行找到的 C++ 构建办法
if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok
supercls = cls->superclass; // this reload avoids a spill on the stack
/// C++ 构建失利了,履行整理操作
// This class's ctor was called and failed.
// Call superclasses's dtors to clean up.
if (supercls) object_cxxDestructFromClass(obj, supercls);
if (flags & OBJECT_CONSTRUCT_FREE_ONFAILURE) free(obj);
if (flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
到这儿,alloc 就完毕了。(本来的英文注释藏着,能够对照着看。。。)
方针创立 – init
咱们直接翻开 NSObject.mm
,找到 -init
办法:
- (id)init {
return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
return obj;
}
就这么点,完了!!! init
啥也没做。也即是说,默许状况下,init
啥也没做。那为何还要保存 init
这个操作呢?我个人针对这有两个理解:
-
承继。一个类在初始化的时分,必需求考虑父类在创立时分的操作,所以咱们需求在
init
办法里调用[super init]
。 -
自界说。咱们能够在
init
办法内来增加一些额定的操作,以到达自界说的意图。
方针创立 – new
除了 [[NSObject alloc] init]
。咱们也能够运用 [NSObject new]
来创立一个实例。来看看源码:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
由于 new
是个关键字,所以没办法 Cmd + click。想看的话再 NSObject.mm
中查找 + (id)new {
。
一看就理解了: alloc + init。
总结一下创立方针流程(抄来的图):
这个图结合源码基本就很理解了。
1.2 isa
- (Class)class
这个办法应该是众所周知的,其回来当时方针的详细类型。咱们来看看完结:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA(/*authenticated*/true);
/// 剩下的部分代码是从 TaggedPointer 获取到这个指针的类型
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
/// 去地址后 4 位和 0xf 做 & 操作得到类型符号
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
/// 从一切 tag 类型中查找类型
cls = objc_tag_classes[slot];
/// 假如没找到,则经过相同的操作在 ext tag 类型查找
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
看一下 isa_t
的结构:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
Class cls;
public:
struct {
ISA_BITFIELD; // defined in isa.h
};
};
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
/// 0 代表一般的 isa 指针,存储 Class、MetaClass
/// 1 代表优化过的指针,可存储更多信息:如引证计数
/// 64 位机型下均是 1
uintptr_t nonpointer : 1; \
/// 是否存在相关方针,若没有能够更快开释
uintptr_t has_assoc : 1; \
/// 是否存在 C++ 析构器,若没有能够更快开释
uintptr_t has_cxx_dtor : 1; \
/// 存储类型
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
/// 在 DEBUG 环境下是否完结初始化
uintptr_t magic : 6; \
/// 是否被弱引证过,若没有能够更快开释
uintptr_t weakly_referenced : 1; \
/// 是否正在开释
uintptr_t deallocating : 1; \
/// 是否存在额定的引证计数记载
/// 当引证记载过大无法存储在 isa.extra_rc 时,引证计数则会存储在一个 sidetable 中
uintptr_t has_sidetable_rc : 1; \
/// 额定的引证计数值
uintptr_t extra_rc : 19
# define ISA_HAS_INLINE_RC 1
# define RC_HAS_SIDETABLE_BIT 44
# define RC_ONE_BIT (RC_HAS_SIDETABLE_BIT+1)
# define RC_ONE (1ULL<<RC_ONE_BIT)
# define RC_HALF (1ULL<<18)
先来验证下以上注释,这儿运用真机验证(模拟器与真机需求核算对应位时偏移不同),先编写一段能够将 isa 信息拆出来的代码:
struct isa_struct {
uintptr_t isa;
};
uintptr_t p = ((__bridge struct isa_struct *)obj)->isa;
/// iOS 运用小端形式
uintptr_t nonpointer = p & 0x1;
uintptr_t has_assoc = p & 0x2;
uintptr_t has_cxx_dtor = p & 0x4;
// 该值输出没有意义,稍后运用 Xcode 调试指令检查
// shiftcls = p & 0x0000000ffffffff8UL; // ISA_MASK
uintptr_t magic = (p & 0x000003f000000001UL) >> 39;
uintptr_t weakly_referenced = (p & 0x0000040000000001UL) >> 42;
uintptr_t deallocating = (p & 0x0000080000000001UL) >> 43;
uintptr_t has_sidetable_rc = (p & 0x0000100000000001UL) >> 44;
uintptr_t extra_rc = (p & 0xffff800000000001UL) >> 45;
如图所示,榜首行 log 为初始化状况:
- 红框:增加相关方针后,
has_assoc
位由 0 变为 1 - 篮框:增加弱引证后,
weakly_referenced
位由 0 变为 1 - 绿框:在增加强引证后,
extra_rc
的值会增加对应的数字 - 黄框:在
extra_rc
到达存储最大值(524287,无符号 19 位最大值)后,再增加引证计数时,runtime 会将其一半的引证计数存储至散列表SideTable
中,详细可检查 objc-object.h 中的rootRetain
办法 - 红色箭头:po 输出核算得到的
shiftcls
与obj.class
共同
关于 shiftcls 也能够采用位移的方式将除 shiftcls 的其他位归零再运用 Xcode 调试指令输出即可:
/// 移除 nonpointer, has_assoc, has_cxx_dtor
pointer >> 3 << 3;
/// 移除 magic, weakly_refrenced, deallocating, has_sidetable_rc, _extra_rc
pointer << (19 + 1 + 1 + 1 + 6) >> (19 + 1 + 1 + 1 + 6)
如图:
假如还想要验证
has_cxx_dtor
,只需给这个类增加一个 C++ 类型的成员变量即可,例如 string
现在来阅览一下在创立 NSObject 没说的 initIsa
:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
/// 其实也就以前的 newisa.shiftcls = (uintptr_t)cls >> 3;
newisa.setClass(cls, this);
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.isa_storage,也就相当于直接赋值
isa() = newisa;
}
1.3 retain
& release
已然说到 isa.extra_rc
中存储这部分引证计数,那么就来看一下引证计数到底是变化的
retatin
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
/// 获取 isa
oldisa = LoadExclusive(&isa().bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_retain()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClassfalse)->hasCustomRR())) {
ClearExclusive(&isa().bits);
if (oldisa.getDecodedClassfalse)->canCallSwiftRR()) {
return swiftRetain.load(memory_order_relaxed)((id)this);
}
return ((i(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
}
/// 未优化的 isa,运用 sidetable_retain()
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa().bits);
return (id)this;
}
}
do {
transcribeToSideTable = false;
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa().bits);
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain(sideTableLocked);
}
// don't check newisa.fast_rr; we already called any RR overrides
///Junes 正在析构...
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa().bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
if (slowpath(tryRetain)) {
return nil
} else {
return (id)this;
}
}
uintptr_t carry;
/// 引证计数 extra_rc++
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
/// 溢出
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (variant != RRVariant::Full) {
ClearExclusive(&isa().bits);
/// 从头调用该函数,第二个参数 handleOverflow 为 true
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
/// 保存一半引证计数
/// 预备将另一半复制到 side table 中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa().bits, &oldisa.bits, newisa.bits)));
if (variant == RRVariant::Full) {
/// 将另一半引证计数复制到 side table
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
} else { }
return (id)this;
}
简略总结一下:
- 判别是否为 tagged pointer,是则直接 return;
- 判别该方针的 isa 是否优化过,若未优化,则运用
sidetable_retain
存储引证计数; - 若正在开释,则不进行 retain 操作;
- 引证计数 +1;
- 若引证计数 +1 后溢出了(extra_rc 仅能存储 524287),则将一半的引证计数存储到 sidetable 中。
当然,在 retain 进程中,需求对 sidetable 进行桎梏操作。
release
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
/// 假如是 tagged pointer 直接 return
if (slowpath(isTaggedPointer())) return false;
bool sideTableLocked = false;
isa_t newisa, oldisa;
oldisa = LoadExclusive(&isa().bits);
if (variant == RRVariant::FastOrMsgSend) {
// These checks are only meaningful for objc_release()
// They are here so that we avoid a re-load of the isa.
if (slowpath(oldisa.getDecodedClass(false)->hasCustomRR())) {
ClearExclusive(&isa().bits);
if (oldisa.getDecodedClass(false)->canCallSwiftRR()) {
swiftRelease.load(memory_order_relaxed)((id)this);
return true;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
return true;
}
}
if (slowpath(!oldisa.nonpointer)) {
// a Class is a Class forever, so we can perform this check once
// outside of the CAS loop
if (oldisa.getDecodedClass(false)->isMetaClass()) {
ClearExclusive(&isa().bits);
return false;
}
}
retry:
do {
newisa = oldisa;
/// 未优化的 isa
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa().bits);
return sidetable_release(sideTableLocked, performDealloc);
}
if (slowpath(newisa.isDeallocating())) {
ClearExclusive(&isa().bits);
if (sideTableLocked) {
ASSERT(variant == RRVariant::Full);
sidetable_unlock();
}
return false;
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
/// 引证计数 extra_rc--
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
/// 是否下溢出,溢出则意味着存储在 isa.extra_rc 本来就已经为 0 了,需求从 sidetable 中拿到引证计数再做 -1 操作
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits))); /// 更新 isa
if (slowpath(newisa.isDeallocating()))
goto deallocate;
if (variant == RRVariant::Full) {
if (slowpath(sideTableLocked)) sidetable_unlock();
} else { }
return false;
/// 下溢出了,意味着需求减少该方针在 sidetable 中的引证计数
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (variant != RRVariant::Full) {
/// 有借位保存,从头进入该函数并传入 performDealloc
ClearExclusive(&isa().bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa().bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
oldisa = LoadExclusive(&isa().bits);
goto retry;
}
// Try to remove some retain counts from the side table.
/// 从 sidetable 中获取借位引证计数,获取最大值为 RC_HALF
auto borrow = sidetable_subExtraRC_nolock(RC_HALF);
bool emptySideTable = borrow.remaining == 0; // we'll clear the side table if no refcounts remain there
if (borrow.borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
bool didTransitionToDeallocating = false;
/// 已从 sidetable 中获取到引证计数,做 -1 操作
newisa.extra_rc = borrow.borrowed - 1; // redo the original decrement too
newisa.has_sidetable_rc = !emptySideTable;
bool stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);
if (!stored && oldisa.nonpointer) {
/// 保存失利,再试一次
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
uintptr_t overflow;
newisa.bits =
addc(oldisa.bits, RC_ONE * (borrow.borrowed-1), 0, &overflow);
newisa.has_sidetable_rc = !emptySideTable;
if (!overflow) {
stored = StoreReleaseExclusive(&isa().bits, &oldisa.bits, newisa.bits);
if (stored) {
didTransitionToDeallocating = newisa.isDeallocating();
}
}
}
if (!stored) {
/// 再试一次还没成功,则将引证计数放入 sidetable 中再试一次
// Inline update failed.
// Put the retains back in the side table.
ClearExclusive(&isa().bits);
sidetable_addExtraRC_nolock(borrow.borrowed);
oldisa = LoadExclusive(&isa().bits);
goto retry;
}
// Decrement successful after borrowing from side table.
if (emptySideTable)
sidetable_clearExtraRC_nolock();
if (!didTransitionToDeallocating) {
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
}
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
/// 没有借位保存的状况
deallocate:
// Really deallocate.
ASSERT(newisa.isDeallocating());
ASSERT(isa().isDeallocating());
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
/// 调用 dealloc 办法
if (performDealloc) {
this->performDealloc();
}
return true;
}
简略总结一下:
- 判别是否为 tagged pointer,是则直接 return;
- 判别该方针的 isa 是否优化过,若未优化,则直接调用
sidetable_release
进行操作然后 return; - 测验将
isa.extra_rc
-1,若未下溢出则履行第 5 步; - 若下溢出了,从 sidetable 中取出 RC_HALF 并将该值 -1 存储到
isa.extra_rc
中(期间有重试流程); - 履行 dealloc 操作 。
dealloc
横竖都看到这儿,就再看一下方针时怎么开释的:
- (void)dealloc {
_objc_rootDealloc(self);
}
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
/// 若能够快速开释,则直接开释
if (fastpath(isa().nonpointer &&
!isa().weakly_referenced &&
!isa().has_assoc &&
!isa().has_cxx_dtor &&
!isa().has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
/// 毁掉该实例方针
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
/// 次序很重要
/// 依据承继链毁掉 C++ 成员变量
if (cxx) object_cxxDestruct(obj);
/// 移除相关方针
if (assoc) _object_remove_associations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
/// 整理一切 weak 引证
/// 清空 sidetable 中的自己
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
}
总结起来便是:
- 依据承继链毁掉 C++ 成员变量,履行其析构办法;
- 移除相关方针;
- 清空自身的 weak 引证(悉数置为 nil);
- 整理 sidetable 中的自己,
- 调用 free 开释空间。
2. 类是怎么被加载的
类要加载的话,那么必须得有类加载的环境,咱们先看一下环境是怎么预备的
2.1 _objc_init
先来看一下 objc-os.mm 中的这段代码:
/*
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
*/
void _objc_initvoid)
{
/// 仅初始化一次
static bool initialized = false;
if (initialized) return;
initialized = true;
/// 读取会影响 runtime 的环境变量。如有需求,也可打印出来
environ_init();
/// 履行 C++ 静态结构办法
static_init();
/// runtime 初始化
runtime_init();
/// libobjc 反常处理体系初始化
exception_init();
/// 缓存初始化
cache_t::init();
/// 回调机制初始化,一般不做任何事情,究竟一切事情都是懒加载的。但关于某些特定进程,需求尽早加载回调机制。
_imp_implementationWithBlock_init();
_dyld_objc_callbacks_v1 callbacks = {
1, // version
&map_images, /// dyld 映射镜像完结时回调:加载由 dyld 映射供给的镜像
load_images, /// dyld 镜像加载完结时回调:加载一切 category,履行 +load 办法,包含类与 category
unmap_image, /// dyld 镜像触摸映射完结时回调:反映射
_objc_patch_root_of_class /// 补全 dyld 给定的类:包含 initIsa, setSuperclass, cache.initializeToEmpty
};
/// 注册刚构建的回调
_dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);
didCallDyldNotifyRegister = true;
}
先看一眼函数注释:引导程序初始化,经过 dyld 注册镜像告诉器。由 libSystem 在库初始化之前调用。
environ_init
源码就不贴了,不过能够看一下到底有哪些能够影响 runtime 的环境变量,在 target 的环境变量中参加 OBJC_HELP
再运转即可:
其间第三行与第五行输出 “XXX is set” 是由于我在环境变量中增加了 OBJC_HELP
与 OBJC_PRINT_OPTION
。
static_init
static void static_init()
{
size_t count1
///+ 从 __**DATA,** __DATA_CONST, __**DATA_DIRTY 获取一切以** __objc_init_func 最初的办法,并履行
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count1);
for (size_t i = 0; i < count1; i++) {
inits[i]();
}
size_t count2;
///+ 从 __**TEXT 区获取一切以** __objc_init_func 最初的办法,并履行
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count2);
for (size_t i = 0; i < count2; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
函数注释:libc 在 dyld 调用咱们的静态结构函数之前会先调用 _objc_init
,所以咱们不得不自己来做这个调用操作。
getLibobjcInitializers
其实是由宏界说来的:
#define GETSECT(name, type, sectname)
type *name(const headerType *mhdr, size_t *outCount) {
return getDataSection<type>(mhdr, sectname, nil, outCount);
}
type *name(const header_info *hi, size_t *outCount) {
return getDataSection<type>(hi->mhdr(), sectname, nil, outCount);
}
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
所以打开一下:
UnsignedInitializer *getLibobjcInitializers(const headerType *mhdr, size_t *outCount) {
return getDataSection<type>(mhdr, "__objc_init_func", nil, outCount);
}
UnsignedInitializer *getLibobjcInitializers(const header_info *hi, size_t *outCount)
{
return getDataSection<type>(hi->mhdr(), "__objc_init_func", nil, outCount);
}
而 getDataSection
长这样:
template <typename T>
T* getDataSection(const headerType *mhdr, const char *sectname,
size_t *outBytes, size_t *outCount)
{
unsigned long byteCount = 0;
T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount);
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount);
}
if (!data) {
data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount);
}
if (outBytes) *outBytes = byteCount;
if (outCount) *outCount = byteCount / sizeof(T);
return data;
}
至于 UnsignedInitializer
,长这样:
struct UnsignedInitializer {
private:
uintptr_t storage;
public:
UnsignedInitializer(uint32_t offset) {
storage = (uintptr_t)&_mh_dylib_header + offset;
}
void operator () () const {
using Initializer = void(*)();
Initializer init =
ptrauth_sign_unauthenticated((Initializer)storage,
ptrauth_key_function_pointer, 0);
init();
}
};
将以上几段代码串联起来,static_init
中榜首个循环其实便是测验先后从 __DATA、__DATA_CONST、__DATA_DIRTY 区获取以 __objc_init_func
字符串最初的办法,并履行。而第二个循环则是从 _TEXT 区获取以 __objc_init_func
字符串最初的办法,并履行。
有关于 _DATA 等区的概念查找 Mach-O 文件,也可直接至 Mach-O 文件格局探索 检查
runtime_init
这儿其实比较简略,它仅仅仅仅结构了 unattachedCategories
、allocatedClasses
两张表以便后续加载类运用。
void runtime_init(void)
{
/// disable class_rx_t pointer signing enforcement
/// 禁用 class_rx_t 指针签名的履行
objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
exception_init
初始化反常处理体系,由 map_images 调用。
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
/// 未产生反常,持续监测
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
/// 产生了反常,检查一下是否是 objc 反常
@try {
/// 测验抛出并持续运转
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
/// 当时反常是 objc 方针,测验履行 Foundation 处理程序
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
/// 当时反常不是 objc 方针,履行 C++ 反常处理
(*old_terminate)();
}
}
}
其流程是:
- 持续检查是否存在反常;
- 产生反常时,先测验抛出反常并测验运转,
- 若该反常未被处理,检查反常是否由 objc 方针产生;
- 假如当时反常是由 objc 方针产生,则利用该方针调用已注册的反常回调
uncaught_handler
; - 假如当时反常不是有 objc 方针产生,调用本来的停止程序
再来检查一下 uncaught_handler
:
static void _objc_default_uncaught_exception_handler(id exception)
{
}
static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;
objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
///设置 反常回调
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn;
return result;
}
能够看到,默许的 uncaught_handler
为空完结,开发者可经过 objc_setUncaughtExceptionHandler
自行设置反常处理程序。可如下操作:
#import <objc/objc-exception.h>
void handleException(id exception) {
NSLog(@"产生反常:%@", exception);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
objc_setUncaughtExceptionHandler(handleException);
SomeObject *obj = [SomeObject new];
/// 这是一个不存在办法,会抛出反常
[obj performSelector:@selector(test)];
}
return 0;
}
运转一下:
上边的 _objc_terminate
中在监测到反常时,会抛出反常交于开发者处理,那么咱们只需捕获到这个反常并处理掉就能够持续运转了,所以咱们将产生反常的当地修改一下:
@try {
/// 这是一个不存在办法,会抛出反常
[obj performSelector:@selector(test)];
} @catch (id e) {
NSLog(@"捕获到了反常");
}
再运转一下,看看结果:
cache_t::init
void cache_t::init()
...
...
mach_msg_type_number_t count = 0;
kern_return_t kr;
/// 经过统计已注册的 task_restartable_range_t 核算得到需求新建用到的下标
while (objc_restartableRanges[count].location) {
count++;
}
/// 在体系当时的 task(其实便是这个进程)注册这个新的 task_restartable_range_t
/// 这儿就牵扯到苹果系体系的进程资源分配,有爱好的自行查阅
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count);
}
这个 objc_restartableRanes 一看便是个数组,其内部到底是什么呢?
extern "C" task_restartable_range_t objc_restartableRanges[];
/// 用户空间中可回收运用的一个区域 section
typedef struct {
/// 在体系的开始地址
mach_vm_address_t location;
/// 这个区域的总长度
unsigned short length;
/// 基于开始方位已运用的偏移
unsigned short recovery_offs;
/// 当时并未运用的字段
unsigned int flags;
} task_restartable_range_t;
2.2 _dyld_objc_register_callbacks
这句非常重要:经过 dyld 注册一些回调(其源码在 dyld 中):
- map_images
- load_images
- unmap_image
- _objc_patch_root_of_class
一个一个来看都干了些什么?
map_images
dyld 完结镜像映射时回调该办法,在这儿加载由 dyld 映射供给的镜像。
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
bool takeEnforcementDisableFault;
{
/// 经过作用于加锁解锁
mutex_locker_t lock(runtimeLock);
/// 加载由 dyld 映射完结之后供给的镜像
map_images_nolock(count, paths, mhdrs, &takeEnforcementDisableFault);
}
...
...
}
能够发现,其主要逻辑均在 map_images_nolock
中,阅览一下:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[],
bool *disabledClassROEnforcement)
{
static bool firstTime = YES;
static bool executableHasClassROSigning = false;
static bool executableIsARM64e = false;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
*disabledClassROEnforcement = false;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
///+ 榜首次初始化时调用
///+ 在二进制库初始化之前调用
///+ 决议是否禁用同享缓存优化
if (firstTime) {
preopt_init();
}
// Find all images with Objective-C metadata.
///+ 经过 OC 元数据查找一切的镜像
hCount = 0;
// Count classes. Size various table based on the total.
///+ 一切类的数目
int totalClasses = 0;
///+ 未优化类的数目
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
///+ 遍历一切的 mach_header
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
///+ 从这个 mach_header 中获取 header_info 并增加到链表尾部
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
///+ 这个 mach_header 的文件类型为可履行文件
// Size some data structures based on main executable's size
///+ 依据主可履行文件调整一些数据结构的 size
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
///+ 假如 dyld3 优化了这个主可履行文件,那么在这个动态映射中不需求任何的 选择器引证
///+ 因而咱们能够只初始化一个 size 为 0 的映射。
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
///+ 从 “__objc_selrefs” 区中获取 selector 引证**
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
///+ 从 “__objc_msgrefs” 区中获取 音讯引证**
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
///+ 假如这是一个 GC app,停止
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason(...);
}
#endif
//+ 检测该 header_info 是否存在经过签名的 class_ro_t 指针
if (hasSignedClassROPointers(hi)) {
executableHasClassROSigning = true;
}
}
///+ 将该 header_info 放入数组中
hList[hCount++] = hi;
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
///+ 履行一切必须延后履行的 runtime 初始化流程,直到找到可履行文件。这部分操作需求再未来的初始化流程之前完结
///+(假如可履行文件不包含 OC 代码但 OC 在之后动态加载,那么这个可履行文件或许不会表现在这个 infoList 中)
if (firstTime) {
///+ 初始化 selector 表,注册内部(libobjc)运用的 selector
sel_init(selrefCount);
///+ 初始化 sideTableMap,并初始化相关方针管理器 AssociationsManager,并在敞开 DebugScanWeakTables 的状况下开端扫描所引证表扫描
arr_init();
#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.
///+ 回绝任何链接到主可履行程序的 GC 镜像
///+ 刚才已经回绝了一切的 GC app
///+ 在启动后才加载的镜像将被 dyld 回绝
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason(...);
}
}
}
#endif
// If the main executable is ARM64e, make sure every image that is loaded
// has pointer signing turned on.
///+ 假如主可履行文件是 ARM64e,保证每个加载的镜像都翻开了指针签名
if (executableIsARM64e) {
bool shouldWarn = (executableHasClassROSigning
&& DebugClassRXSigning);
for (uint32_t i = 0; i < hCount; ++i) {
auto hi = hList[i];
if (!hasSignedClassROPointers(hi)) {
if (!objc::disableEnforceClassRXPtrAuth) {
*disabledClassROEnforcement = true;
objc::disableEnforceClassRXPtrAuth = 1;
}
}
}
}
if (hCount > 0) {
///+ 读取镜像
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
///+ 在一切东西都设置完结呢之后调用镜像的 load 办法
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
总结一下流程:
- 假如是榜首次进入,依据需求决议是否禁用同享缓存优化
- 从一切 mach_header 中获取一切的 header_info 并增加到链表中
- 假如该 mach_header 的文件类型为可履行文件且未被 dyld3 优化过,则读取 selector 引证与 message 引证
- 假如是榜首次进入,
- 初始化 selector 表,注册内部(libobjc)运用的 selector
- 初始化 sideTableMap
- 初始化相关方针管理器 AssociationsManager
- 敞开 DebugScanWeakTables 的状况下敞开所引证表扫描
- 回绝任何想要链接到主可履行程序的 GC 镜像
- 读取上边获取到的一切 header_info
- 履行镜像的 +load 办法
接下来看看 map_images
内部比较重要的几个子流程
preopt_init
void preopt_init(void)
{
// Get the memory region occupied by the shared cache.
///+ 获取同享缓存占用的内存区域(start 与 length)
size_t length;
const uintptr_t start = (uintptr_t)_dyld_get_shared_cache_range(&length);
///+ 将刚获取到的奉献个缓存区域设置给 dataSegmentsRanges.sharedCacheRange
if (start) {
objc::dataSegmentsRanges.setSharedCacheRange(start, start + length);
}
///+ “设置了 OBJC_DISABLE_PREOPTIMIZATION” 或 “表缺失”,则符号为失利
if (DisablePreopt) {
// OBJC_DISABLE_PREOPTIMIZATION is set
// If opt->version != VERSION then you continue at your own risk.
failure = "(by OBJC_DISABLE_PREOPTIMIZATION)";
}
else if (opt->version != 16) {
// This shouldn't happen. You probably forgot to edit objc-sel-table.s.
// If dyld really did write the wrong optimization version,
// then we must halt because we don't know what bits dyld twiddled.
///+ 这是不应该产生的。若产生了则直接停止
_objc_fatal(...);
}
else if (!opt->headeropt_ro()) {
// One of the tables is missing.
///+ 表缺失
failure = "(dyld shared cache is absent or out of date)";
}
///+ 依据 failure 符号决议是否禁用同享缓存优化
if (failure) {
// All preoptimized selector references are invalid.
///+ 一切预优化的 selector 引证均无效
preoptimized = NO;
opt = nil;
///+ 禁用同享缓存优化
disableSharedCacheOptimizations();
}
else {
// Valid optimization data written by dyld shared cache
///+ dyld 同享缓存写入的优化有效
preoptimized = YES;
}
}
所以,preopt_init
用于决议是否禁用同享缓存优化。
addHeader
///+ 从 mach_header 中获取 header_info
static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
header_info *hi;
if (bad_magic(mhdr)) return NULL;
bool inSharedCache = false;
// Look for hinfo from the dyld shared cache.
///+ 从 dyld 同享缓存中查找 header_info
hi = preoptimizedHinfoForHeader(mhdr);
if (hi) {
// Found an hinfo in the dyld shared cache.
///+ 找到了方针 header_info
// Weed out duplicates.
///+ 过滤掉已增加到链表中的
if (hi->isLoaded()) {
return NULL;
}
inSharedCache = true;
// Initialize fields not set by the shared cache
// hi->next is set by appendHeader
///+ 将当时 header_info.loaded 符号为 true
hi->setLoaded(true);
///+ DEBUG 形式下会验证 image_info
}
else
{
// Didn't find an hinfo in the dyld shared cache.
///+ 未找到 header_info,封装 header_info
// Locate the __OBJC segment
///+ 经过 "__**objc_imageinfo" 定位** __OJBC 段
size_t info_size = 0;
unsigned long seg_size;
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
if (!objc_segment && !image_info) return NULL;
// Allocate a header_info entry.
// Note we also allocate space for a single header_info_rw in the
// rw_data[] inside header_info.
///+ 拓荒 header_info 进口
///+ 此外,还在 header_info 内部的 header_info_rw[] 中拓荒了一个 header_info_rw 的空间
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
// Set up the new header_info entry.
///+ 设置这个新的 header_info 进口
hi->setmhdr(mhdr);
// Install a placeholder image_info if absent to simplify code elsewhere
///+ 假如没有,设置 image_info 占位符 {0, 0} 以简化其余代码
static const objc_image_info emptyInfo = {0, 0};
hi->setinfo(image_info ?: &emptyInfo);
///+ 将当时 header_info.loaded 符号为 true
hi->setLoaded(true);
///+ 将当时 header_info.allClassesRealized 符号为 false
hi->setAllClassesRealized(NO);
}
{
size_t count = 0;
///+ 经过 “__**objc_classlist” 在这个 header_info 中获取类列表**
if (_getObjc2ClassList(hi, &count)) {
totalClasses += (int)count;
if (!inSharedCache) unoptimizedTotalClasses += count;
}
}
///+ 将这个 header_info 增加到链表尾部
///+ 并将该 header_info 增加至数据段区域数字 dataSegmentsRanges
appendHeader(hi);
return hi;
}
所以 addHeader
便是读取指定 mach_header 并将信息封装为 header_info
然后增加到链表中。
什么是 mach_header
呢?
struct mach_header_64 {
///+ 标志符
uint32_t magic; /* mach magic number identifier */
///+ cpu 类型标志符
int32_t cputype; /* cpu specifier */
///+ cpu 类型标志符
int32_t cpusubtype; /* machine specifier */
///+ 文件类型,Mach-O 支撑多种文件格局
uint32_t filetype; /* type of file */
///+ 加载指令的树木
uint32_t ncmds; /* number of load commands */
///+ 一切加载指令的总大小
uint32_t sizeofcmds; /* the size of all the load commands */
///+ dyld 的标志
uint32_t flags; /* flags */
uint32_t reserved; /* reserved */
};
更多信息可至 Mach-O 文件格局探索
而 header_info
是什么呢?其实也没必要太究根究底,知道它
typedef struct header_info {
private:
intptr_t mhdr_offset;
///+ mhdr 偏移
intptr_t mhdr_offset;
///+ info 偏移
intptr_t info_offset;
intptr_t info_offset;
// Offset from this location to the non-lazy class list
///+ 非懒加载类列表偏移
intptr_t nlclslist_offset;
uintptr_t nlclslist_count;
// Offset from this location to the non-lazy category list
///+ 非懒加载 category 列表偏移
intptr_t nlcatlist_offset;
uintptr_t nlcatlist_count;
// Offset from this location to the category list
///+ category 列表偏移
intptr_t catlist_offset;
uintptr_t catlist_count;
// Offset from this location to the category list 2
///+ 第二个 category 列表偏移
intptr_t catlist2_offset;
uintptr_t catlist2_count;
...
当然还有一些经过这些变量设置或获取信息的函数
...
private:
header_info_rw rw_data[];
} header_info;
typedef struct header_info_rw {
...
一些办法
...
private:
#ifdef __LP64__
uintptr_t isLoaded : 1;
uintptr_t allClassesRealized : 1;
uintptr_t next : 62;
#else
///+ 是否已加载
uintptr_t isLoaded : 1;
///+ 类是否已辨认
uintptr_t allClassesRealized : 1;
///+ 下一个 node
uintptr_t next : 30;
#endif
} header_info_rw;
sel_init
void sel_init(size_t selrefCount)
{
///+ 依据给定数目初始化无需调集
namedSelectors.init((unsigned)selrefCount);
// Register selectors used by libobjc
///+ 注册内部(libobjc)运用的 selector
mutex_locker_t lock(selLock);
///+ 注册 C++ 结构与析构办法
SEL_cxx_construct = sel_registerNameNoLock(".cxx_construct", NO);
SEL_cxx_destruct = sel_registerNameNoLock(".cxx_destruct", NO);
}
SEL sel_registerNameNoLock(const char *name, bool copy) {
return __sel_registerName(name, 0, copy); // NO lock, maybe copy
}
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) lockdebug::assert_unlocked(&selLock);
else lockdebug::assert_locked(&selLock);
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
///+ 经过 name 获取。格局为 <address, name>
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
///+ 若不存在,分配空间再插入
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
- 依据给定数目初始化无序 selectors 调集
- 注册内部(libobjc)运用的办法
- 注册 C++ 结构办法
- 注册 C++ 析构办法
arr_init
void arr_init(void)
{
///+ 初始化散列表 sideTableMap
SideTablesMap.init();
///+ 初始化相关方针管理器 AssociationsManager
_objc_associations_init();
///+ 创立专用线程,敞开弱引证表扫描
if (DebugScanWeakTables)
startWeakTableScan();
}
_read_images
void _read_images(header_info **hList, uint32_t hCount, int totalCl__**objc_selrefs** asses, int unoptimizedTotalClasses)
加载镜像。该办法 400 多行,所以分开检查
榜首部分:doneOnce
这部分代码只要榜首次进入时会履行:
static bool doneOnce;
if (!doneOnce) {
doneOnce = YES;
///+ 依据需求禁用 tagged pointer
if (DisableTaggedPointers) {
disableTaggedPointers();
}
///+ 依据生成随机数,初始化 tagged pointer 混杂器
initializeTaggedPointerObfuscator();
///+ 依据类总数扩容
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
///+ 创立存储类的哈希表
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
第二部分:修正 Sel
// Note this has to be before anyone uses a method list, as relative method
// lists point to selRefs, and assume they are already fixed up (uniqued).
///+ 在任何当地运用办法列表之前,由于办法列表指向 selector 引证
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
///+ 若该 header_info 中的办法已预优化,略过
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
///+ 从 “__objc_selrefs” 区获取 selector 引证
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
///+ 获取办法地址
const char *name = sel_cname(sels[i]);
///+ 注册该办法
SEL sel = sel_registerNameNoLock(name, isBundle);
///+ 修正 selector 指向:不同类或许存在相同的办法,他们的指向是不同的
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
办法名是能够为所欲为的,但是在程序履行时可不能为所欲为。不同的类或许会有相同姓名的办法,他们指向本该不同,因而需求修正。
检查更多