iOS完全深度指南(深入底层版)

9 阅读49分钟

融会贯通 · 深入浅出 · 横向对比 · 纵向深度 · 底层源码级


目录


第一章:iOS 最全面最底层基础八股文

一、Objective-C 对象模型与 Runtime

1.1 对象的本质——从源码到内存

OC 对象的 C 语言底层定义:


// objc-private.h

struct objc_object {

    isa_t isa;  // 联合体,不再是简单指针

};

  


typedef struct objc_class *Class;

  


struct objc_class : objc_object {

    Class superclass;

    cache_t cache;             // 方法缓存,核心性能关键

    class_data_bits_t bits;    // 指向 class_rw_t

};

**isa_t 联合体的完整 bit-field 定义(arm64): **


union isa_t {

    Class cls;

    uintptr_t bits;

    struct {

        uintptr_t nonpointer        : 1// 0=纯指针 1=优化的isa

        uintptr_t has_assoc         : 1// 是否有关联对象

        uintptr_t has_cxx_dtor      : 1// 是否有C++/OC析构器(.cxx_destruct)

        uintptr_t shiftcls          : 33; // 类指针地址(右移3位存储,因为类地址8字节对齐)

        uintptr_t magic             : 6// 固定值0x1a,用于标识是否已初始化

        uintptr_t weakly_referenced : 1// 是否有weak指针指向

        uintptr_t unused            : 1// 未使用

        uintptr_t has_sidetable_rc  : 1// 引用计数是否溢出到SideTable

        uintptr_t extra_rc          : 19; // 引用计数值-1(能存储0~524287)

    };

};

**为什么 shiftcls 只需要 33 位就能存储 64 位指针? **

  • 类对象在内存中是8字节对齐的,所以地址的后 3 位一定为 0

  • 高位一般也用不到(用户空间地址不会超过 2^36)

  • 所以存储时右移 3 位,读取时左移 3 位还原,33 位足够

  • 具体取出 class 指针:(isa.bits >> 3) << 3isa.bits & ISA_MASK

  • arm64 的 ISA_MASK = 0x0000000ffffffff8ULL

**extra_rc 的工作方式: **

  • 对象初始化时 extra_rc = 0(表示引用计数为 1)

  • retainextra_rc++,如果溢出(超过 19 bit 最大值 524287):

  - 将 extra_rc一半搬移到 SideTable 的 refcnts 哈希表中

  - 设置 has_sidetable_rc = 1

  - extra_rc 设为原来的一半(留出空间继续增长,避免频繁操作 SideTable)

  • releaseextra_rc--,如果变为负数:

  - 从 SideTable 借一批回来

  - 如果 SideTable 中也为 0 → dealloc

1.2 class_rw_t 与 class_ro_t 的完整结构


// class_ro_t:编译期生成,只读

struct class_ro_t {

    uint32_t flags;               // 标志位(是否为元类、是否为根类等)

    uint32_t instanceStart;       // 成员变量起始偏移量

    uint32_t instanceSize;        // 实例大小(包含 isa + 所有成员变量)

    uint32_t reserved;            // 保留

    union {

        const uint8_t *ivarLayout;      // ivar 布局(用于 GC,ARC 下也有用)

        Class nonMetaclass;

    };

    explicit_atomic<const char *> name;  // 类名

    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;   // 编译期的方法列表

    protocol_list_t *baseProtocols;     // 编译期的协议列表

    const ivar_list_t *ivars;           // 成员变量列表(运行时不可修改!)

    const uint8_t *weakIvarLayout;      // weak ivar 布局

    property_list_t *baseProperties;    // 属性列表

};

  


// class_rw_t:运行时创建

struct class_rw_t {

    uint32_t flags;

    uint16_t witness;

    explicit_atomic<uintptr_t> ro_or_rw_ext;  // 指向 class_ro_t 或 class_rw_ext_t

  


    Class firstSubclass;

    Class nextSiblingClass;

  


    // 以下方法用于获取方法/属性/协议列表

    // 如果没有 class_rw_ext_t,直接从 class_ro_t 获取

    // 如果有 class_rw_ext_t,从 ext 获取(ext 包含动态添加的)

};

  


// class_rw_ext_t:iOS 14+ 优化,按需创建

// 只有当运行时需要修改类信息时才创建(如 category 加载、class_addMethod 等)

struct class_rw_ext_t {

    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)

    class_ro_t_authed_ptr<const class_ro_t> ro;

    method_array_t methods;      // 方法列表(二维数组:多个 method_list_t)

    property_array_t properties;  // 属性列表

    protocol_array_t protocols;   // 协议列表

    char *demangledName;

    uint32_t version;

};

**iOS 14 class_rw_ext_t 优化的意义: **

  • 实测发现约 90% 的类在运行时不会被动态修改

  • 之前每个类都要创建完整的 class_rw_t(含方法/属性/协议数组),浪费内存

  • 优化后,class_rw_t 只存基本信息,ro_or_rw_ext 默认指向 class_ro_t

  • 只有需要动态修改时才创建 class_rw_ext_t

  • 全系统节省约 14MB 内存

1.3 方法缓存 cache_t 的底层实现


struct cache_t {

    // arm64:

    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  // buckets 指针 + mask

    // _bucketsAndMaybeMask 的高 48 位存储 buckets 指针

    // 低 16 位存储 mask(buckets 数量 - 1)

  


    union {

        struct {

            explicit_atomic<mask_t> _maybeMask;     // 真正的 mask

            uint16_t               _flags;

            uint16_t               _occupied;       // 已使用的 bucket 数量

        };

        explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 共享缓存中的预优化缓存

    };

};

  


struct bucket_t {

    // arm64:

    explicit_atomic<uintptr_t> _imp;  // 方法实现指针(PAC 签名加密的)

    explicit_atomic<SEL> _sel;        // 方法选择器

};

**缓存查找的汇编实现流程(arm64,CacheLookup 宏): **


1. 从 isa 中取出 class 指针

2. 从 class 偏移固定字节取出 cache_t

3. 从 cache_t 中取出 buckets 指针和 mask

4. 计算 index = selector & mask

5. 从 buckets[index] 取出 bucket

6. 比较 bucket._sel 是否等于目标 selector

7. 相等 → CacheHit:直接跳转到 bucket._imp(br x17)

8. 不相等 → 线性探测:index = (index - 1) & mask(注意是减一,往前找)

9. 如果 bucket._sel == 0 → CacheMiss:跳转到 __objc_msgSend_uncached

10. 如果绕了一圈回到起点 → CacheMiss

**为什么线性探测是 index - 1(往前找)而不是 + 1? **

  • arm64 下的 bucket_t 结构是 sel 在高位、imp 在低位

  • 从后往前遍历可以利用 ldp 指令同时加载两个连续的 64 位值,减少内存访问次数

  • 同时方便判断数组边界

**扩容策略细节: **

  • 初始容量:4(INIT_CACHE_SIZE)

  • _occupied 达到容量的 7/8(arm64)或 3/4(其他)时扩容

  • 新容量 = 旧容量 × 2(但有最大限制)

  • 扩容时完全丢弃旧缓存,不做 rehash

  • 为什么不 rehash?因为方法调用具有时间局部性,最近调用的方法更可能再被调用,旧缓存的价值不大

1.4 消息发送机制——从汇编到 C++

**objc_msgSend 的完整汇编实现(arm64 简化版): **


// 入口:x0 = receiver, x1 = selector

_objc_msgSend:

    // 1. 检查 receiver 是否为 nil 或 tagged pointer

    cmp p0, #0

    b.le LNilOrTagged

  


    // 2. 从 receiver 取出 isa

    ldr p13, [x0]           // p13 = isa

  


    // 3. 从 isa 中取出 class(ISA_MASK)

    and p16, p13, #ISA_MASK // p16 = class

  


    // 4. CacheLookup(内联宏,见上面的描述)

    CacheLookup NORMAL

  


LNilOrTagged:

    // tagged pointer 走特殊路径

    // nil receiver 直接返回 0

**lookUpImpOrForward(C++ 函数,缓存未命中时调用): **


IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {

    IMP imp = nil;

    Class curClass;

  


    // 1. 确保类已经 realized(class_rw_t 已创建)

    if (slowpath(!cls->isRealized())) {

        cls = realizeClassMaybeSwiftMaybeRelock(cls, runtimeLock);

    }

  


    // 2. 确保类已经 initialized

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {

        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);

    }

  


    runtimeLock.lock();

  


    // 3. 再次检查缓存(加锁期间可能被其他线程填充)

    imp = cache_getImp(cls, sel);

    if (imp) goto done;

  


    // 4. 在当前类的方法列表中查找

    {

        Method meth = getMethodNoSuper_nolock(cls, sel);

        if (meth) {

            imp = meth->imp(false);

            goto done;

        }

    }

  


    // 5. 沿 superclass 链查找

    for (curClass = cls->getSuperclass();

         curClass != nil;

         curClass = curClass->getSuperclass()) {

  


        // 先查父类缓存

        imp = cache_getImp(curClass, sel);

        if (imp) {

            if (imp == (IMP)_objc_msgForward_impcache) break;

            goto done;

        }

  


        // 再查父类方法列表

        Method meth = getMethodNoSuper_nolock(curClass, sel);

        if (meth) {

            imp = meth->imp(false);

            goto done;

        }

    }

  


    // 6. 未找到:进入动态方法解析

    if (slowpath(behavior & LOOKUP_RESOLVER)) {

        behavior ^= LOOKUP_RESOLVER;  // 确保只解析一次

        if (resolveMethod_locked(inst, sel, cls, behavior)) {

            goto retry;  // 解析成功,重走查找流程

        }

    }

  


    // 7. 进入消息转发

    imp = (IMP)_objc_msgForward_impcache;

  


done:

    // 8. 将找到的 IMP 缓存到当前类的 cache_t 中

    if (imp != (IMP)_objc_msgForward_impcache) {

        log_and_fill_cache(cls, imp, sel, inst, curClass);

    }

  


    runtimeLock.unlock();

    return imp;

}

**方法列表的查找算法细节: **


static method_t *search_method_list(const method_list_t *mlist, SEL sel) {

    if (mlist->isFixedUp()) {

        // 已排序:使用二分查找

        // 二分查找的 key 是 selector 的地址值(SEL 本质是 const char*)

        return findMethodInSortedMethodList(sel, mlist);

    } else {

        // 未排序:线性查找

        for (auto& meth : *mlist) {

            if (meth.name() == sel) return &meth;

        }

    }

    return nil;

}

**为什么先查缓存再查方法列表? **

  • 缓存查找在汇编中实现,仅需 35 条指令(约 12 个 CPU 周期)

  • 方法列表查找需要加锁、遍历(即使二分也是 O(logN))、可能还需遍历父类

  • 实测方法调用的****缓存命中率超过 90%** **,大多数调用直接在汇编中完成

1.5 动态方法解析的底层


static void resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {

    if (!cls->isMetaClass()) {

        // 实例方法解析

        resolveInstanceMethod(inst, sel, cls);

    } else {

        // 类方法解析

        resolveClassMethod(inst, sel, cls);

        // 如果类方法解析失败,尝试实例方法解析

        // 因为类方法存储在元类中,元类也是对象

        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {

            resolveInstanceMethod(inst, sel, cls);

        }

    }

}

  


static void resolveInstanceMethod(id inst, SEL sel, Class cls) {

    SEL resolve_sel = @selector(resolveInstanceMethod:);

  


    // 检查元类中是否实现了 resolveInstanceMethod:

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA())) {

        return// 没有实现,直接返回

    }

  


    // 调用 +resolveInstanceMethod:

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;

    bool resolved = msg(cls, resolve_sel, sel);

  


    // 再次查找(开发者可能在 resolveInstanceMethod: 中调用了 class_addMethod)

    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    // ...

}

1.6 消息转发的底层( __ ***forwarding* **__)

___forwarding___ 是 CoreFoundation 中的私有函数,伪代码还原:


int ___forwarding___(void *frameStackPointer, int isStret) {

    id receiver = *(id *)frameStackPointer;

    SEL sel = *(SEL *)(frameStackPointer + 8);

  


    Class cls = object_getClass(receiver);

  


    // 1. 快速转发

    if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {

        id forwardTarget = [receiver forwardingTargetForSelector:sel];

        if (forwardTarget && forwardTarget != receiver) {

            // 将 receiver 替换为 forwardTarget,重新走 objc_msgSend

            return objc_msgSend(forwardTarget, sel, ...);

        }

    }

  


    // 2. 完整转发

    if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {

        NSMethodSignature *sig = [receiver methodSignatureForSelector:sel];

        if (sig) {

            // 创建 NSInvocation

            NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];

            [inv setTarget:receiver];

            [inv setSelector:sel];

            // 设置参数...

  


            if (class_respondsToSelector(cls, @selector(forwardInvocation:))) {

                [receiver forwardInvocation:inv];

                // 取出返回值并返回

                return;

            }

        }

    }

  


    // 3. 都没处理 → 崩溃

    [receiver doesNotRecognizeSelector:sel];

    __builtin_unreachable();

}

**NSInvocation 底层原理: **

  • NSInvocation 封装了一次方法调用的所有信息:target、selector、参数、返回值

  • 底层维护一块参数缓冲区_frame),按照方法签名的格式存储参数

  • invoke 时使用 objc_msgSendobjc_msgSend_stret 发送消息

  • 参数的类型编码使用 @encode() 格式(如 @ 表示 id,: 表示 SEL,i 表示 int)

1.7 Method Swizzling 的底层实现


void method_exchangeImplementations(Method m1, Method m2) {

    mutex_locker_t lock(runtimeLock);

  


    // 交换两个 Method 结构体中的 imp 字段

    IMP imp1 = m1->imp(false);

    IMP imp2 = m2->imp(false);

    SEL sel1 = m1->name();

    SEL sel2 = m2->name();

  


    m1->setImp(imp2);

    m2->setImp(imp1);

  


    // 关键:清除相关类的方法缓存!

    // 因为缓存中可能还存着旧的 IMP

    flushCaches(nil, __func__, [sel1, sel2](Class c) {

        return c->cache.occupied() > 0;

    });

  


    // 更新自定义 RR/AWZ 标志

    adjustCustomFlagsForMethodChange(nil, m1);

    adjustCustomFlagsForMethodChange(nil, m2);

}

**关键细节:为什么要清除缓存? **

  • cache_t 中存储的是 SEL → IMP 映射

  • 交换 IMP 后,缓存中的 IMP 指向了错误的函数

  • 如果不清除缓存,已缓存的方法调用仍然走旧的 IMP

  • flushCaches(nil, ...) 传 nil 表示清除所有类的缓存(因为子类可能也缓存了)

1.8 关联对象的完整数据结构


// 全局关联对象管理器

class AssociationsManager {

    static AssociationsHashMap *_map;  // 全局唯一的哈希表

    static StripedLock _lock;          // 分离锁(不是单一锁,减少竞争)

};

  


// 一级哈希表:对象地址 → ObjectAssociationMap

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>

    AssociationsHashMap;

  


// 二级哈希表:key → ObjcAssociation

typedef DenseMap<const void *, ObjcAssociation>

    ObjectAssociationMap;

  


// 关联值

class ObjcAssociation {

    uintptr_t _policy;  // 关联策略(ASSIGN/RETAIN/COPY + NONATOMIC/ATOMIC)

    id _value;           // 关联的值

};

**关联对象的设置流程(objc_setAssociatedObject): **


1. 创建 AssociationsManager(获取锁)

2. 获取全局 AssociationsHashMap

3. 以对象地址的 DisguisedPtr 为 key,查找 ObjectAssociationMap

4. 如果不存在,创建新的 ObjectAssociationMap 并插入

5. 以传入的 key 为 key,创建 ObjcAssociation(policy + value)

6. 插入到 ObjectAssociationMap 中

7. 设置对象的 isa.has_assoc = 1(标记有关联对象)

8. 释放旧的关联值(如果有)

9. 释放锁

**关联对象的清除(对象释放时): **


dealloc → objc_destructInstance → _object_remove_assocations

→ 在 AssociationsHashMap 中以对象地址为 key 查找

→ 取出该对象的所有关联值

→ 释放锁

→ 逐个 release 关联值(在锁外释放,避免死锁)

1.9 Category 的底层加载过程

**编译后的 Category 结构体: **


struct category_t {

    const char *name;                      // 分类名

    classref_t cls;                        // 宿主类

    WrappedPtr<method_list_t> instanceMethods;   // 实例方法列表

    WrappedPtr<method_list_t> classMethods;      // 类方法列表

    struct protocol_list_t *protocols;     // 协议列表

    struct property_list_t *instanceProperties;  // 属性列表

    struct property_list_t *_classProperties;    // 类属性列表

  


    method_list_t *methodsForMeta(bool isMeta) {

        return isMeta ? classMethods : instanceMethods;

    }

};

**Runtime 加载 Category 的流程: **


_objc_init

  → map_images(dyld 通知 Runtime 镜像被映射到内存)

    → _read_images

      → 遍历所有 category_t

      → 如果宿主类已经 realized:

         → attachCategories(cls, cats, ...)

      → 如果宿主类尚未 realized:

         → 存入 unattachedCategories 表,等待类 realize 时再加载

  


attachCategories:

  1. 分配新的 method_list_t 数组(二维数组的形式)

  2. 将所有 category 的方法列表按逆序添加(后编译的在前面)

  3. 将新方法列表**插入到 class_rw_ext_t.methods 的最前面**

  4. 触发 class_rw_ext_t 的创建(如果还没有)

  5. 对属性列表和协议列表做同样操作

**为什么分类方法"覆盖"宿主类方法? **

  • 实际上方法没有被覆盖,宿主类的方法仍然存在

  • 只是分类的方法列表被插入到了 methods 数组的前面

  • lookUpImpOrForward 查找方法时是从前往后遍历方法列表数组

  • 所以先找到分类的方法,就不会继续找宿主类的了

  • 可以通过遍历方法列表找到原始方法并调用

1.10 +load 与 +initialize 的底层实现差异

** +load 的调用链: **


_objc_init

  → load_images(每次加载新的 image 时调用)

    → prepare_load_methods

      → 将所有类的 +load 方法添加到 loadable_classes 数组

         (按继承层级排序,父类在前)

      → 将所有分类的 +load 方法添加到 loadable_categories 数组

    → call_load_methods

      → 先调用所有类的 +loaddo while 循环)

        → (*load_method)(cls, @selector(load))  // 直接函数指针调用!

      → 再调用所有分类的 +load

        → (*load_method)(cat, @selector(load))  // 直接函数指针调用!

** +initialize 的调用链: **


objc_msgSendlookUpImpOrForward

  → if (!cls->isInitialized())

    → initializeAndLeaveLocked

      → initializeAndMaybeRelock

        → initializeNonMetaClass

          → 递归初始化父类(如果父类未初始化)

          → callInitialize(cls)

            → ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize))

            // 走 objc_msgSend!所以会被分类覆盖

**关键区别的本质原因: **

  • +loadIMP 直接调用(*load_method)(cls, SEL)),所以不走消息发送,分类不会覆盖

  • +initializeobjc_msgSend 调用,走完整的消息发送流程,分类会覆盖

二、内存管理

2.1 SideTable 的完整结构


struct SideTable {

    spinlock_t slock;            // 自旋锁(实际是 os_unfair_lock)

    RefcountMap refcnts;         // 引用计数表(DenseMap<DisguisedPtr, size_t>)

    weak_table_t weak_table;     // 弱引用表

};

  


// 全局 SideTable 数组

static StripedMap<SideTable> SideTables;

// StripedMap 内部是一个固定大小的数组,64 个元素

// 通过对象地址哈希取模确定使用哪个 SideTable

// 哈希函数:((addr >> 4) ^ (addr >> 9)) % StripeCount

  


struct weak_table_t {

    weak_entry_t *weak_entries;   // hash 数组

    size_t num_entries;           // 已使用数量

    uintptr_t mask;               // 容量 - 1

    uintptr_t max_hash_displacement;  // 最大哈希冲突偏移量

};

  


struct weak_entry_t {

    DisguisedPtr<objc_object> referent;  // 被弱引用的对象地址

  


    // 存储弱引用指针(指向 weak 变量的指针的指针)

    union {

        struct {

            weak_referrer_t *referrers;    // 动态数组(超过4个时)

            uintptr_t out_of_line_ness : 2;

            uintptr_t num_refs : PTR_MINUS_2;

            uintptr_t mask;

            uintptr_t max_hash_displacement;

        };

        struct {

            // 内联数组(4个以内时,避免额外分配内存)

            weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // WEAK_INLINE_COUNT = 4

        };

    };

};

**weak 指针置 nil 的完整流程(dealloc → clearDeallocating): **


void objc_object::clearDeallocating_slow() {

    SideTable& table = SideTables()[this];

    table.lock();

  


    // 1. 清除弱引用

    if (isa.weakly_referenced) {

        // 在 weak_table 中以当前对象地址为 key 查找 weak_entry_t

        weak_entry_t *entry = weak_entry_for_referent(&table.weak_table, this);

        if (entry) {

            // 遍历所有弱引用指针,置为 nil

            weak_referrer_t *referrers = entry->referrers;

            for (size_t i = 0; i < TABLE_SIZE(entry); i++) {

                objc_object **referrer = referrers[i];

                if (referrer) {

                    if (*referrer == this) {

                        *referrer = nil;  // 将 weak 变量置 nil

                    }

                }

            }

            // 从 weak_table 中移除该 entry

            weak_entry_remove(&table.weak_table, entry);

        }

    }

  


    // 2. 清除引用计数

    if (isa.has_sidetable_rc) {

        table.refcnts.erase(this);

    }

  


    table.unlock();

}

2.2 retain/release 的底层实现

**objc_retain: **


id objc_retain(id obj) {

    if (obj->isTaggedPointerOrNil()) return obj;  // TaggedPointer 直接返回

    return obj->retain();

}

  


objc_object::retain() {

    // 快速路径:Non-pointer isa

    if (fastpath(!isTaggedPointer() && isa.nonpointer)) {

        // 原子操作:extra_rc++

        uintptr_t newisa = __sync_fetch_and_add(&isa.bits, RC_ONE);

        // 检查是否溢出

        if (slowpath(newisa.extra_rc == 0)) {  // 溢出了

            return rootRetain_overflow(false);

        }

        return (id)this;

    }

    // 慢速路径:操作 SideTable

    return rootRetain(false, RRVariant::Fast);

}

  


// rootRetain 溢出处理:

// 将 extra_rc 的一半(RC_HALF = 524288/2)搬到 SideTable

// extra_rc 设为另一半

// 设置 has_sidetable_rc = 1

**objc_release: **


void objc_release(id obj) {

    if (obj->isTaggedPointerOrNil()) return// TaggedPointer 直接返回

    obj->release();

}

  


objc_object::release() {

    if (fastpath(isa.nonpointer)) {

        // 原子操作:extra_rc--

        uintptr_t newisa = __sync_sub_and_fetch(&isa.bits, RC_ONE);

        if (slowpath(newisa.extra_rc == -1ULL)) {  // 下溢了

            return rootRelease_underflow(false);

        }

        return;

    }

    return rootRelease(false, RRVariant::Fast);

}

  


// rootRelease 下溢处理:

// 尝试从 SideTable 借 RC_HALF 到 extra_rc

// 如果 SideTable 中也为 0:

//   → 设置 deallocating 标志

//   → 调用 ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc))

2.3 AutoreleasePool 的底层实现


class AutoreleasePoolPage : private AutoreleasePoolPageData {

    // 每页大小 = 4096 字节(一个虚拟内存页)

    static size_t const SIZE = PAGE_MIN_SIZE;  // 4096

  


    // 页头结构(AutoreleasePoolPageData):

    magic_t const magic;               // 校验值

    __unsafe_unretained id *next;      // 指向下一个可存放对象的位置

    pthread_t const thread;             // 所属线程

    AutoreleasePoolPage * const parent; // 上一页(双向链表)

    AutoreleasePoolPage *child;         // 下一页(双向链表)

    uint32_t const depth;               // 链表深度

    uint32_t hiwat;                     // 最大入栈数量(high water mark)

  


    // 页头之后的剩余空间用于存放 autorelease 对象指针

    // 第一页的可用空间 = 4096 - sizeof(AutoreleasePoolPageData) ≈ 4096 - 56 = 4040

    // 可存放 4040 / 8 = 505 个对象指针

};

  


// POOL_BOUNDARY 就是 nil(0x0)

#define POOL_BOUNDARY nil

  


// push 操作

static inline void *push() {

    // 获取当前线程的 hotPage(最新的 page)

    AutoreleasePoolPage *page = hotPage();

  


    if (page && !page->full()) {

        // 插入 POOL_BOUNDARY 标记

        return page->add(POOL_BOUNDARY);

    }

    return autoreleaseFullPage(POOL_BOUNDARY, page);

}

  


// autorelease 操作

static inline id autorelease(id obj) {

    AutoreleasePoolPage *page = hotPage();

    if (page && !page->full()) {

        return page->add(obj);  // 将对象指针存入 next 位置,next++

    }

    return autoreleaseFullPage(obj, page);

}

  


// pop 操作

static inline void pop(void *token) // token = push 时返回的 POOL_BOUNDARY 位置

    AutoreleasePoolPage *page = pageForPointer(token);

  


    // 从 next-1 开始,逐个取出对象指针并调用 release

    // 直到遇到 token(POOL_BOUNDARY)

    page->releaseUntil(token);

  


    // 释放空的子页(保留一个空子页作为缓存)

    if (page->child) {

        if (page->child->child) {

            page->child->child->kill();  // 释放孙子页及其后续

        }

    }

}

**autorelease 与 RunLoop 的关系(底层源码): **


// main RunLoop 注册了两个 Observer:

  


// Observer 1: kCFRunLoopEntry(优先级最高 = 0x7FFFFFFF)

//   回调:_objc_autoreleasePoolPush()

  


// Observer 2: kCFRunLoopBeforeWaiting | kCFRunLoopExit(优先级最低 = -0x7FFFFFFF)

//   BeforeWaiting 回调:

//     _objc_autoreleasePoolPop(oldPool)    // 释放旧池中的对象

//     _objc_autoreleasePoolPush()          // 创建新池

//   Exit 回调:

//     _objc_autoreleasePoolPop(pool)       // 释放池中所有对象

**这意味着每次 RunLoop 循环(处理事件 → 休眠 → 唤醒),autorelease 对象都会被释放。 **

2.4 TaggedPointer 的完整实现


// arm64 下判断 TaggedPointer

static inline bool isTaggedPointer(const void *ptr) {

    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;

    // _OBJC_TAG_MASK = 1ULL << 63  (最高位为 1)

}

  


// TaggedPointer 的位布局(arm64):

// [63]    : 1 = TaggedPointer 标志

// [62-60] : tag index(0~6,表示类型:NSNumber=0, NSDate=1, NSString=5 等)

//           tag=7 时为扩展类型,使用 [59-52] 作为扩展 tag

// [59-4]  : payload(实际数据)

// [3-0]   : payload 继续或 tagged pointer 的特殊信息

  


// 安全措施:TaggedPointer 的值经过了混淆(XOR 一个随机值)

// objc_debug_taggedpointer_obfuscator 在启动时随机生成

// 编码:encoded = ptr ^ objc_debug_taggedpointer_obfuscator

// 解码:decoded = encoded ^ objc_debug_taggedpointer_obfuscator

**为什么要混淆? ** 防止攻击者构造特定的 TaggedPointer 值来欺骗 Runtime。

**TaggedPointer 优化效果: **

  • NSNumber 存储小整数、float:无需 malloc/free,无引用计数操作

  • NSString 存储 7 字节以内的 ASCII 字符串(NSTaggedPointerString)

  • NSDate 存储时间戳

  • 性能提升 3-100 倍(取决于操作类型)

三、Block

3.1 Block 的完整底层结构


// Block 编译后的结构

struct __block_impl {

    void *isa;       // 指向 _NSConcreteStackBlock / _NSConcreteGlobalBlock / _NSConcreteMallocBlock

    int Flags;       // 标志位:

                     //   bit 0:  BLOCK_DEALLOCATING

                     //   bit 16: BLOCK_REFCOUNT_MASK(引用计数掩码)

                     //   bit 24: BLOCK_NEEDS_FREE(堆 Block)

                     //   bit 25: BLOCK_HAS_COPY_DISPOSE(有 copy/dispose 辅助函数)

                     //   bit 26: BLOCK_HAS_CTOR(有 C++ 构造函数)

                     //   bit 27: BLOCK_IS_GC(GC 相关,iOS 不用)

                     //   bit 28: BLOCK_IS_GLOBAL(全局 Block)

                     //   bit 30: BLOCK_HAS_SIGNATURE(有方法签名)

    int Reserved;

    void (*FuncPtr)(void *, ...);  // 函数指针,指向 Block 的实现代码

};

  


// Block 描述信息

struct __block_desc_0 {

    unsigned long reserved;

    unsigned long Block_size;           // Block 结构体大小

    // 以下两个函数只在 BLOCK_HAS_COPY_DISPOSE 时存在:

    void (*copy)(void *dst, void *src);  // 从栈拷贝到堆时调用

    void (*dispose)(void *);             // 堆 Block 释放时调用

    // 以下在 BLOCK_HAS_SIGNATURE 时存在:

    const char *signature;               // Block 的类型签名(参数/返回值编码)

};

**Block 捕获 OC 对象时的 copy 函数实现: **


static void __main_block_copy_0(struct __main_block_impl_0 *dst,

                                 struct __main_block_impl_0 *src) {

    // _Block_object_assign 相当于 retain(根据对象的所有权修饰符决定)

    // BLOCK_FIELD_IS_OBJECT = 3:对象类型

    // BLOCK_FIELD_IS_BLOCK = 7:Block 类型

    // BLOCK_FIELD_IS_BYREF = 8:__block 变量

    _Block_object_assign(&dst->obj, src->obj, BLOCK_FIELD_IS_OBJECT);

}

  


static void __main_block_dispose_0(struct __main_block_impl_0 *src) {

    // _Block_object_dispose 相当于 release

    _Block_object_dispose(src->obj, BLOCK_FIELD_IS_OBJECT);

}

3.2 __block 变量的底层细节****


// __block int a = 10; 编译后生成的结构体

struct __Block_byref_a_0 {

    void *__isa;           // nil

    __Block_byref_a_0 *__forwarding;  // 核心!

    int __flags;

    int __size;

    int a;                 // 原始变量的值

};

  


// 如果 __block 修饰的是 OC 对象

struct __Block_byref_obj_0 {

    void *__isa;

    __Block_byref_obj_0 *__forwarding;

    int __flags;

    int __size;

    // 以下两个函数用于管理捕获的对象的内存

    void (*__Block_byref_id_object_copy)(void*, void*);

    void (*__Block_byref_id_object_dispose)(void*);

    id obj;

};

****** __forwarding 指针的状态变化: **


栈上的 Block 未 copy 时:

  栈上 byref.__forwarding → 栈上 byref(指向自己)

  


Block copy 到堆上后:

  栈上 byref.__forwarding → 堆上 byref(指向堆上的副本)

  堆上 byref.__forwarding → 堆上 byref(指向自己)

  


访问 __block 变量时:

  a->__forwarding->a

  无论是从栈上还是堆上访问,都通过 __forwarding 访问到堆上的同一份数据

****** __block 变量与对象内存管理: **

  • 当 __block 变量被 Block copy 到堆上时:****

****- __Block_byref_id_object_copy 被调用

  - 对捕获的对象执行 _Block_object_assign

  - 根据变量的所有权修饰符(__strong/__weak)决定 retain 还是创建 weak 引用

  • 当堆上 __block 变量释放时:****

****- __Block_byref_id_object_dispose 被调用

  - 对捕获的对象执行 _Block_object_dispose(release / 移除 weak 引用)

3.3 Block 的 copy 流程


// _Block_copy 的简化实现

void *_Block_copy(const void *arg) {

    struct Block_layout *aBlock = (struct Block_layout *)arg;

  


    if (!aBlock) return nil;

  


    // 如果已经在堆上,引用计数 +1 后返回

    if (aBlock->flags & BLOCK_NEEDS_FREE) {

        latching_incr_int(&aBlock->flags);

        return aBlock;

    }

  


    // 如果是全局 Block,直接返回(不需要 copy)

    if (aBlock->flags & BLOCK_IS_GLOBAL) {

        return aBlock;

    }

  


    // 栈 Block → 堆 Block

    struct Block_layout *result =

        (struct Block_layout *)malloc(aBlock->descriptor->size);

    if (!result) return nil;

  


    // 内存拷贝

    memmove(result, aBlock, aBlock->descriptor->size);

  


    // 修改 flags:设置 BLOCK_NEEDS_FREE,引用计数设为 1

    result->flags &= ~(BLOCK_REFCOUNT_MASK | BLOCK_DEALLOCATING);

    result->flags |= BLOCK_NEEDS_FREE | 2// 2 = 初始引用计数

  


    // 修改 isa 为 _NSConcreteMallocBlock

    _Block_call_copy_helper(result, aBlock);

    result->isa = _NSConcreteMallocBlock;

  


    return result;

}

四、RunLoop

4.1 RunLoop 的底层源码实现(CFRunLoopRun 简化版)


// CFRunLoopRun 的核心逻辑(简化版)

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName,

                            CFTimeInterval seconds, Boolean returnAfterSourceHandled) {

    // 1. 根据 modeName 找到对应的 CFRunLoopModeRef

    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    if (!currentMode || __CFRunLoopModeIsEmpty(rl, currentMode)) {

        return kCFRunLoopRunFinished;  // Mode 中没有 Source/Timer/Observer

    }

  


    // 2. 通知 Observer:即将进入 RunLoop

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

  


    // 3. 进入核心循环

    SInt32 result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled);

  


    // 4. 通知 Observer:即将退出 RunLoop

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

  


    return result;

}

  


static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm,

                               CFTimeInterval seconds, Boolean stopAfterHandle) {

    // 获取基于 mach_port 的唤醒端口

    mach_port_name_t dispatchPort = _dispatch_get_main_queue_port_4CF();

  


    // 使用 GCD timer 或 mk_timer 设置超时

    dispatch_source_t timeout_timer = NULL;

    // ...

  


    Boolean didDispatchPortLastTime = true;

    int32_t retVal = 0;

  


    do {

        // 2. 通知 Observer:即将处理 Timer

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

  


        // 3. 通知 Observer:即将处理 Source0

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

  


        // 4. 处理 Blocks(dispatch_async 到主线程的 block)

        __CFRunLoopDoBlocks(rl, rlm);

  


        // 5. 处理 Source0(非端口事件)

        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        if (sourceHandledThisLoop) {

            __CFRunLoopDoBlocks(rl, rlm);

        }

  


        // 6. 如果有 Source1 就绪,跳到步骤 9 处理

        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {

            goto handle_msg;

        }

  


        // 7. 通知 Observer:即将休眠

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        __CFRunLoopSetSleeping(rl);

  


        // 8. 调用 mach_msg 等待唤醒(用户态 → 内核态)

        // 这里会阻塞线程,直到:

        //   - 收到 mach_port 消息(Source1 / Timer / dispatch_main_queue)

        //   - 超时

        //   - 被外部唤醒(CFRunLoopWakeUp)

        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer),

                                   &livePort, poll ? 0 : TIMEOUT_INFINITY,

                                   &voucherState, &voucherCopy, rl, rlm);

  


        __CFRunLoopUnsetSleeping(rl);

  


        // 9. 通知 Observer:已经唤醒

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

  


handle_msg:

        // 10. 根据唤醒来源处理事件

        if (livePort == dispatchPort) {

            // GCD 事件(dispatch_async 到主线程的)

            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

  


        } else if (livePort == rl->_timerPort || (livePort == modeQueuePort)) {

            // Timer 事件

            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());

  


        } else {

            // Source1 事件

            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);

            if (rls) {

                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);

            }

        }

  


        // 处理 Blocks

        __CFRunLoopDoBlocks(rl, rlm);

  


        // 判断是否退出

        if (sourceHandledThisLoop && stopAfterHandle) {

            retVal = kCFRunLoopRunHandledSource;

        } else if (timeout) {

            retVal = kCFRunLoopRunTimedOut;

        } else if (__CFRunLoopIsStopped(rl)) {

            retVal = kCFRunLoopRunStopped;

        } else if (__CFRunLoopModeIsEmpty(rl, rlm)) {

            retVal = kCFRunLoopRunFinished;

        }

  


    } while (retVal == 0);

  


    return retVal;

}

**mach_msg 系统调用——RunLoop 休眠的本质: **


// mach_msg 是 Mach 内核的消息收发函数

// 发送消息:将消息放入端口的消息队列

// 接收消息:从端口的消息队列取出消息,如果队列为空则阻塞

  


mach_msg_return_t mach_msg(

    mach_msg_header_t *msg,       // 消息头

    mach_msg_option_t option,     // MACH_SEND_MSG / MACH_RCV_MSG

    mach_msg_size_t send_size,    // 发送大小

    mach_msg_size_t rcv_size,     // 接收缓冲区大小

    mach_port_name_t rcv_name,    // 接收端口

    mach_msg_timeout_t timeout,   // 超时时间

    mach_port_name_t notify       // 通知端口

);

  


// RunLoop 休眠时调用:

mach_msg(msg, MACH_RCV_MSG | MACH_RCV_LARGE, 0, size, port, 0, MACH_PORT_NULL);

// MACH_RCV_MSG:接收模式

// 没有消息时线程被内核挂起(不消耗 CPU)

// 有消息时内核唤醒线程,mach_msg 返回

**CFRunLoopWakeUp 的实现(唤醒休眠的 RunLoop): **


void CFRunLoopWakeUp(CFRunLoopRef rl) {

    // 向 RunLoop 的 _wakeUpPort 发送一个 mach 消息

    kern_return_t ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);

    // 这会唤醒正在 mach_msg 中等待的线程

}

4.2 NSTimer 的底层

  • NSTimer 本质是 CFRunLoopTimerRef

  • 内部维护一个 _fireTSR(下次触发的绝对时间,mach_absolute_time 格式)

  • RunLoop 每次循环时检查 Timer 是否到期(__CFRunLoopDoTimers

  • 不精确的原因

  1. RunLoop 可能在处理其他事件(Source/Observer),错过了精确触发时间

  2. 到期时间的计算基于上次循环的时间

  3. 如果 RunLoop 处于非 Timer 注册的 Mode 中,Timer 不会触发

  • **Tolerance(容差) **:iOS 7+ 引入,允许系统合并多个 Timer 的触发时机,减少 CPU 唤醒次数,节省电量

4.3 GCD 与 RunLoop 的关系

  • dispatch_async(dispatch_get_main_queue(), block) 的 block 最终在 RunLoop 中执行

  • 当有 block 被提交到主队列时,libdispatch 会向主线程的 RunLoop 发送一个 mach_msg

  • RunLoop 被唤醒后,在 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 中执行 block

  • 这就是为什么 dispatch_async 到主线程的 block 不会立即执行,而是等到下一次 RunLoop 循环

五、多线程

5.1 GCD 的底层实现

**dispatch_queue 的底层结构: **


struct dispatch_queue_s {

    DISPATCH_QUEUE_HEADER;

    // 关键字段:

    // dq_serialnum:队列序列号(主队列=1,全局队列=2~5)

    // dq_width:并发宽度(串行=1,并发=DISPATCH_QUEUE_WIDTH_MAX=0xfff)

    // dq_items_head/tail:任务链表头尾

    // dq_state:队列状态(原子变量)

    // do_targetq:目标队列(层级关系)

};

**dispatch_async 的底层流程: **


dispatch_async(queue, block)

  → _dispatch_continuation_init:将 block 包装成 dispatch_continuation_t(轻量级对象)

    → 设置 voucher(优先级传递信息)

    → 设置 dc_func = block 的函数指针

    → 设置 dc_ctxt = block 的上下文

  → _dispatch_continuation_async

    → dx_push(queue, continuation)  // 将任务推入队列

      → 串行队列:_dispatch_lane_push

        → 如果队列为空且未执行中:直接 drain(执行)

        → 否则:追加到任务链表尾部

      → 并发队列:_dispatch_lane_concurrent_push

        → 追加到任务链表尾部

        → 唤醒线程池中的工作线程

  


// 工作线程的执行循环:

_dispatch_worker_thread3

  → _dispatch_root_queue_drain

    → _dispatch_queue_override_invoke

      → _dispatch_client_callout

        → block 的函数指针(block 的上下文)  // 最终执行 block

**dispatch_sync 的死锁检测: **


void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block) {

    // 检查是否会死锁(当前线程是否正在执行该串行队列的任务)

    if (unlikely(_dispatch_queue_try_acquire_barrier_sync(queue, tid))) {

        // 不会死锁,直接在当前线程执行 block

        _dispatch_sync_invoke_and_complete(queue, block);

    } else {

        // 会死锁!

        // 但实际上 GCD 的死锁检测不够完善,只检测了直接死锁

        // dispatch_sync 到自己所在的串行队列 → 直接 crash:

        DISPATCH_CLIENT_CRASH(0, "dispatch_sync called on queue already owned by current thread");

    }

}

**线程池管理: **

  • GCD 维护一个全局线程池

  • 线程数量由 libdispatch 动态管理,上限通常为 64 或 512(取决于系统)

  • 当所有线程都忙时,新提交的任务排队等待

  • 空闲线程超过一定时间后被回收

  • 线程爆炸问题:大量 dispatch_async 可能创建过多线程,导致性能下降(上下文切换开销)

5.2 dispatch_once 的底层实现


void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block) {

    // predicate 的值:

    //   0 = 未执行

    //   ~0 = 已完成

    //   其他值 = 正在执行(存储等待线程链表信息)

  


    if (likely(os_atomic_load(predicate, acquire) == DONCE_DONE)) {

        return// 快速路径:已经执行过

    }

  


    return dispatch_once_f(predicate, block, _dispatch_Block_invoke(block));

}

  


void dispatch_once_f(dispatch_once_t *predicate, void *ctxt,

                      dispatch_function_t func) {

    // 尝试原子比较交换:0 → locked value

    if (os_atomic_cmpxchg(predicate, 0, locked_value, relaxed)) {

        // 成功获取"锁",执行 block

        func(ctxt);

  


        // 标记为完成

        os_atomic_store(predicate, DONCE_DONE, release);

  


        // 唤醒所有等待的线程

        _dispatch_once_wake(predicate);

    } else {

        // 已经有线程在执行,当前线程等待

        _dispatch_once_wait(predicate);

        // 等待完成后返回(block 已被另一个线程执行)

    }

}

**为什么 dispatch_once 是线程安全的? **

  • 使用****原子操作(CAS)** **保证只有一个线程能获取执行权

  • 其他线程通过自旋 + 信号量等待

  • os_atomic_store 使用 release 语义,确保 block 的副作用对其他线程可见

  • os_atomic_load 使用 acquire 语义,确保能看到 block 的所有副作用

5.3 dispatch_semaphore 的底层实现


struct dispatch_semaphore_s {

    DISPATCH_SEMAPHORE_HEADER;

    long volatile dsema_value;    // 当前信号量值

    long dsema_orig;              // 初始信号量值

    _dispatch_sema4_t dsema_sema; // 底层信号量(内核信号量或 UL 信号量)

};

  


long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) {

    // 原子减 1

    long value = os_atomic_dec2o(dsema, dsema_value, acquire);

    if (likely(value >= 0)) {

        return 0// 信号量充足,直接返回

    }

    // 信号量不足,进入慢速路径

    return _dispatch_semaphore_wait_slow(dsema, timeout);

    // 内部调用 semaphore_wait / WaitForSingleObject

    // 线程被内核挂起

}

  


long dispatch_semaphore_signal(dispatch_semaphore_t dsema) {

    // 原子加 1

    long value = os_atomic_inc2o(dsema, dsema_value, release);

    if (likely(value > 0)) {

        return 0// 没有等待的线程

    }

    // 有线程在等待,唤醒一个

    return _dispatch_semaphore_signal_slow(dsema);

    // 内部调用 semaphore_signal / ReleaseSemaphore

}

5.4 @synchronized 的底层实现


// @synchronized(obj) { ... } 编译后:

objc_sync_enter(obj);

// ... 代码 ...

objc_sync_exit(obj);

  


// objc_sync_enter 实现:

int objc_sync_enter(id obj) {

    if (obj) {

        SyncData* data = id2data(obj, ACQUIRE);

        // data 中包含一个 recursive_mutex_t

        data->mutex.lock();

    }

    // obj == nil 时什么都不做(不会加锁!这是一个常见陷阱)

    return 0;

}

  


// id2data 函数:

// 1. 先查 Thread Local Storage(TLS)中的缓存

//    每个线程缓存最近使用的 SyncData(快速链表 SyncList)

// 2. TLS 未命中,查全局哈希表 sDataLists

//    key = obj 地址的哈希值

//    value = SyncData 链表

// 3. 在链表中找到或创建 SyncData

//    SyncData 包含:object 指针 + threadCount + recursive_mutex_t

// 4. 缓存到 TLS 中

** @synchronized 性能差的原因: **

  1. 每次加锁都要做哈希查找(全局表 + TLS)

  2. recursive_mutex_t 本身比 os_unfair_lock

  3. obj == nil 时不加锁,存在安全隐患

  4. 使用全局哈希表,存在锁竞争

六、KVO 底层——更深入

6.1 动态子类的完整创建过程


addObserver:forKeyPath:

  → _NSKeyValueRetainedObservationInfoForObject

    → _NSKeyValueObservationInfoCreateByAdding

  → _NSSetObjectValueAndNotify 的生成过程:

  


1. 获取对象的 class

2. 检查是否已有 NSKVONotifying_ 子类

3. 没有则创建:

   a. objc_allocateClassPair(originalClass, "NSKVONotifying_ClassName", 0)

   b. 添加方法:

      - setter 方法(如 -setAge:)→ IMP = _NSSetIntValueAndNotify / _NSSetObjectValueAndNotify 等

        (根据属性类型选择不同的 setter IMP,不同类型有不同的实现)

      - -class → 返回原类(伪装)

      - -dealloc → 在释放时恢复 isa

      - -_isKVOA → 返回 YES

      - -description → 返回原类信息

   c. objc_registerClassPair(kvoClass)

4. object_setClass(obj, kvoClass)  // 修改 isa

****** _**NSSetXxxValueAndNotify 的内部实现(根据属性类型不同): ****


_NSSetIntValueAndNotify:

  → [self willChangeValueForKey:key]

    → _NSKeyValueWillChange

      → 记录旧值(如果 options 包含 NSKeyValueObservingOptionOld)

      → 入栈 changeDictionary

  → 调用原始 setter([super setAge:newAge])

  → [self didChangeValueForKey:key]

    → _NSKeyValueDidChange

      → 从栈中取出 changeDictionary

      → 填充新值(如果 options 包含 NSKeyValueObservingOptionNew)

      → 遍历所有观察者,调用:

        [observer observeValueForKeyPath:keyPath

                                ofObject:self

                                  change:changeDictionary

                                 context:context]

**集合类型的 KVO(NSMutableArray 等): **

  • 直接 [self.array addObject:] 不会触发 KVO

  • 需要通过 [[self mutableArrayValueForKey:@"array"] addObject:]

  • mutableArrayValueForKey: 返回一个代理对象(NSKeyValueMutableArray)

  • 代理对象在增删改时自动调用 willChange:valuesAtIndexes:forKey:didChange:valuesAtIndexes:forKey:

七、事件响应链底层

7.1 触摸事件的完整传递路径


用户触摸屏幕

  → 硬件产生触摸信号

  → IOKit.framework 将触摸事件封装为 IOHIDEvent

  → 通过 mach port 发送给 SpringBoard 进程

  → SpringBoard 判断前台 App,通过 mach port 发送给 App 进程

  → App 主线程 RunLoop 的 Source1 回调被触发

    → __IOHIDEventSystemClientQueueCallback

      → _UIApplicationHandleEventQueue

        → 将 IOHIDEvent 包装为 UIEvent(包含 UITouch)

        → [UIApplication sendEvent:event]

          → [UIWindow sendEvent:event]

            → [UIWindow hitTest:withEvent:]  // 开始 Hit-Testing

**hitTest:withEvent: 的完整实现: **


- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    // 1. 检查是否能接收事件

    if (!self.userInteractionEnabled || self.hidden || self.alpha < 0.01) {

        return nil;

    }

  


    // 2. 检查触摸点是否在自身范围内

    if (![self pointInside:point withEvent:event]) {

        return nil;

    }

  


    // 3. 从后往前遍历子视图(最上面的子视图优先)

    for (NSInteger i = self.subviews.count - 1; i >= 0; i--) {

        UIView *subview = self.subviews[i];

        // 坐标转换到子视图坐标系

        CGPoint convertedPoint = [subview convertPoint:point fromView:self];

        // 递归调用子视图的 hitTest

        UIView *hitView = [subview hitTest:convertedPoint withEvent:event];

        if (hitView) {

            return hitView;  // 找到了最合适的视图

        }

    }

  


    // 4. 没有子视图能处理,返回自己

    return self;

}

  


- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {

    return CGRectContainsPoint(self.bounds, point);

}

**手势识别器与 UIResponder 的竞争关系(底层): **


UIWindow 收到 UIEvent 后:

  1. 将事件同时发送给 hit-tested view 和相关的 UIGestureRecognizer

  2. UIGestureRecognizer 进入 .began 状态,开始识别

  3. 同时 UIView 收到 touchesBegan:

  4. 如果手势识别器识别成功(.recognized / .changed):

     → 调用手势的 target-action

     → 同时调用 hit-tested view 的 touchesCancelled:(如果 cancelsTouchesInView = YES)

  5. 如果手势识别器识别失败(.failed):

     → UIView 正常接收后续 touch 事件

  


UIGestureRecognizer 状态机:

  Possible → Recognized(离散手势)

  Possible → Began → Changed → Ended(连续手势)

  Possible → Failed

  任意状态 → Cancelled

八、UI 渲染底层

8.1 CALayer 的渲染原理


UIViewCALayer 的关系:

  - UIViewCALayerdelegatelayer.delegate = view)

  - UIView 负责事件处理和响应链

  - CALayer 负责内容的存储和渲染

  - 每个 CALayer 对应一个 backing store(位图,存储在 GPU 纹理缓存中)

  


一帧的完整渲染流程:

  


1. CPU 阶段(App 进程):

   a. Handle Events → 触发 UI 更新

   b. Commit Transaction(在 RunLoopBeforeWaiting 回调中):

      - Layout 阶段:

        → [UIView layoutSubviews]

        → [CALayer layoutSublayers]

        → Auto Layout 约束求解

      - Display 阶段:

        → [CALayer display]

          → 如果实现了 -displayLayer:,交给代理绘制

          → 否则 [CALayer drawInContext:]

            → [UIView drawRect:](如果重写了)

            → 创建 CGContextbacking store)

            → Core Graphics 绘制

      - Prepare 阶段:

        → 图片解码(JPEG/PNG → 位图)

        → 图片格式转换(如果 GPU 不支持的格式)

      - Commit 阶段:

        → 将图层树(layer tree)编码打包

        → 通过 IPCmach_msg)发送到 Render Server

  


2. Render Server 进程(backboardd):

   a. 解码图层树

   b. 构建渲染树(render tree)

   c. 调用 GPU:

      - 生成 GPU 命令缓冲区

      - 提交到 GPU 命令队列

  


3. GPU 阶段:

   a. 顶点着色器(Vertex Shader)

   b. 图元装配(Primitive Assembly)

   c. 光栅化(Rasterization)→ 将几何图形转换为像素

   d. 片段着色器(Fragment Shader)→ 计算每个像素的颜色

   e. 写入帧缓冲区(Frame Buffer4. 显示器:

   a. VSync 信号到来(每秒 60 次 = 16.67ms 一帧)

   b. 视频控制器从帧缓冲区读取数据

   c. 逐行显示

8.2 离屏渲染底层原理

**正常渲染(On-Screen Rendering): **


GPU 直接将渲染结果写入帧缓冲区(Frame Buffer)

一次遍历即可完成

**离屏渲染(Off-Screen Rendering): **


GPU 需要额外的离屏缓冲区(Offscreen Buffer)来临时存储中间结果

  


以圆角 + masksToBounds 为例:

1. GPU 先渲染 layer 的内容到离屏缓冲区

2. GPU 再渲染 layer 的所有子 layer 到离屏缓冲区

3. 等所有子 layer 都渲染完毕后

4. 应用圆角裁剪(mask)

5. 将裁剪后的结果从离屏缓冲区 → 帧缓冲区

  


问题在于:

- 正常渲染是"画家算法",从后往前逐层绘制到帧缓冲区,不需要保存中间结果

- 但 mask 操作需要等所有内容都渲染完成后才能应用

- 这就需要额外的缓冲区来保存中间结果

- 缓冲区的创建/销毁 + 上下文切换(from on-screen to off-screen and back)开销很大

**各种情况的离屏渲染分析: **

| 操作 | 是否离屏渲染 | 原因 |

|------|-------------|------|

| cornerRadius(单独使用) | 否 | 只影响背景和边框,不需要裁剪子内容 |

| cornerRadius + masksToBounds | 是 | 需要裁剪所有子内容 |

| shadow(无 shadowPath) | 是 | 阴影需要根据内容轮廓计算,需要等内容渲染完成 |

| shadow(有 shadowPath) | 否 | 直接根据 path 绘制阴影,不需要等内容 |

| mask | 是 | 需要等内容渲染完成后应用遮罩 |

| shouldRasterize | 是(首次)| 首次渲染时离屏渲染并缓存,后续直接使用缓存 |

| opacity < 1(group opacity) | 是 | 需要等所有子 layer 渲染完成后统一设置透明度 |

**iOS 9+ 的圆角优化: **

  • UIImageViewUIButtoncornerRadius + masksToBounds 不再触发离屏渲染

  • 前提:没有设置 backgroundColor,图层内容是静态图片

  • 系统对这些简单情况做了优化,直接在 GPU 中完成裁剪

8.3 隐式动画的底层原理

  • CALayer 的可动画属性(如 position、opacity、backgroundColor)被修改时,默认会产生隐式动画

  • 原理:

  1. CALayer 的 actionForKey: 被调用

  2. 如果返回一个 CAAction 对象(通常是 CABasicAnimation

  3. 将动画添加到 layer 的动画队列中

  4. 在下一个 commit transaction 中提交给 Render Server

  • UIView 的属性修改不会产生隐式动画(因为 UIView 在 actionForLayer:forKey: 中返回 NSNull)

  • [UIView animateWithDuration:animations:] 的原理:

  1. 内部调用 [CATransaction begin]

  2. 设置 animationDurationanimationTimingFunction

  3. 在 animations block 中修改属性时,actionForKey: 返回的不是 NSNull 而是 CABasicAnimation

  4. [CATransaction commit] 提交

九、App 启动底层

9.1 dyld 加载的完整流程


内核(XNU):

  1. exec() 系统调用

  2. 创建进程,分配虚拟地址空间

  3. 将 App 的 Mach-O 映射到虚拟内存(mmap)

  4. 将 dyld 映射到虚拟内存

  5. 设置进程入口为 dyld 的入口(__dyld_start)

  6. 切换到用户态

  


dyld(动态链接器):

  7. __dyld_start → dyldbootstrap::start → dyld::_main

  


  8. 加载可执行文件(读取 Mach-O header 和 load commands)

  


  9. 加载所有依赖的动态库(递归):

     - 从 LC_LOAD_DYLIB 中读取依赖列表

     - 检查共享缓存(dyld shared cache)中是否已有

     - 未在缓存中:mmap 加载 dylib

     - 验证签名

     - 递归加载 dylib 的依赖

  


  10. Rebase(内部指针修正):

      - ASLR 为每个镜像分配随机 slide 值

      - 遍历 __DATA 段中所有需要 rebase 的指针

      - 将每个指针加上 slide 值

      - 这些指针信息存储在 LC_DYLD_INFO_ONLY 的 rebase info 中

  


  11. Bind(外部符号绑定):

      - 遍历 bind info(存储在 LC_DYLD_INFO_ONLY 中)

      - 在依赖的 dylib 中查找符号的实际地址

      - 将 __DATA 段中的符号指针修改为实际地址

      - 懒绑定(Lazy Binding):延迟到第一次调用时才绑定(通过 stub_helper)

  


  12. 通知 objc runtime:

      - dyld 调用 _objc_init 中注册的 map_images 回调

      - Runtime 注册所有 ObjC 类、Category、Protocol

      - 处理 +load 方法

  


  13. 初始化器(Initializers):

      - C++ 全局变量构造函数(__mod_init_func)

      - __attribute__((constructor)) 函数

      - +load 方法(已在步骤 12 中调用)

  


  14. 调用 main() 函数

**dyld shared cache(共享缓存): **

  • 所有系统动态库被预先链接并合并为一个大文件

  • 路径:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

  • 优点:

  - 加载速度快(已预链接,不需要 rebase/bind)

  - 内存共享(所有进程共享同一份映射)

  - 大小约 1GB+

9.2 _objc_init 的完整流程**


void _objc_init(void) {

    // 1. 初始化锁

    // 2. 初始化 tagged pointer 混淆器

    // 3. 向 dyld 注册三个回调:

    _dyld_objc_notify_register(

        &map_images,        // 当新的 image 被映射到内存时调用

        &load_images,       // 当 image 初始化时调用(调用 +load)

        &unmap_image        // 当 image 被卸载时调用

    );

}

  


// map_images:读取镜像中的 ObjC 元数据

void map_images(unsigned count, const char * const paths[],

                const struct mach_header * const mhdrs[]) {

    mutex_locker_t lock(runtimeLock);

    map_images_nolock(count, paths, mhdrs);

}

  


void map_images_nolock(unsigned mhCount, const char * const mhPaths[],

                        const struct mach_header * const mhdrs[]) {

    // ...

    _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);

}

  


void _read_images(header_info **hList, uint32_t hCount,

                   int totalClasses, int unoptimizedTotalClasses) {

  


    // 1. 读取所有类(__objc_classlist 段)

    for (EACH_HEADER) {

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

        for (i = 0; i < count; i++) {

            Class cls = (Class)classlist[i];

            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            // 将类添加到 gdb_objc_realized_classes 哈希表中

        }

    }

  


    // 2. 修正重映射的类(Remapped Classes)

  


    // 3. 读取所有 Category(__objc_catlist 段)

    for (EACH_HEADER) {

        auto catlist = _getObjc2CategoryList(hi, &count);

        for (i = 0; i < count; i++) {

            category_t *cat = catlist[i];

            // 如果宿主类已 realized,立即 attach

            // 否则存入 unattachedCategories

        }

    }

  


    // 4. 读取协议(__objc_protolist 段)

    // 5. 读取 selector 引用(__objc_selrefs 段)→ 唯一化

    // 6. 读取类引用(__objc_classrefs 段)

    // 7. 实现所有未 realized 的类(懒加载 vs 非懒加载)

  


    // 非懒加载类(实现了 +load 或有静态实例):立即 realize

    // 懒加载类:等第一次使用时再 realize

}

十、Swift 底层

10.1 Swift 对象的完整内存布局


Swift Class Object(堆上):

┌──────────────────────────────┐

│ Metadata Pointer (8 bytes)    │  → 类似 OC 的 isa

│   指向 Full Metadata          │

├──────────────────────────────┤

│ Reference Count (8 bytes)     │

│   ┌─ Strong RC (32 bits)      │  位域:上半部分

│   ├─ Unowned RC (32 bits)     │  位域:下半部分

│   └─ Flags                    │  是否使用 side table、是否正在 deinit

├──────────────────────────────┤

│ Stored Property 1             │

│ Stored Property 2             │

│ ...                           │

└──────────────────────────────┘

  


Full Metadata(类元数据):

┌──────────────────────────────┐

│ Kind(类型种类)                │  0 = Class, 1 = Struct, 2 = Enum...

├──────────────────────────────┤

│ Type Descriptor Pointer       │  → 类型描述(包含名称、字段信息等)

├──────────────────────────────┤

│ Superclass Metadata Pointer   │  → 父类元数据(OC 类也有对应的)

├──────────────────────────────┤

│ Negative Stride Flags         │

├──────────────────────────────┤

│ Instance Address Point        │

├──────────────────────────────┤

│ Instance Size                 │  实例大小

├──────────────────────────────┤

│ Instance Alignment            │  实例对齐

├──────────────────────────────┤

│ VTable                        │  虚函数表

│   entry 0: func ptr           │

│   entry 1: func ptr           │

│   ...                         │

└──────────────────────────────┘

**Swift 引用计数的三种状态: **


状态 1:Inline(内联,大部分情况)

  - Strong RC 和 Unowned RC 直接存储在对象头的 refCounts 字段中

  - 原子操作读写

  


状态 2:Side Table(当有 weak 引用时)

  - 创建 HeapObjectSideTableEntry

  - refCounts 字段变为指向 side table 的指针

  - Side Table 包含:Strong RC + Unowned RC + Weak RC + 对象指针

  - Weak 引用指向 Side Table 而非对象本身

  - 对象 deinit 后,Side Table 仍然存在(直到最后一个 weak 引用消失)

  - 这就是为什么 weak 引用访问已释放对象返回 nil 而不是 crash

  


状态 3:Immortal(永生对象)

  - Strong RC = max,永远不会被释放

  - 用于全局变量、字符串常量等

10.2 Swift 方法派发的底层

**VTable 派发(class 的方法): **


调用 obj.method() 时:

1. 从 obj 的第一个字段取出 metadata pointer

2. 从 metadata 中根据方法的 VTable offset 取出函数指针

3. 直接调用函数指针

// 整个过程只需要 2-3 次内存访问 + 1 次间接跳转

// 比 OC 的 objc_msgSend 快得多(不需要哈希查找)

**Protocol Witness Table 派发: **


protocol Drawable {

    func draw()

    func area() -> Double

}

  


// 每个遵循 Drawable 的具体类型都会生成一个 Witness Table:

struct DrawableWitnessTable {

    func_ptr draw;    // → Circle.draw 的实际函数指针

    func_ptr area;    // → Circle.area 的实际函数指针

};

  


// 调用 existential.draw() 时:

1. 从 Existential Container 中取出 Protocol Witness Table 指针

2. 从表中根据方法的偏移量取出函数指针

3. 从 Existential Container 中取出值(或值的指针)

4. 调用函数指针,传入值作为 self

10.3 Swift 的 Existential Container 深入


// 5 个 word = 40 字节

  


// 小值(<= 3 words = 24 bytes):

┌────────────────────────┐

│ Value Buffer (24 bytes) │  ← 直接存储值(inline storage)

│   word 0: value data    │

│   word 1: value data    │

│   word 2: value data    │

├────────────────────────┤

│ Value Witness Table ptr │  ← 值操作表(copy/destroy/size/alignment...)

├────────────────────────┤

│ Protocol Witness Table  │  ← 协议方法表

└────────────────────────┘

  


// 大值(> 3 words):

┌────────────────────────┐

│ Value Buffer (24 bytes) │

│   word 0: heap ptr  ─────→ [堆上分配的值]

│   word 1: (unused)      │

│   word 2: (unused)      │

├────────────────────────┤

│ Value Witness Table ptr │

├────────────────────────┤

│ Protocol Witness Table  │

└────────────────────────┘

  


// 多个协议组合(如 Drawable & Printable):

// 每多一个协议,就多一个 Protocol Witness Table 指针

// 容器大小 = 24 + 8 + 8 * N(N = 协议数量)

**为什么泛型约束比 Existential Type 性能好? **


// 方式 1:Existential Type(协议类型)

func draw(shape: Drawable) {

    shape.draw()  // 通过 Existential Container → PWT → 间接调用

}

  


// 方式 2:泛型约束

func draw<T: Drawable>(shape: T) {

    shape.draw()  // 编译器在 Whole Module Optimization 下可以特化

    // 特化后变成:

    // shape.draw()  // 直接调用 Circle.draw(),零开销!

}

  • Existential:每次调用都要通过容器 → 表 → 间接跳转,无法内联优化

  • 泛型:编译器可以生成特化版本,直接调用具体类型的方法,甚至内联

  • 泛型在未特化时也比 Existential 好:不需要装箱/拆箱(Value Buffer 的分配/释放)


第二章:第三方常用库原理与八股文

一、SDWebImage 源码级分析

1.1 SDWebImageManager 的核心流程


sd_setImageWithURL:

  → UIView+WebCache 分类方法

  → sd_internalSetImageWithURL:...

    → 取消当前正在进行的下载操作(sd_cancelImageLoadOperationWithKey:)

    → [SDWebImageManager loadImageWithURL:options:progress:completed:]

      → 1. 生成缓存 key(默认 = URL.absoluteString)

      → 2. [SDImageCache queryImageForKey:options:context:completion:]

        → 2a. 内存缓存查找(SDMemoryCache: NSCache 子类)

          → [self.memoryCache objectForKey:key]

          → NSCache 底层使用 pthread_mutex + 双向链表 + 哈希表

          → LRU 淘汰(NSCache 自动管理,但不保证 LRU)

        → 2b. 磁盘缓存查找(在 ioQueue 串行队列中异步执行)

          → 文件名 = MD5(key)

          → NSFileManager 查找文件

          → 读取 NSData

          → [SDImageCodersManager.sharedManager decodedImageWithData:]

            → 根据图片格式选择解码器(PNG/JPEG/WebP/GIF/HEIF)

            → 解码后的 UIImage 存入内存缓存

      → 3. 缓存未命中 → [SDWebImageDownloader downloadImageWithURL:...]

        → 创建 SDWebImageDownloaderOperation(NSOperation 子类)

        → 添加到 downloadQueue(NSOperationQueue,默认 maxConcurrentOperationCount = 6)

        → 内部使用 NSURLSession dataTask

        → 下载完成后:

          a. 解码(子线程)

          b. 存入磁盘缓存(ioQueue 串行队列)

          c. 存入内存缓存

          d. 回调主线程

1.2 SDWebImage 的图片解码细节


为什么需要预解码?

  


UIImage 默认使用"惰性解码":

  1. imageWithData: 只是创建了 UIImage 对象,持有压缩数据的引用

  2. 图片实际显示时(CALayer 提交到 GPU 前),系统才进行解码

  3. 解码发生在主线程,可能导致卡顿

  


SDWebImage 的预解码(在子线程):

  1. 创建 CGBitmapContext(位图上下文)

     → CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, bitmapInfo)

     → bytesPerRow = 对齐到 64 字节(GPU 优化)

  2. 将压缩图片绘制到 context 中

     → CGContextDrawImage(context, rect, imageRef)

     → 这一步触发解码

  3. 从 context 创建新的 CGImageRef

     → CGBitmapContextCreateImage(context)

     → 得到的是已解码的位图

  4. 用解码后的 CGImageRef 创建 UIImage

  5. 缓存解码后的 UIImage 到内存中

  


内存代价:

  解码后的位图大小 = width × height × 4(RGBA)

  例如:1000×1000 的图 = 4MB

  这就是为什么大图需要降采样

1.3 SDWebImage 的请求合并


同一 URL 的多个请求不会重复下载:

  


SDWebImageDownloader 维护一个字典:

  _URLOperations: { URL → SDWebImageDownloaderOperation }

  


当请求到来时:

  1. 检查 _URLOperations 中是否已有该 URL 的 operation

  2. 如果有:将新的 progressBlock 和 completedBlock 添加到现有 operation 的回调数组中

  3. 如果没有:创建新的 operation

  


operation 完成时:

  遍历所有回调,逐个调用

  从 _URLOperations 中移除该 URL

二、YYCache 源码级分析

2.1 YYMemoryCache 的 LRU 实现


核心数据结构:

  _YYLinkedMap:双向链表 + CFMutableDictionary

  


  _YYLinkedMapNode:

    - _prev, _next(链表指针)

    - _key, _value

    - _cost(内存代价)

    - _time(最后访问时间)

  


  _YYLinkedMap:

    - _head, _tail(链表头尾)

    - _dic(CFMutableDictionary,key  node)

    - _totalCost, _totalCount

    - _releaseOnMainThread(是否在主线程释放对象)

    - _releaseAsynchronously(是否异步释放对象)

  


操作时间复杂度:

  - 插入:O(1)(插入链表头部 + 哈希表 put)

  - 查找:O(1)(哈希表 get + 移到链表头部)

  - 删除最久未使用:O(1)(删除链表尾部 + 哈希表 remove)

  


为什么不用 NSCache?

  1. NSCacheLRU 策略不可靠(只保证"大致 LRU")

  2. NSCache 不提供 cost limit 的精确控制

  3. NSCache 不支持超时清理

  4. YYMemoryCache 额外提供了 age-based 清理

2.2 YYDiskCache 的 SQLite + File 混合存储


性能测试数据(YY 作者的 benchmark):

  


写入性能(数据大小 → 最优方案):

  < 20KB → SQLite 更快(文件系统的 inode 创建开销大于 SQLite 事务开销)

  > 20KB → 文件更快(SQLite 对大 BLOB 处理效率低)

  


读取性能:

  < 20KB → SQLite 更快(单次 IO 完成)

  > 20KB → 文件更快(SQLite 需要将整个 BLOB 加载到内存后再返回)

  


YYKVStorage 的实现:

  - SQLite 存储:key + valueBLOB+ size + modifiedTime + accessTime + extendedData

  - 文件存储:key → 文件名,文件路径 = basePath/data/MD5(key)

  - SQLite 中仍然存储文件的元数据(key、size、time),只是 value 为空

  - 查询时先查 SQLite 获取元数据,如果 value 非空直接返回,否则读文件

三、fishhook 底层实现详解

3.1 Mach-O 中的符号间接寻址机制


当代码调用 NSLog(@"Hello") 时:

  


编译后的指令(__TEXT 段):

  bl __stub_NSLog       // 跳转到 stub

  


__stubs 段:

  __stub_NSLog:

    ldr x16, [x16, #offset]  // 从 __la_symbol_ptr 加载地址

    br x16                     // 跳转

  


__la_symbol_ptr 段(__DATA):

  [slot for NSLog] = __stub_helper + offset  // 初始指向 stub_helper

  


__stub_helper 段:

  __stub_helper_NSLog:

    ldr w16, literal_pool    // 加载符号的 lazy bind info 偏移

    b __stub_helper_common   // 跳转到公共 helper

  


  __stub_helper_common:

    // 调用 dyld::fastBindLazySymbol

    // dyld 在符号表中查找 NSLog 的实际地址

    // 将地址写回 __la_symbol_ptr 对应的 slot

    // 跳转到实际地址

  


第二次调用时:

  __la_symbol_ptr 已被修改为实际地址

  ldr + br 直接跳转到 Foundation 中的 NSLog 实现

3.2 fishhook 的符号查找算法


// fishhook 的核心函数

static void rebind_symbols_for_image(struct rebinding rebindings[],

                                      size_t rebindings_nel,

                                      const struct mach_header *header,

                                      intptr_t slide) {

    // 1. 获取 __LINKEDIT 段的加载命令

    // 2. 获取符号表(LC_SYMTAB → symtab)

    // 3. 获取动态符号表(LC_DYSYMTAB → dysymtab)

    // 4. 获取字符串表(strtab)

  


    // 5. 遍历 __DATA 段中的所有 section

    for (每个 __la_symbol_ptr 或 __nl_symbol_ptr section) {

        // 6. 获取 indirect symbol table 的对应区域

        uint32_t *indirect_symtab = dysymtab->indirectsymoff + linkedit_base;

        // section->reserved1 记录了该 section 在 indirect symbol table 中的起始索引

  


        // 7. 遍历每个指针 slot

        for (uint i = 0; i < section->size / sizeof(void *); i++) {

            // 8. 从 indirect symbol table 获取符号索引

            uint32_t symtab_index = indirect_symtab[section->reserved1 + i];

  


            // 9. 从符号表获取符号名的字符串表偏移

            uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;

  


            // 10. 从字符串表获取符号名

            char *symbol_name = strtab + strtab_offset;

  


            // 11. 与要替换的符号名比较

            if (strcmp(symbol_name, target_name) == 0) {

                // 12. 保存原始指针

                *(rebinding->replaced) = indirect_symbol_bindings[i];

                // 13. 替换为新指针

                indirect_symbol_bindings[i] = rebinding->replacement;

            }

        }

    }

}

四、Combine 底层原理

4.1 Publisher-Subscriber 的连接过程


let publisher = [1, 2, 3].publisher

let subscriber = Subscribers.Sink<Int, Never>(receiveCompletion: { _ in },

                                               receiveValue: { print($0) })

publisher.subscribe(subscriber)

  


底层流程:

1. publisher.subscribe(subscriber)

   → publisher 调用 subscriber.receive(subscription:)

   → 传入一个 Subscription 对象(协议要求)

  


2. subscriber 收到 subscription 后,调用:

   subscription.request(.unlimited)  // 请求数据

   → 这就是 Backpressure 机制:subscriber 控制接收速率

  


3. subscription 根据请求量发送数据:

   subscriber.receive(1// 返回 Subscribers.Demand,表示还需要多少

   subscriber.receive(2)

   subscriber.receive(3)

   subscriber.receive(completion: .finished)

  


Backpressure(背压)的意义:

  - subscriber 可以控制 publisher 的数据发送速率

  - .unlimited:不限制

  - .none:不再需要更多

  - .max(n):再发 n 个

  - 这是 Combine 与 RxSwift 的核心区别之一

  - RxSwift 没有背压,数据流出过快时可能导致缓冲区爆满


第三章:iOS 开发难点问题与解决方案

一、内存相关难题

1.1 循环引用的底层排查——MLeaksFinder 原理详解


MLeaksFinder 的核心原理:

  


1. 利用 Method Swizzling hook 以下方法:

   - UINavigationControllerpopViewControllerAnimated:

   - UINavigationControllerdismissViewControllerAnimated:completion:

   - UIViewControllerviewDidDisappear:

  


2. 当 VCpop/dismiss 时:

   - 调用 [vc willDealloc]

   - willDealloc 内部:

     __weak id weakSelf = self;

     dispatch_after(2秒, dispatch_get_main_queue(), ^{

         // 2秒后检查 weakSelf 是否还有值

         if (weakSelf) {

             // 对象还在!说明没有被正确释放 → 可能有循环引用

             [weakSelf alertLeakInfo];

         }

     });

   - 同时递归调用所有子视图和子控制器的 willDealloc

  


3. 为什么延迟 2 秒?

   - 给系统足够的时间完成 dealloc

   - 考虑到动画时间和 autorelease pool 的释放时机

  


4. 配合 FBRetainCycleDetector(可选):

   - 检测到泄漏后,使用 FBRetainCycleDetector 分析循环引用链

   - FBRetainCycleDetector 原理:

     a. 从泄漏对象开始,使用 Runtime 获取所有强引用的成员变量

     b. 对每个强引用对象递归获取其强引用

     c. 使用 DFS(深度优先搜索)检测引用图中是否有环

     d. 找到环后输出循环引用链

1.2 NSTimer 循环引用的三种解决方案详解

方案一:NSProxy 中间对象


NSProxy 特殊之处:

  - 继承自 NSProxy 而非 NSObject

  - 不走 OC 的方法查找流程

  - 直接进入消息转发(-methodSignatureForSelector: / -forwardInvocation:)

  - 转发效率比 NSObject 高(NSObject 要先查方法列表,查不到才转发)

  


实现:

  WeakProxy 持有 weak target

  Timer 强引用 WeakProxy

  WeakProxy 将所有消息转发给 target

  target 释放后,WeakProxy.target = nil

  Timer fire 时调用 WeakProxy 的方法,转发给 nil → 无操作

  


为什么用 NSProxy 而不是 NSObject?

  - NSObject 子类需要重写 forwardingTargetForSelector:

  - 但 NSObject 可能已有同名方法(如 respondsToSelector:),不会走转发

  - NSProxy 完全没有自己的实例方法,所有消息都走转发

  - 更纯粹、更安全

方案二:iOS 10+ Block API


// iOS 10+ 提供的 block-based API

__weak typeof(self) weakSelf = self;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0

                                             repeats:YES

                                               block:^(NSTimer *timer) {

    __strong typeof(weakSelf) strongSelf = weakSelf;

    if (!strongSelf) {

        [timer invalidate];

        return;

    }

    [strongSelf doSomething];

}];

  


// 底层原理:Timer 强引用 block,block 弱引用 self

// self 释放后,block 中 weakSelf 为 nil,可手动 invalidate timer

方案三:在合适的时机 invalidate


问题:在 dealloc 中 invalidate 是行不通的

  - Timer 强引用 selfself 引用计数永远 > 0 → dealloc 永远不会调用

  

正确时机:

  - viewWillDisappear:(但注意 push 新页面也会调用)

  - viewDidDisappear:(配合判断是否是被 pop)

  - didMoveToParentViewController:(参数为 nil 时表示被移除)

1.3 野指针的底层检测方案


自定义野指针检测系统(类似 Zombie Objects 但更强大):

  


原理:

1. Hook NSObjectdealloc 方法

2. 在 dealloc 中:

   a. 记录对象的类名和地址

   b. 不调用原始的 dealloc(对象不会真正释放!)

   c. 使用 objc_destructInstance(self) 释放关联对象和弱引用

   d. 用 memset 将对象内存填充为特殊值(如 0x55)

   e. 将对象的 isa 修改为自定义的"僵尸类"

   f. 将对象放入"延迟释放队列"

  


3. 僵尸类实现了 -forwardInvocation: 等方法:

   a. 当野指针访问时,消息发送到僵尸类

   b. 僵尸类捕获信息:被调用的方法名 + 对象原始类名 + 地址

   c. 输出详细的错误日志

   d. 可选择 crash 或继续运行

  


4. 延迟释放队列管理:

   a. 队列有大小限制(如 100 个对象 或 10MB)

   b. 超出限制时,释放最早加入的对象(先进先出)

   c. 释放时恢复 isa 并调用原始 free

  


优势(vs Xcode Zombie Objects):

  - 可以在 Release 环境使用

  - 可以控制内存消耗

  - 可以上报到崩溃监控系统

  - 可以记录更多上下文信息(调用栈等)

二、线程安全深度问题

2.1 多线程读写 crash 的底层原因


NSMutableArray 多线程 crash 的根本原因:

  


场景:线程 A 读取 array[5],线程 B 同时 removeAllObjects

  


crash 原因分析:

1. NSMutableArray 内部使用 C 数组存储元素指针

2. removeAllObjects 会:

   a. 逐个 release 元素

   b. 修改 count

   c. 可能 realloc 内部数组(缩容)

3. 如果线程 A 在步骤 2c 之后访问 array[5]:

   - 内部数组可能已被 realloc,原地址失效 → EXC_BAD_ACCESS

4. 如果线程 A 在步骤 2a 的过程中访问 array[5]:

   - 元素可能已被 release 但指针还在数组中 → 野指针访问

  


NSDictionary 多线程 crash 的根本原因:

1. NSDictionary 内部使用哈希表

2. 插入/删除可能触发 rehash(扩容/缩容)

3. rehash 过程中:

   - 分配新的哈希表

   - 将旧表的元素重新哈希到新表

   - 释放旧表

4. 如果读线程正在遍历旧表,而写线程触发了 rehash

   - 旧表被释放 → 读线程访问已释放的内存 → crash

2.2 dispatch_barrier_async 实现读写锁


读写锁的语义:

  - 多个读取可以同时进行

  - 写入时独占,不允许其他读取或写入

  


GCD 实现:

  // 创建自定义并发队列(必须是自定义的!不能用全局队列)

  dispatch_queue_t rwQueue = dispatch_queue_create("com.example.rw",

                                                     DISPATCH_QUEUE_CONCURRENT);

  


  // 读取操作(可并发)

  - (id)readData {

      __block id result;

      dispatch_sync(rwQueue, ^{  // sync:等待结果返回

          result = self.data;

      });

      return result;

  }

  


  // 写入操作(独占)

  - (void)writeData:(id)data {

      dispatch_barrier_async(rwQueue, ^{  // barrier:等前面的都完成,自己独占执行

          self.data = data;

      });

  }

  


底层原理:

  barrier task 提交到并发队列后:

  1. 队列标记进入 barrier 模式

  2. 等待所有正在执行的 task 完成

  3. 执行 barrier task(此时不允许新 task 执行)

  4. barrier task 完成后,解除 barrier 模式

  5. 后续 task 恢复正常并发执行

  


为什么不能用全局队列?

  全局队列被所有代码共享,如果使用 barrier:

  - 会阻塞其他不相关代码提交到全局队列的 task

  - GCD 会直接忽略全局队列上的 barrier 标记(按普通 async 执行)

三、架构难题深入

3.1 CTMediator 的底层实现原理


核心思想:利用 Runtime 动态调用 Target-Action

  


流程:

1. 调用方通过 CTMediator 的分类方法:

   [CTMediator performTarget:@"UserModule"

                       action:@"showUserProfile"

                       params:@{@"userId": @"123"}]

  


2. CTMediator 内部:

   // 拼接 Target 类名

   NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];

   // 拼接 Action 方法名

   NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];

  


   // 利用 Runtime 获取类和方法

   Class targetClass = NSClassFromString(targetClassString);

   id target = [[targetClass alloc] init];

   SEL action = NSSelectorFromString(actionString);

  


   // 调用

   if ([target respondsToSelector:action]) {

       return [target performSelector:action withObject:params];

   }

  


3. Target 类(每个组件提供):

   @implementation Target_UserModule

   - (UIViewController *)Action_showUserProfile:(NSDictionary *)params {

       NSString *userId = params[@"userId"];

       UserProfileViewController *vc = [[UserProfileViewController alloc] initWithUserId:userId];

       return vc;

   }

   @end

  


4. 分类(供调用方使用,提供编译期检查):

   @implementation CTMediator (UserModule)

   - (UIViewController *)showUserProfileWithUserId:(NSString *)userId {

       return [self performTarget:@"UserModule"

                           action:@"showUserProfile"

                           params:@{@"userId": userId}];

   }

   @end

  


优势:

  - Target 类不需要注册,Runtime 动态查找

  - 组件间完全无依赖(只依赖 CTMediator)

  - 分类提供了类型安全的调用接口

  

劣势:

  - 字符串硬编码(target name + action name)

  - 需要维护分类和 Target 类的一致性

  - performSelector 最多传一个参数,复杂参数只能用字典


第四章:性能优化八股文与深入细节

一、启动优化——二进制重排原理深入

1.1 Page Fault 的底层原理


虚拟内存 → 物理内存的映射:

  


1. App 的 Mach-O 被 mmap 到虚拟地址空间

   - mmap 不会立即加载文件到物理内存

   - 只是建立虚拟地址 → 文件偏移的映射关系

  


2. 当 CPU 访问某个虚拟地址时:

   a. MMU(内存管理单元)查找页表

   b. 如果页表中有映射 → 直接访问物理内存(命中 TLB 缓存更快)

   c. 如果页表中无映射 → Page Fault(缺页中断)

  


3. Page Fault 处理流程:

   a. CPU 暂停当前执行,陷入内核态

   b. 内核的 page fault handler 处理:

      - 找到空闲的物理内存页

      - 从磁盘读取对应的文件数据到物理内存页

      - 对代码页进行签名验证(iOS 安全要求)

      - 更新页表(虚拟页 → 物理页的映射)

   c. 返回用户态,CPU 从中断处继续执行

  


4. 耗时分析:

   - 从磁盘读取数据:约 0.1~0.5ms

   - 签名验证:约 0.1~0.3ms

   - 页表更新:约 0.01ms

   - 总计:一次 Page Fault 约 0.3~1ms

  


5. 启动时的 Page Fault:

   - App 启动时需要执行大量代码

   - 这些代码分布在多个不同的内存页中

   - 每个未加载的页都会触发一次 Page Fault

   - 如果启动相关的函数分散在 100 个页中 → 100 次 Page Fault → 30~100ms

1.2 Clang 插桩获取启动函数调用顺序


原理:

  编译时在每个函数入口插入一个回调函数调用

  运行时记录函数调用顺序

  


步骤:

1. 编译设置:

   Other C Flags: -fsanitize-coverage=func,trace-pc-guard

   Other Swift Flags: -sanitize-coverage=func -sanitize=undefined

  


2. 实现回调函数:

   void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {

       // start 和 stop 是编译器为所有插桩点分配的 guard 数组的起止指针

       // 初始化 guard 值(非零表示启用,零表示禁用)

       if (start == stop || *start) return;

       for (uint32_t *x = start; x < stop; x++) {

           *x = 1// 启用所有插桩点

       }

   }

  


   void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {

       if (!*guard) return;  // guard 为 0 表示不需要记录

       *guard = 0;           // 每个函数只记录一次

  


       // 获取调用者的地址

       void *PC = __builtin_return_address(0);

  


       // 存储到线程安全的数组中

       // 使用 atomic 操作或 OSAtomicEnqueue

       OSAtomicEnqueue(&symbolQueue, createNode(PC), offsetof(Node, next));

   }

  


3. 在 App 启动完成后,收集所有记录的 PC 地址:

   - 将 PC 转换为符号名(使用 dladdr)

   - 去重(保留首次出现)

   - 输出 order 文件

  


4. 配置 Xcode:

   Build Settings → Order File → 设置为生成的 order 文件路径

   链接器(ld)会按照 order 文件中的函数顺序排列代码

  


效果:

  将启动时调用的所有函数紧凑地排列在连续的内存页中

  Page Fault 次数可从 100+ 减少到 10-20 次

  启动耗时可减少 10-30%

二、卡顿监测——底层实现

2.1 基于 RunLoop Observer 的卡顿监测


完整实现:

  


// 子线程监测

- (void)startMonitor {

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        while (self.isMonitoring) {

            // 信号量等待,超时时间 = 阈值(如 50ms)

            long result = dispatch_semaphore_wait(self.semaphore,

                dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));

  


            if (result != 0) {

                // 超时!说明主线程在 50ms 内没有触发 RunLoop 状态变化

                if (self.currentActivity == kCFRunLoopBeforeSources ||

                    self.currentActivity == kCFRunLoopAfterWaiting) {

                    // 连续超时 N 次才上报(避免偶发误报)

                    if (++self.timeoutCount < 3) continue;

                    self.timeoutCount = 0;

  


                    // 收集主线程调用栈

                    NSString *stack = [self captureMainThreadStack];

                    // 上报

                    [self reportLag:stack];

                }

            }

            self.timeoutCount = 0;

        }

    });

  


    // 主线程 RunLoop Observer

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(

        NULL, kCFRunLoopAllActivities, YES, 0,

        ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

            self.currentActivity = activity;

            // 每次 RunLoop 状态变化,发送信号

            dispatch_semaphore_signal(self.semaphore);

        });

    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

}

  


// 获取主线程调用栈的底层实现

- (NSString *)captureMainThreadStack {

    thread_t mainThread = mach_thread_self(); // 需要保存主线程的 thread port

  


    // 挂起主线程(暂停执行,保证栈不变)

    thread_suspend(mainThread);

  


    // 获取主线程的寄存器状态

    _STRUCT_MCONTEXT machineContext;

    mach_msg_type_number_t stateCount = MACHINE_THREAD_STATE_COUNT;

    thread_get_state(mainThread, MACHINE_THREAD_STATE,

                     (thread_state_t)&machineContext.__ss, &stateCount);

  


    // 从 FP(Frame Pointer)寄存器开始回溯调用栈

    uintptr_t fp = machineContext.__ss.__fp;

    uintptr_t pc = machineContext.__ss.__pc;

  


    NSMutableArray *frames = [NSMutableArray array];

    while (fp && pc) {

        Dl_info info;

        if (dladdr((void *)pc, &info)) {

            [frames addObject:[NSString stringWithFormat:@"%s %s + %ld",

                info.dli_fname, info.dli_sname,

                (long)(pc - (uintptr_t)info.dli_saddr)]];

        }

        // 沿栈帧链回溯

        pc = *(uintptr_t *)(fp + 8);   // 返回地址(LR)

        fp = *(uintptr_t *)fp;          // 上一个栈帧的 FP

    }

  


    // 恢复主线程

    thread_resume(mainThread);

  


    return [frames componentsJoinedByString:@"\n"];

}

三、图片降采样的底层实现


ImageIO 降采样(推荐方式):

  


// 问题:加载 4000x3000 的图片

// 解码后内存:4000 × 3000 × 4 bytes = 48MB !!!

// 但 UIImageView 只有 200x150,实际只需要 400x300(@2x)

  


// 降采样方案:在解码时就按目标大小读取

func downsample(imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {

    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale

  


    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary

    guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL,

                                                       imageSourceOptions) else { return nil }

  


    let downsampleOptions = [

        kCGImageSourceCreateThumbnailFromImageAlways: true,

        kCGImageSourceShouldCacheImmediately: true,     // 立即解码,避免主线程惰性解码

        kCGImageSourceCreateThumbnailWithTransform: true, // 应用 EXIF 方向

        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels

    ] as CFDictionary

  


    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(

        imageSource, 0, downsampleOptions) else { return nil }

  


    return UIImage(cgImage: downsampledImage)

}

  


// 底层原理:

// CGImageSourceCreateThumbnailAtIndex 在解码时就按照目标尺寸读取

// 不需要先完整解码再缩放

// 对于 JPEG:利用 JPEG 的 DCT 分块特性,可以在解码时跳过不需要的块

// 内存使用:400 × 300 × 4 = 0.48MB(vs 48MB,减少 99%)

四、Auto Layout 底层性能


Auto Layout 的约束求解引擎:Cassowary

  


原理:

1. 每个约束表示为一个线性方程或不等式:

   view.left = superview.left + 20

   view.width = superview.width * 0.5

   view.height >= 44

  


2. Cassowary 算法使用单纯形法(Simplex Method)求解线性规划问题

  


3. 性能复杂度:

   iOS 11 及之前:O(N³)(最坏情况),一般 O(N²)

   iOS 12+:Apple 重写了引擎,某些场景下可达到 O(N)

     - 通过识别独立的约束子系统

     - 缓存中间计算结果

     - 增量更新(只重新计算变化的约束)

  


4. 性能建议:

   - 避免约束的动态添加/删除(开销大),用优先级 + 激活/禁用代替

   - 减少约束数量

   - 使用 intrinsicContentSize 减少显式约束

   - 避免大量使用 "priority" 产生的不等式(增加方程数量)


第五章:八股文中的对比总结

一、内存管理对比(深入底层差异)

1.1 weak vs unowned(Swift,底层差异)


weak 的底层实现:

  1. 创建 HeapObjectSideTableEntry(如果还没有)

  2. 对象的 refCounts 从 inline 模式切换为 side table 模式

  3. weak 引用指向 Side Table Entry(不是对象本身)

  4. Side Table 中维护 weak reference count

  5. 对象 deinit 后:

     - Strong RC = 0

     - Side Table 仍然存在(因为 weak RC > 0)

     - weak 引用通过 Side Table 检测到对象已 deinit  返回 nil

  6. 最后一个 weak 引用消失后:

     - weak RC = 0

     - Side Table 释放

     - 对象内存释放

  


unowned 的底层实现:

  1. 不需要 Side Table(如果没有 weak 引用的话)

  2. 使用 inline 的 unowned reference count

  3. 对象 deinit 后:

     - Strong RC = 0

     - Unowned RC > 0  对象内存不释放(保持分配状态)

     - 对象被标记为"已释放但未回收"

  4. 访问 unowned 引用时:

     - 检查 Strong RC 是否为 0

     - 如果是  fatal error: "Attempted to read an unowned reference but object was already deallocated"

  5. 最后一个 unowned 引用消失后:

     - Unowned RC = 0

     - 对象内存真正释放(free)

  


性能对比:

  weak:

    - 额外开销:创建 Side Table + 维护 weak RC + 清除时遍历

    - 每次访问需要检查 Side Table + 可选值解包

  unowned:

    - 额外开销:仅 unowned RC 的原子操作

    - 每次访问只需检查标志位

    -weak 快约 2-3 倍

  


选择建议:

  weak:不确定生命周期关系时使用(安全)

  unowned:确定引用对象生命周期更长时使用(性能)

  unowned(unsafe):等价于 OC 的 __unsafe_unretained(最快但不安全)

1.2 atomic 底层实现


// objc 源码中 atomic 的 getter/setter 实现

  


// Getter

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {

    if (offset == 0) {

        return object_getClass(self);

    }

  


    id *slot = (id *)((char *)self + offset);

  


    if (!atomic) return *slot;  // nonatomic:直接读取

  


    // atomic:加锁读取

    spinlock_t& slotlock = PropertyLocks[slot];

    slotlock.lock();

    id value = objc_retain(*slot);

    slotlock.unlock();

    return objc_autoreleaseReturnValue(value);

    // 注意:这里 retain + autorelease 保证返回值在使用期间不被释放

}

  


// Setter

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) {

    reallySetProperty(self, _cmd, newValue, offset, true, false);

}

  


static void reallySetProperty(id self, SEL _cmd, id newValue,

                                ptrdiff_t offset, bool atomic, bool copy) {

    id oldValue;

    id *slot = (id *)((char *)self + offset);

  


    if (copy) {

        newValue = [newValue copyWithZone:nil];

    } else {

        newValue = [newValue retain];

    }

  


    if (!atomic) {

        oldValue = *slot;

        *slot = newValue;

    } else {

        spinlock_t& slotlock = PropertyLocks[slot];

        slotlock.lock();

        oldValue = *slot;

        *slot = newValue;

        slotlock.unlock();

    }

  


    [oldValue release];

}

  


// PropertyLocks 是一个全局的 StripedMap<spinlock_t>

// 不同属性可能共用同一个锁(哈希冲突)

// 这也是 atomic 性能不太好的原因之一

二、锁的底层对比

2.1 os_unfair_lock vs pthread_mutex 底层差异


os_unfair_lock(iOS 10+):

  - 底层:使用 __ulock_wait / __ulock_wake 系统调用(darwin 内核)

  - 大小:4 字节(一个 uint32_t)

  - 等待方式:互斥等待(不自旋)

  - 特点:

    - "unfair" 意味着不保证 FIFO 公平性

    - 刚释放锁的线程可能立即再次获取(减少上下文切换)

    - 在低竞争场景下,这种"不公平"反而提高了吞吐量

  - 性能:最快的互斥锁

  


pthread_mutex:

  - 底层:POSIX 标准实现,内部也使用 __ulock_wait / __ulock_wake

  - 大小:64 字节(包含更多状态信息)

  - 类型:

    - PTHREAD_MUTEX_NORMAL:普通互斥锁(同线程重复加锁 → 死锁)

    - PTHREAD_MUTEX_RECURSIVE:递归锁(同线程可重复加锁)

    - PTHREAD_MUTEX_ERRORCHECK:检查锁(同线程重复加锁 → 返回错误)

  - 性能:比 os_unfair_lock 慢 10-20%(额外的状态管理和错误检查)

  


dispatch_semaphore:

  - 底层:

    - 快速路径:原子操作 dsema_value(用户态,无系统调用)

    - 慢速路径:semaphore_wait / semaphore_signal(Mach 内核信号量)

  - 特点:

    - 可用于跨进程同步(Mach 信号量是内核对象)

    - 但通常只用于线程同步

    - 支持超时等待

  - 性能:快速路径与 os_unfair_lock 相当,慢速路径开销较大

2.2 OSSpinLock 的优先级反转问题(深入分析)


优先级反转的完整场景:

  


线程优先级:A(高) > B(中) > C(低)

  


时间线:

  t1: 线程 C 获取 spinlock,开始执行临界区

  t2: 线程 A 就绪,抢占 CCPU 时间

  t3: 线程 A 尝试获取 spinlock → 自旋等待

      - A 持续占用 CPU 自旋(因为 A 优先级最高)

      - C 被剥夺了 CPU 时间,无法执行和释放锁

  t4: 线程 B 就绪

      - B 的优先级高于 C,所以 B 也抢占 C

      - C 更加无法获得 CPU 时间

  t5: 死锁状态

      - A 自旋等待 C 释放锁

      - C 无法获得 CPU 时间释放锁(被 AB 抢占)

      - B 虽然不需要锁,但也在运行

  


os_unfair_lock 如何解决?

  - 不使用自旋,而是让等待线程休眠

  - 内核知道哪个线程持有锁(通过 __ulock_waitowner 参数)

  - 如果高优先级线程在等待低优先级线程的锁:

    - 内核临时提升低优先级线程的优先级(Priority Inheritance)

    - 让低优先级线程能获得 CPU 时间并释放锁

    - 释放锁后恢复原始优先级

三、GCD vs NSOperation 底层差异


GCD 底层:

  - C 语言 API

  - libdispatch 库实现

  - 直接管理内核线程(pthread)

  - 使用工作窃取(work-stealing)算法

  - 每个 CPU 核心有一个线程

  - 任务以 block 形式封装(dispatch_continuation_t)

  


NSOperation 底层:

  - Objective-C 封装

  - 内部使用 GCD(dispatch_queue)来管理线程

  - NSOperationQueue 使用 dispatch_queue 作为底层执行器

  - 每个 NSOperation 有状态机(ready → executing → finished)

  - 使用 KVO 通知状态变化

  


// NSOperationQueue 内部大致实现:

@implementation NSOperationQueue

- (void)addOperation:(NSOperation *)op {

    [self.lock lock];

  


    // 检查依赖是否满足

    if ([op isReady]) {

        [self _scheduleOperation:op];

    } else {

        // 添加到等待队列,监听 isReady 变化

        [op addObserver:self forKeyPath:@"isReady" options:0 context:nil];

        [self.pendingOperations addObject:op];

    }

  


    [self.lock unlock];

}

  


- (void)_scheduleOperation:(NSOperation *)op {

    dispatch_async(self.underlyingQueue, ^{

        if ([op isCancelled]) return;

  


        [op start];  // start 内部设置 isExecuting = YES

                     // 调用 main

                     // 完成后设置 isFinished = YES

  


        // isFinished KVO 触发后检查依赖方是否 ready

    });

}

@end

四、事件传递方向 vs 事件响应方向


这是一个经典的对比,方向完全相反:

  


事件传递(Hit-Testing):从上往下

  UIApplicationUIWindow → RootView → SubView → ... → 最深层的 View

  目的:找到最合适的事件处理者(firstResponder)

  方向:父 → 子(但在同级子视图中从后往前,即最上面的优先)

  


事件响应(Responder Chain):从下往上

  Hit-tested View → SuperView → ... → ViewController → UIWindowUIApplication → AppDelegate

  目的:如果 firstResponder 不处理,向上传递找到能处理的

  方向:子 → 父

  


实际应用示例:

  


  问题:按钮超出父视图 bounds,点击无响应

  原因:hitTest 时 pointInside 判定点不在父视图内 → 直接返回 nil

  解决:重写父视图的 pointInside,扩大判定范围

  


  问题:让不规则形状视图只响应可见区域

  解决:重写 pointInside,使用路径判断点是否在不规则区域内

五、RunLoop Mode 对比


NSDefaultRunLoopMode vs UITrackingRunLoopMode vs NSRunLoopCommonModes

  


底层实现:

  CFRunLoopMode 包含的数据:

  struct __CFRunLoopMode {

      CFStringRef _name;            // Mode 名称

      CFMutableSetRef _sources0;    // Source0 集合

      CFMutableSetRef _sources1;    // Source1 集合

      CFMutableArrayRef _observers; // Observer 数组

      CFMutableArrayRef _timers;    // Timer 数组

      CFMutableDictionaryRef _portToV1SourceMap;  // mach port → Source1 映射

      mach_port_t _timerPort;       // Timer 专用端口

      // ...

  };

  


NSRunLoopCommonModes 的底层:

  - 不是一个真正的 Mode

  - RunLoop 结构体中有一个 _commonModes 集合,存放标记为 Common 的 Mode 名称

  - 默认包含:kCFRunLoopDefaultMode + UITrackingRunLoopMode

  - 还有一个 _commonModeItems 集合,存放被添加到 CommonModes 的 Source/Timer/Observer

  - 当一个 Timer 被添加到 CommonModes 时:

    1. 将 Timer 添加到 _commonModeItems

    2. 遍历 _commonModes 中的每个 Mode

    3. 将 Timer 添加到每个 Mode 的 _timers 中

  - 效果:Timer 在 Default 和 Tracking 模式下都能触发

  


滑动时 Timer 不触发的原因:

  1. UIScrollView 滑动时,RunLoop 从 kCFRunLoopDefaultMode 切换到 UITrackingRunLoopMode

  2. 如果 Timer 只添加到了 DefaultMode → 在 TrackingMode 下不在 _timers 中

  3. RunLoop 不会执行不在当前 Mode 中的 Timer

六、深拷贝 vs 浅拷贝(底层实现)


NSStringcopy 底层:

  


// 不可变字符串的 copy

[immutableString copy]

  → 实际上调用的是 -copyWithZone:

  → NSString 的实现:return [self retain// 浅拷贝!

  → 因为不可变字符串不需要真正复制(内容永远不会变)

  


// 不可变字符串的 mutableCopy

[immutableString mutableCopy]

  → 调用 -mutableCopyWithZone:

  → 创建新的 NSMutableString → 深拷贝

  


// 可变字符串的 copy

[mutableString copy]

  → 调用 -copyWithZone:

  → 创建新的 NSString → 深拷贝(返回不可变类型)

  → 如果不深拷贝,返回的"不可变"对象可能被原可变对象修改

  


// property copy 修饰符的底层实现(setter)

- (void)setName:(NSString *)name {

    id old = _name;

    _name = [name copy];  // 如果 name 是 NSString → retain(浅拷贝)

                           // 如果 name 是 NSMutableString → 深拷贝

    [old release];

}


第六章:高难度深底层原理问题

一、Objective-C 类的 Realize 过程


什么是 Realize?

  将类从"未初始化状态"(只有编译期的 class_ro_t)转变为"可用状态"(有 class_rw_t)

  


Non-lazy class vs Lazy class:

  - Non-lazy:实现了 +load 方法 → 在 _read_images 中立即 realize

  - Lazy:大部分类 → 延迟到第一次使用时(lookUpImpOrForward 中)

  


realizeClassWithoutSwift(cls, nil):

  1. 从 class_data_bits_t 中取出 class_ro_t

  2. 分配 class_rw_t 结构体

  3. 设置 rw->ro = ro

  4. 设置 rw->flags(根据 ro->flags 和其他信息)

  5. cls->bits = rw(现在 bits 指向 rw 而非 ro)

  

  6. 递归 realize 父类和元类:

     supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);

     metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

  

  7. 设置正确的父类和元类指针:

     cls->setSuperclass(supercls);

     cls->initClassIsa(metacls);

  

  8. 调整 ivar 布局(如果父类大小变化了——Non-Fragile ABI)

  

  9. 将类插入父类的子类链表

  

  10. 加载所有未附加的 Category:

      methodizeClass(cls, previously);

  


methodizeClass(cls, previously):

  1. 从 class_ro_t 中获取基础方法列表

  2. 将方法列表按 selector 地址排序(用于二分查找)

  3. 添加到 class_rw_t 的方法列表中

  

  4. 加载属性列表和协议列表

  

  5. 附加所有 category:

     attachCategories(cls, cats, ...)

二、Non-Fragile ABI(非脆弱ABI)


问题背景(Fragile ABI 的问题):

  // Objective-C 1.0 的脆弱 ABI

  @interface NSObject { Class isa; }          // isa 在偏移 0

  @interface UIView : NSObject { id _layer; } // _layer 在偏移 8

  @interface UIButton : UIView { id _title; } // _title 在偏移 16

  


  如果 AppleUIView 添加新的成员变量:

  @interface UIView : NSObject { id _layer; id _newIvar; }

  // _layer 偏移 8, _newIvar 偏移 16

  


  问题:UIButton 的 _title 编译时确定偏移是 16

  但现在 UIView 多了一个 ivar,_title 应该在偏移 24

   UIButton 的二进制代码仍然用偏移 16 访问 _title  读到的是 UIView._newIvar  crash

  


Non-Fragile ABI 的解决方案(Objective-C 2.0):

  1. 编译器不再硬编码成员变量的偏移量

  2. 而是生成一个"偏移量变量"(ivar offset variable)

  3. 运行时在 realize 时重新计算偏移量

  


// 编译后的代码:

// 硬编码方式:*(id *)((char *)obj + 16) = value;

// Non-Fragile 方式:*(id *)((char *)obj + OBJC_IVAR_$_UIButton._title) = value;

// OBJC_IVAR_$_UIButton._title 是一个全局变量,运行时修正

  


// realizeClassWithoutSwift 中的步骤 8:

// 如果父类的 instanceSize 与编译时不同:

//   重新计算所有成员变量的偏移量

//   更新 OBJC_IVAR_$ 变量的值

//   更新 instanceSize

三、Swift 的 Type Metadata 系统


Swift 中每个类型都有对应的 Metadata(类型元数据)

  


Metadata 的种类:

  - Class Metadata:类的元数据,包含 VTable

  - Struct Metadata:值类型元数据

  - Enum Metadata:枚举元数据

  - Function Metadata:函数类型元数据

  - Existential Metadata:协议类型元数据

  - Metatype Metadata:类型的类型元数据

  - Tuple Metadata:元组元数据

  


Metadata 的获取方式:

  - 编译时已知类型:直接嵌入二进制(存储在 __TEXT 段或 __DATA 段)

  - 泛型实例化:运行时通过 swift_getGenericMetadata 创建

     使用 MetadataCache 缓存已创建的泛型元数据

     例如 Array<Int> 和 Array<String> 有不同的元数据

  


Value Witness Table(VWT):

  每个类型都有一个 VWT,包含操作该类型值的函数指针:

  


  struct ValueWitnessTable {

      initializeBufferWithCopyOfBuffer  // 在 buffer 中拷贝初始化

      destroy                           // 销毁值

      initializeWithCopy                // 拷贝初始化

      assignWithCopy                    // 拷贝赋值

      initializeWithTake                // 移动初始化(take = 移动语义)

      assignWithTake                    // 移动赋值

      getEnumTagSinglePayload           // 枚举相关

      storeEnumTagSinglePayload         // 枚举相关

      size                              // 值的大小

      stride                            // 对齐后的步幅

      flags                             // 标志(是否 POD、是否 BitwiseTakable 等)

      extraInhabitantCount              // 额外的无效值数量(用于 Optional 优化)

  };

  


Optional 优化(Extra Inhabitants):

  - Bool 只使用 01 两个值

  - UInt8256 个值,其中 254 个可以作为"无效值"

  - Optional<Bool> 不需要额外的存储空间

     使用值 2 表示 .none(nil)

     大小 = 1 字节(与 Bool 相同!)

  - 类引用类型:nil0x0)就是"无效值"

     Optional<AnyObject> 大小 = 8 字节(与指针相同!)

     这就是 Optional"零开销"优化

四、ARM64 调用约定(函数调用的底层)


ARM64(AArch64)调用约定:

  


参数传递:

  - x0~x7:前 8 个整数/指针参数

  - d0~d7:前 8 个浮点参数

  - 超过 8 个的参数通过栈传递

  


返回值:

  - x0:整数/指针返回值

  - d0:浮点返回值

  - 大结构体:通过 x8 传入返回值地址(caller 分配空间)

  


特殊寄存器:

  - x29(FP):帧指针(Frame Pointer),指向当前栈帧底部

  - x30(LR):链接寄存器,保存返回地址

  - SP:栈指针

  


ObjC 方法调用:

  // [obj doSomething:arg1 with:arg2]

  // 编译为:

  // x0 = obj(self)

  // x1 = @selector(doSomething:with:)(_cmd)

  // x2 = arg1

  // x3 = arg2

  // bl _objc_msgSend

  


栈帧结构:

  高地址

  ┌─────────────────┐

  │ 参数 N(如果 > 8 个)│

  │ ...               │

  │ 参数 9             │

  ├─────────────────┤ ← 调用者的 SP

  │ 保存的 LR(x30)   │  ← FP + 8

  │ 保存的 FP(x29)   │  ← FP(当前帧指针)

  ├─────────────────┤

  │ 局部变量         │

  │ ...               │

  ├─────────────────┤ ← SP(当前栈指针)

  低地址

  


  // 回溯调用栈就是沿着 FP 链向上走:

  // 当前 FP → 上一个 FP → 上上一个 FP → ...

  // 每个 FP + 8 处是对应帧的返回地址(LR)

五、PAC(Pointer Authentication Codes)——A12+ 安全特性


arm64e 架构(iPhone XS 及以后)引入了 PAC

  


原理:

  - 利用 64 位指针中未使用的高位存储签名

  - 函数指针、返回地址在使用前会被验证签名

  - 如果签名不匹配(被篡改)→ 进程立即崩溃

  


对 objc_msgSend 的影响:

  - bucket_t 中的 IMP 是经过 PAC 签名的

  - 调用 IMP 前需要先验证签名

  - 这增加了一些性能开销(约 1-2%),但大幅提升了安全性

  


对 fishhook 的影响:

  - fishhook 替换函数指针时,需要使用 ptrauth_sign_unauthenticated 重新签名

  - 否则签名不匹配 → crash

六、Swift Concurrency 的底层——Cooperative Thread Pool


传统 GCD 线程模型:

  - 每个 dispatch_async 可能创建新线程

  - 线程数量可能远超 CPU 核心数

  - 大量线程 → 频繁上下文切换 → 性能下降

  - 线程爆炸问题(Thread Explosion)

  


Swift Concurrency 的协作式线程池:

  - 线程数量 = CPU 核心数(严格限制)

  - 任务不绑定到特定线程

  - 任务在 await 处挂起时:

    1. 保存任务的状态到"续体帧"(continuation frame)

    2. 释放当前线程(线程可执行其他任务)

    3. 当异步操作完成时,任务被重新调度

    4. 可能在不同的线程上恢复执行

  


续体帧(Continuation Frame):

  - 类似函数调用的栈帧,但分配在堆上

  - 包含 await 后续代码的执行状态(局部变量、PC 等)

  - 多个续体帧形成"异步栈"

  - 不同于传统线程栈的固定大小(通常 512KB),续体帧按需分配

  


Job 和 Executor:

  - Task 被分解为多个 Job(在每个 await 点之间是一个 Job)

  - 每个 Job 提交到 Executor 执行

  - Global Executor:默认的全局线程池

  - Serial Executor:Actor 使用的串行执行器

  - Main Actor:在主线程执行的特殊 Actor

七、Crash 收集的底层实现


三种层级的异常捕获:

  


层级 1:Mach 异常(最底层)

  // 注册异常端口

  task_set_exception_ports(

      mach_task_self(),           // 当前进程

      EXC_MASK_ALL,              // 所有异常类型

      exceptionPort,             // 自定义端口

      EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,

      THREAD_STATE_NONE

  );

  


  // 在专门的线程中等待异常消息

  mach_msg(&msg, MACH_RCV_MSG, 0, sizeof(msg), exceptionPort, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);

  


  // 收到异常消息后:

  // msg 包含:异常类型、异常代码、线程 ID、任务 ID

  // 可以获取崩溃线程的寄存器状态和调用栈

  


层级 2:Unix Signal(中间层)

  // 内核将未处理的 Mach 异常转换为 Signal

  // 映射关系:

  //   EXC_BAD_ACCESS → SIGSEGV / SIGBUS

  //   EXC_BAD_INSTRUCTION → SIGILL

  //   EXC_ARITHMETIC → SIGFPE

  //   abort() → SIGABRT

  


  struct sigaction sa;

  sa.sa_sigaction = signalHandler;

  sa.sa_flags = SA_SIGINFO;

  sigemptyset(&sa.sa_mask);

  sigaction(SIGSEGV, &sa, NULL);

  sigaction(SIGBUS, &sa, NULL);

  sigaction(SIGABRT, &sa, NULL);

  


  void signalHandler(int sig, siginfo_t *info, void *context) {

      // sig:信号类型

      // info->si_addr:导致信号的内存地址

      // context:CPU 寄存器上下文

      // 在这里收集调用栈并写入文件

      // 注意:signal handler 中只能调用 async-signal-safe 函数!

      //   不能 malloc、不能 objc_msgSend、不能 NSLog

      //   只能 write()、_exit() 等

  }

  


层级 3:NSException(OC 层)

  NSSetUncaughtExceptionHandler(&exceptionHandler);

  


  void exceptionHandler(NSException *exception) {

      // exception.name:异常名称(如 NSInvalidArgumentException)

      // exception.reason:异常原因

      // exception.callStackSymbols:调用栈符号

      // 在这里保存崩溃信息

  }

  


为什么需要三个层级?

  1. Mach 异常最底层,能捕获所有类型

  2. 但 Mach 异常处理复杂,且某些异常(如 SIGABRT)不是 Mach 异常

  3. Signal 处理 SIGABRT 和其他信号

  4. NSException 提供最详细的 OC 层信息(name、reason、userInfo)

  5. 三者互补,确保不遗漏任何崩溃

八、符号化(Symbolication)的底层过程


从崩溃地址到函数名的完整过程:

  


1. 崩溃日志中的原始信息:

   0x0000000104a8c3d4  MyApp + 0x1243d4

  


2. 计算文件偏移地址:

   文件偏移 = 崩溃地址 - 加载基地址

   = 0x104a8c3d4 - 0x104968000

   = 0x1243D4

  


3. 在 dSYM 文件中查找:

   dSYM 中包含 DWARF 调试信息,结构:

   .debug_info:编译单元、函数、变量等的描述

   .debug_line:地址到源代码行号的映射表

   .debug_abbrev:调试信息的缩写表

   .debug_str:字符串表

  


4. 查找过程:

   a. 在 .debug_aranges 中二分查找 0x1243D4 属于哪个编译单元

   b. 在该编译单元的 .debug_info 中找到包含该地址的 DW_TAG_subprogram(函数)

   c. DW_TAG_subprogram 包含:

      - DW_AT_name:函数名

      - DW_AT_low_pc / DW_AT_high_pc:函数地址范围

      - DW_AT_decl_file:源文件名

      - DW_AT_decl_line:声明行号

   d. 在 .debug_line 中查找精确的源代码行号

  


5. UUID 匹配:

   - 每次编译生成唯一的 UUID(128 位)

   - 存储在 Mach-O 的 LC_UUID load command 中

   - 和 dSYM 中的 UUID 必须一致

   - 不一致说明 dSYM 不是这个 binary 对应的,符号化结果不可信

  


6. atos 命令行工具的原理:

   atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x104968000 0x104a8c3d4

   // 输出:-[ViewController viewDidLoad] (in MyApp) (ViewController.m:42)


附录:高频面试题速查(带底层深度)

必问题目 + 追问

  1. 消息发送机制 → 追问:objc_msgSend 为什么用汇编?cache_t 的哈希冲突如何解决?扩容时为什么清空缓存?

  2. weak 原理 → 追问:SideTable 为什么有 64 个?weak_entry_t 为什么有 inline 优化?

  3. Block → 追问:__forwarding 指针的两种状态?Block copy 时 capture 的对象怎么处理?****

4. RunLoop → 追问:mach_msg 是什么?休眠为什么不消耗 CPU?autorelease 对象什么时候释放?****

5. Category → 追问:运行时如何加载?为什么"覆盖"原方法?class_rw_ext_t 是什么?****

6. KVO → 追问:为什么用 isa swizzling 而不是 method swizzling?动态子类重写了哪些方法?****

7. AutoreleasePool → 追问:page 大小是多少?怎么找到 POOL_BOUNDARY?子页的释放策略?****

8. GCD 死锁 → 追问:dispatch_sync 底层如何检测死锁?线程池大小是多少?****

9. 启动优化 → 追问:Page Fault 一次多少毫秒?Clang 插桩的原理?dyld3 闭包是什么?****

10. 卡顿优化 → 追问:如何在子线程获取主线程调用栈?为什么离屏渲染慢?VSync 机制?****

11. 循环引用 → 追问:MLeaksFinder 原理?NSProxy vs NSObject 的消息转发差异?****

12. 线程安全 → 追问:OSSpinLock 优先级反转的完整过程?os_unfair_lock 如何解决?****

13. 事件传递 → 追问:hitTest 为什么从后往前遍历子视图?手势识别器和 touch 事件的竞争?****

14. HTTPS → 追问:TLS 1.2 vs 1.3 的区别?证书固定的两种方式?****

15. Swift 内存 → 追问:weak 和 unowned 的底层区别?Side Table Entry?****


本文档深入到源码级和汇编级,覆盖 iOS 面试中 99% 以上的深度知识点。理解"为什么这样设计"比记住"怎么实现"更重要。面试时能从表层讲到底层,从使用讲到原理,展现出对技术的深度理解,这才是高分答案。