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)

描绘

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

@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_imagesdidInitialAttachCategories 设置为 true 时加载
    • 若方针类已完结,则直接附加到方针类中
    • 若方针类未完结,则增加至 unattachedCategories 中
  • category 的 load 办法在一切非延时类完结之后,假如方针类还未完结则完结
  • 不同的 category 的 load 办法加载次序取决于其在 Compile Sources 中的次序
  • category 中的办法是增加在办法列表的头部的。所以,假如方针类是同一个,后加载的 category 中的办法会“掩盖” 先加载的同名办法的(不止能“掩盖” category 中的同名办法,也能“掩盖”类自身的同名办法)

6 category & extension 差异,能给 NSObject增加 Extension 吗,结果怎么

  • category 能够在恣意当地,其增加的特点是没有实例变量的
  • extension 是隐藏在类自身 .m 文件中的。人如其名,是扩展,其增加的特点是有成员变量的

关于体系类来说,咱们是拿不到 .m 文件的,所以给体系类增加的 extension 相当于 category,没有意义

7 音讯转发机制,音讯转发机制和其他言语的音讯机制好坏对比

  1. objc_msgSend
    • 办法调用
  2. receiver 的 NilOrTaggedTest
    • 接受者的 nil tagged 监测
  3. CacheLookUp
    • 缓存查找
  4. MethodTableLookUp
    • 办法列表查找
  5. lookUpImpOrForward
    • 查找办法的 IMP
  6. resolveMethod:
    • 动态派发
  7. forwardingTargetForSelector
    • 音讯转发
  8. forwardInvocation
    • 音讯转发
  • 长处
    • 灵活:编译期间并不确认真正的函数调用地址,仅仅把办法调用重写为 objc_msgSend。在运转期间才会经过上述操作确认函数地址
  • 缺陷
    • 功率略低:需求查找,需求更多的时间。办法 cache 便是为了提高功率而规划的,但仍然比不上直接调用来得快

8 在办法调用的时分,办法查询-> 动态解析-> 音讯转发 之前做了什么

办法调用时,办法查询之前。所以跟编译期间没有任何关系。所以这儿的答案是:

  1. 检测 receiver 状况:nil 或 tagged
  2. 在缓存中查找完结

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 进行交互的方式有三种,便是网上流传甚广的这张图:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

  • OC 源码(Objective-C Code

只需咱们是运用 OC 在写代码,便是在与 runtime 体系交互了。就比如这个:

NSObject *obj = [[NSObject alloc] init];
  • NSObject 界说办法(Framework & Service

在 OC 的世界里,其实只要两个类:NSObjectNSProxy,两者都完结了 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 的代码提示,咱们能够在这儿进行设置:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

默许值为 YES,改为 NO 即可。

1、一切从 NSObject 开端

先看一下界说:

@interface NSObject <NSObject> {
    Class isa OBJC_ISA_AVAILABILITY;
}

1.1 创立一个 NSObject

咱们先来看一下 NSObject 的创立进程,先简略地看一下 alloc

方针创立 – alloc

打上断点:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

运转程序,进入断点后翻开汇编(勾选 “Xcode 菜单中的【Debug > Debug Workflow > Always Show Disassembly】”)

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

咱们到 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));
}

slowpathfastpath 不用介意,这是为了告诉编译器这个判别逻辑的大概率倾向,当做不存在即可

#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 这个操作呢?我个人针对这有两个理解:

  1. 承继。一个类在初始化的时分,必需求考虑父类在创立时分的操作,所以咱们需求在 init 办法里调用 [super init]
  2. 自界说。咱们能够在 init 办法内来增加一些额定的操作,以到达自界说的意图。

方针创立 – new

除了 [[NSObject alloc] init] 。咱们也能够运用 [NSObject new] 来创立一个实例。来看看源码:

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

由于 new 是个关键字,所以没办法 Cmd + click。想看的话再 NSObject.mm 中查找 + (id)new {

一看就理解了: alloc + init


总结一下创立方针流程(抄来的图):

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

这个图结合源码基本就很理解了。


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;

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)
如图所示,榜首行 log 为初始化状况:

  • 红框:增加相关方针后,has_assoc 位由 0 变为 1
  • 篮框:增加弱引证后,weakly_referenced 位由 0 变为 1
  • 绿框:在增加强引证后,extra_rc 的值会增加对应的数字
  • 黄框:在 extra_rc 到达存储最大值(524287,无符号 19 位最大值)后,再增加引证计数时,runtime 会将其一半的引证计数存储至散列表 SideTable 中,详细可检查 objc-object.h 中的 rootRetain 办法
  • 红色箭头:po 输出核算得到的 shiftclsobj.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)

如图:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

假如还想要验证 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;
}

简略总结一下:

  1. 判别是否为 tagged pointer,是则直接 return;
  2. 判别该方针的 isa 是否优化过,若未优化,则运用 sidetable_retain 存储引证计数;
  3. 若正在开释,则不进行 retain 操作;
  4. 引证计数 +1;
  5. 若引证计数 +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;
}

简略总结一下:

  1. 判别是否为 tagged pointer,是则直接 return;
  2. 判别该方针的 isa 是否优化过,若未优化,则直接调用 sidetable_release 进行操作然后 return;
  3. 测验将 isa.extra_rc -1,若未下溢出则履行第 5 步;
  4. 若下溢出了,从 sidetable 中取出 RC_HALF 并将该值 -1 存储到 isa.extra_rc 中(期间有重试流程);
  5. 履行 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();
  }
}

总结起来便是:

  1. 依据承继链毁掉 C++ 成员变量,履行其析构办法;
  2. 移除相关方针;
  3. 清空自身的 weak 引证(悉数置为 nil);
  4. 整理 sidetable 中的自己,
  5. 调用 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 再运转即可:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

其间第三行与第五行输出 “XXX is set” 是由于我在环境变量中增加了 OBJC_HELPOBJC_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

这儿其实比较简略,它仅仅仅仅结构了 unattachedCategoriesallocatedClasses 两张表以便后续加载类运用。

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)();
    }
  }
}

其流程是:

  1. 持续检查是否存在反常;
  2. 产生反常时,先测验抛出反常并测验运转,
  3. 若该反常未被处理,检查反常是否由 objc 方针产生;
  4. 假如当时反常是由 objc 方针产生,则利用该方针调用已注册的反常回调 uncaught_handler
  5. 假如当时反常不是有 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;
}

运转一下:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

上边的 _objc_terminate 中在监测到反常时,会抛出反常交于开发者处理,那么咱们只需捕获到这个反常并处理掉就能够持续运转了,所以咱们将产生反常的当地修改一下:

@try {
    /// 这是一个不存在办法,会抛出反常
    [obj performSelector:@selector(test)];
} @catch (id e) {
    NSLog(@"捕获到了反常");
}

再运转一下,看看结果:

阿里、字节:一套高效的iOS面试题(一 - runtime 结构模型 - 上)

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]);
    }
  }

总结一下流程:

  1. 假如是榜首次进入,依据需求决议是否禁用同享缓存优化
  2. 从一切 mach_header 中获取一切的 header_info 并增加到链表中
    • 假如该 mach_header 的文件类型为可履行文件且未被 dyld3 优化过,则读取 selector 引证与 message 引证
  3. 假如是榜首次进入,
    • 初始化 selector 表,注册内部(libobjc)运用的 selector
    • 初始化 sideTableMap
    • 初始化相关方针管理器 AssociationsManager
    • 敞开 DebugScanWeakTables 的状况下敞开所引证表扫描
    • 回绝任何想要链接到主可履行程序的 GC 镜像
  4. 读取上边获取到的一切 header_info
  5. 履行镜像的 +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;
}
  1. 依据给定数目初始化无序 selectors 调集
  2. 注册内部(libobjc)运用的办法
  3. 注册 C++ 结构办法
  4. 注册 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;
            }
        }
    }
}

办法名是能够为所欲为的,但是在程序履行时可不能为所欲为。不同的类或许会有相同姓名的办法,他们指向本该不同,因而需求修正。

检查更多