普通视图

发现新文章,点击刷新页面。
昨天以前首页

isa 指针、元类、继承链

作者 泉木
2026年3月17日 09:58

一、isa 不只是一个指针

在 64 位设备上,指针只需要 36~40 位就能表示所有内存地址。苹果觉得剩下的位浪费了,于是把 isa 设计成了一个 union(联合体) ,把类指针和一堆标志位都塞进了这 64 位里。

这叫 Tagged Pointer / Non-pointer ISA 技术。


二、isa_t 的完整源码

// 文件:objc-private.h
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;         // 原始的 64 位值

private:
    Class cls;              // 类指针(只在 non-pointer isa 关闭时使用)

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;       // 展开后是一堆位域定义
    };
    ...
};

ISA_BITFIELD 展开(ARM64,iOS 真机)

// 这是 ARM64 的位域定义
uintptr_t nonpointer        : 1;   // bit 0
uintptr_t has_assoc         : 1;   // bit 1
uintptr_t has_cxx_dtor      : 1;   // bit 2
uintptr_t shiftcls          : 33;  // bit 3~35  ← 类指针在这里!
uintptr_t magic             : 6;   // bit 36~41
uintptr_t weakly_referenced : 1;   // bit 42
uintptr_t unused            : 1;   // bit 43
uintptr_t has_sidetable_rc  : 1;   // bit 44
uintptr_t extra_rc          : 19;  // bit 45~63

三、每一位的含义逐个解释

bit 0:nonpointer

uintptr_t nonpointer : 1;

含义: 这个 isa 是不是 "non-pointer isa"(优化过的 isa)。

  • 0:纯指针,整个 64 位就是类地址(老设备/某些特殊情况)
  • 1:non-pointer isa,64 位里藏了很多信息

现代 iOS 设备全是 1


bit 1:has_assoc

uintptr_t has_assoc : 1;

含义: 这个对象是否有关联对象(Associated Object)。

关联对象就是你用 objc_setAssociatedObject 给对象动态绑定的数据。

为什么需要这一位?

  • 对象 dealloc 时,runtime 需要清理关联对象
  • 用这一位做快速判断:has_assoc == 0 → 跳过关联对象清理,直接释放,更快

bit 2:has_cxx_dtor

uintptr_t has_cxx_dtor : 1;

含义: 这个类(或它的父类)是否有 C++ 析构函数,或者 OC 的 .cxx_destruct 方法。

.cxx_destruct 是编译器自动生成的方法,用来清理带有 __strong 修饰的成员变量(ARC 下自动 release)。

为什么需要这一位?

  • 对象 dealloc 时,如果没有需要清理的 C++ 对象,就跳过 .cxx_destruct 调用
  • 优化释放速度

bit 3~35:shiftcls(33位)

uintptr_t shiftcls : 33;

含义: 这 33 位才是真正的类指针(右移 3 位存储,取的时候左移 3 位还原)。

为什么只用 33 位?因为 ARM64 的内存对齐保证类地址的低 3 位永远是 0,可以省掉。

如何取出类指针?

// runtime 内部的取法
Class getClass() const {
    return (Class)(shiftcls << 3);  // 左移3位还原真实地址
}

bit 36~41:magic(6位)

uintptr_t magic : 6;

含义: 固定的魔数,值是 0b011010(十进制 26)。

用途: 调试用。当你看到一个 isa,如果 magic 值不对,说明这个对象已经被释放或内存被踩了(野指针)。Xcode 和 runtime 的断言会检查这个值。


bit 42:weakly_referenced

uintptr_t weakly_referenced : 1;

含义: 这个对象是否被弱引用__weak 指针)指向过。

为什么需要这一位?

  • 对象 dealloc 时,如果有弱引用指向它,需要去 SideTable(全局散列表)里把那些弱引用都清零(避免 dangling pointer)
  • 用这一位快速判断:weakly_referenced == 0 → 跳过 SideTable 查找,直接释放

bit 43:unused

uintptr_t unused : 1;

含义: 目前未使用,预留位。


bit 44:has_sidetable_rc

uintptr_t has_sidetable_rc : 1;

含义: 引用计数是否溢出到了 SideTable。

正常情况下,引用计数存在 isa 的 extra_rc 里(19位,最大能存 2^19 - 1 = 524287)。如果引用计数超过了这个值,has_sidetable_rc = 1,多出来的部分存在全局的 SideTable 里。


bit 45~63:extra_rc(19位)

uintptr_t extra_rc : 19;

含义: 存储对象的引用计数 - 1

为什么是减 1?因为对象存活时引用计数至少为 1,存 0 代表计数是 1,节省一点空间。

实际的引用计数 = extra_rc + 1(如果 has_sidetable_rc == 0)


四、如何取出 isa 里的类指针(实际代码)

// objc-object.h
inline Class objc_object::getIsa() {
    if (fastpath(!isTaggedPointer())) {
        return ISA();
    }
    // ... TaggedPointer 的特殊处理
}

inline Class objc_object::ISA(bool authenticated) {
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    // 某些架构用索引
    ...
#else
    // ARM64 主路径:取 shiftcls 位,左移3位还原地址
    return (Class)(isa.bits & ISA_MASK);
#endif
}

其中 ISA_MASK 在 ARM64 是 0x0000000ffffffff8ULL,作用就是取 bit 3~35。


五、元类(Metaclass)是什么?

这是 OC 最难理解的概念之一,但其实逻辑非常自洽。

问题的由来

在 OC 里,"一切皆对象"——包括类本身也是对象。

[NSString class]  // 这返回的是一个对象
[NSString stringWithString:@"hello"]  // 这是给"类对象"发消息

既然类也是对象,那类对象的 isa 指向哪里?

答案就是:元类(metaclass)

元类的定义

元类是"类的类"。它存储的是类方法+ 方法),就像普通类存储实例方法(- 方法)一样。

对比:类 vs 元类

普通类(Class) 元类(Metaclass)
本质 objc_class 结构体 也是 objc_class 结构体
方法列表里存的 实例方法(- 类方法(+
isa 指向 元类 根元类(NSObject 的元类)
superclass 指向 父类 父类的元类

六、完整的 isa + 继承链图

这是 OC 里最经典的一张图,一定要理解它:

                 isa                  isa               isa
实例对象(inst) --------→ 类(MyClass) --------→ 元类(Meta-MyClass) ──→ 根元类
                                                                          │
              superclass              superclass             superclass    │ isa(自指)
         MyClass ───────→ NSObject     Meta-MyClass ──────→ Meta-NSObject─┘
                               │                                  │
                               │ superclass = nil                 │ superclass
                               ↓                                  ↓
                             (nil)                             NSObject(不是元类!)

用文字描述:

  1. 实例对象.isaMyClass(类)
  2. MyClass.isaMeta-MyClass(元类)
  3. Meta-MyClass.isaMeta-NSObject(根元类)
  4. Meta-NSObject.isaMeta-NSObject自指!根元类的 isa 指向自己

继承链:

  1. MyClass.superclassNSObject
  2. NSObject.superclassnil
  3. Meta-MyClass.superclassMeta-NSObject(元类也有继承链)
  4. Meta-NSObject.superclassNSObject元类继承链的终点是 NSObject 类,不是 nil!

七、为什么元类的继承链终点是 NSObject?

这个设计让你可以在任何类方法里调用 NSObject 的实例方法(比如 respondsToSelector:)。

// 这为什么能工作?
[MyClass respondsToSelector:@selector(doSomething)];

+respondsToSelector: 是 NSObject 的实例方法(- 方法),存在 NSObject 类里。
当你给 MyClass 发这个消息,runtime 查找路径:

Meta-MyClass(没有)
    → Meta-NSObject(没有)
        → NSObject(在这找到了!)

因为 Meta-NSObject.superclass = NSObject,所以元类链最终能访问到 NSObject 的实例方法。优雅!


八、TaggedPointer:特殊的对象

不是所有"对象"都是真正的对象(有 isa 的结构体)。

什么是 TaggedPointer?

对于一些小值对象(比如 NSNumberNSDate、小字符串),苹果直接把值编码进指针本身,不分配堆内存。

NSNumber *num = @42;
// 在 64 位下,这个指针可能长这样:
// 0xb000000000000162  (不是真实的堆地址!)
// 最高位 1 = TaggedPointer 标志
// 低位存了 42 这个值

判断是否是 TaggedPointer

static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
// ARM64: _OBJC_TAG_MASK = (1UL<<63),最高位为1就是 TaggedPointer

TaggedPointer 的好处

  • 不需要堆分配:直接在指针里存值,alloc 时不走 malloc
  • 不需要引用计数:也不需要 release,直接丢弃
  • 更快:少了内存分配和释放的开销

九、SideTable:引用计数和弱引用的大本营

当 isa 的 extra_rc 不够用,或者有弱引用时,数据存在 SideTable 里。

struct SideTable {
    spinlock_t slock;           // 自旋锁,保证线程安全
    RefcountMap refcnts;        // 引用计数表(散列表)
    weak_table_t weak_table;    // 弱引用表
};

全局有 8 个(或 64 个)SideTable,通过对象地址取模来分配,减少锁竞争。

weak_table_t 弱引用表

struct weak_table_t {
    weak_entry_t *weak_entries;  // 弱引用条目数组
    size_t num_entries;
    ...
};

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;  // 被指向的对象
    // 指向该对象的所有 __weak 指针地址的集合
    union {
        struct { weak_referrer_t *referrers; ... };
        struct { weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; };
    };
};

__weak 置零的过程

对象 dealloc
    ↓
检查 isa.weakly_referenced
    ↓(== 1)
去 SideTable 找 weak_entry_t
    ↓
遍历所有指向该对象的 __weak 指针
    ↓
全部置 nil
    ↓
从 weak_table 删除该条目

这就是为什么 __weak 指针在对象释放后自动变成 nil,而不会变成野指针——runtime 帮你清零了。


十、小结

概念 本质 存在哪里
isa 64位 union,含类指针+引用计数+标志位 每个对象的第一个字段
元类 存类方法的 objc_class 全局静态区
TaggedPointer 值直接编码进指针,无堆对象 栈/寄存器
extra_rc 引用计数(-1)的快速存储 isa 的高19位
SideTable 溢出引用计数 + 弱引用表 全局散列表

下一篇:延伸问题 Q&A——消息发送、方法查找、Swizzle、dealloc 全流程等

objc_class 结构体逐行解析

作者 泉木
2026年3月16日 20:05

前言

objc_class 开始,是因为它是整个 Runtime 的基础数据结构。Runtime 管的事很多——消息发送、方法查找、内存管理、Category 加载……但这些行为最终都要落在"类长什么样"上面。搞清楚 objc_class,后面的东西才能接得上。

一、源码全貌(先看完整结构)

下面是从 Apple 开源的 objc4 里提取的核心结构体,我做了适度精简,保留所有关键字段。

建议先整体扫一遍,有个印象,后面逐个解释。

// ============================================================
// 文件:objc-runtime-new.h(objc4-818.2)
// 源码地址:https://opensource.apple.com/source/objc4/
// ============================================================

// -------------------- 1. objc_object --------------------
// 所有 OC 对象的基类,只有一个字段:isa
struct objc_object {
private:
    isa_t isa;  // 64位,包含类指针+引用计数+标志位

public:
    Class ISA(bool authenticated = false);
    Class getIsa();
    // ... 省略其他方法
};


// -------------------- 2. objc_class --------------------
// 这就是"类"的底层结构,继承自 objc_object
struct objc_class : objc_object {
    // 注意:isa 字段继承自 objc_object,这里不重复写
    
    Class superclass;           // 父类指针
    cache_t cache;              // 方法缓存(哈希表)
    class_data_bits_t bits;     // 指向 class_rw_t 的指针+标志位

    // 取出真正的数据
    class_rw_t *data() const {
        return bits.data();
    }
    // ... 省略其他方法
};


// -------------------- 3. class_data_bits_t --------------------
// 这是 objc_class.bits 的类型,用来存储指向 class_rw_t 的指针 + 几个标志位
struct class_data_bits_t {
private:
    uintptr_t bits;   // 就是一个 64 位整数,低位藏标志位,高位存指针

public:
    // 用掩码取出真正的 class_rw_t 指针
    class_rw_t *data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    // 各种标志位的读取方法
    bool isSwiftLegacy() const {
        return getBit(FAST_IS_SWIFT_LEGACY);
    }
    bool isSwiftStable() const {
        return getBit(FAST_IS_SWIFT_STABLE);
    }
    // ... 其他方法
};

// ARM64 下的掩码和标志位定义:
// FAST_DATA_MASK      = 0x00007ffffffffff8UL  (取 bit 3~46,即真正的指针)
// FAST_IS_SWIFT_LEGACY = 1 << 0  (bit 0: 是否是旧版 Swift 类)
// FAST_IS_SWIFT_STABLE = 1 << 1  (bit 1: 是否是新版 Swift 类)
// FAST_HAS_DEFAULT_RR  = 1 << 2  (bit 2: 是否有默认的 retain/release)


// -------------------- 4. cache_t --------------------
// 方法缓存,加速方法查找
struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  // 桶数组地址
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;   // 桶数量-1(用于哈希取模)
            uint16_t                   _flags;
            uint16_t                   _occupied;    // 已使用的桶数
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
public:
    // ... 省略查找、插入方法
};

// 单个缓存桶
struct bucket_t {
private:
    explicit_atomic<SEL> _sel;       // 方法名(选择子)
    explicit_atomic<uintptr_t> _imp; // 函数指针(方法实现地址)
};


// -------------------- 5. class_rw_t --------------------
// 运行时可读写数据(Category 方法会合并到这里)
struct class_rw_t {
    uint32_t flags;
    uint16_t witness;
    uint16_t index;

    explicit_atomic<uintptr_t> ro_or_rw_ext;  // 指向 class_ro_t 或扩展数据

    Class firstSubclass;       // 第一个子类
    Class nextSiblingClass;    // 兄弟类(形成链表)

    // 获取方法/属性/协议列表
    const method_array_t methods() const;
    const property_array_t properties() const;
    const protocol_array_t protocols() const;

    // 获取只读数据
    const class_ro_t *ro() const;
};


// -------------------- 6. class_ro_t --------------------
// 编译期只读数据(源码里写死的方法、变量、属性)
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;      // 实例变量起始偏移
    uint32_t instanceSize;       // sizeof(实例),对象占多少字节

    const uint8_t * ivarLayout;  // 强引用 ivar 的内存布局

    const char * name;           // 类名字符串,如 "NSString"
    
    WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;  // 方法列表
    protocol_list_t * baseProtocols;     // 协议列表
    const ivar_list_t * ivars;           // 实例变量列表
    
    const uint8_t * weakIvarLayout;      // 弱引用 ivar 的内存布局
    property_list_t *baseProperties;     // 属性列表
};


// -------------------- 7. method_t --------------------
// 单个方法的描述
struct method_t {
    SEL name;              // 方法名(选择子),本质是 const char *
    const char *types;     // 类型编码,如 "v16@0:8"
    IMP imp;               // 函数指针(真正的代码地址)
};


// -------------------- 8. ivar_t --------------------
// 单个实例变量的描述
struct ivar_t {
    int32_t *offset;       // 偏移量指针(Non-Fragile ABI 用)
    const char *name;      // 变量名,如 "_name"
    const char *type;      // 类型编码,如 "@"NSString""
    uint32_t alignment_raw;// 对齐方式
    uint32_t size;         // 占多少字节
};


// -------------------- 9. isa_t --------------------
// isa 的真正定义(union,64位里塞了很多信息)
union isa_t {
    uintptr_t bits;        // 原始64位值

    // ARM64 位域展开(iOS 真机):
    struct {
        uintptr_t nonpointer        : 1;   // bit 0:  是否是优化过的 isa
        uintptr_t has_assoc         : 1;   // bit 1:  有关联对象?
        uintptr_t has_cxx_dtor      : 1;   // bit 2:  有 C++ 析构?
        uintptr_t shiftcls          : 33;  // bit 3-35:  类指针(右移3位存储)
        uintptr_t magic             : 6;   // bit 36-41: 固定值 0x1a,调试用
        uintptr_t weakly_referenced : 1;   // bit 42: 被弱引用?
        uintptr_t unused            : 1;   // bit 43: 未使用
        uintptr_t has_sidetable_rc  : 1;   // bit 44: 引用计数溢出到 SideTable?
        uintptr_t extra_rc          : 19;  // bit 45-63: 引用计数-1
    };
};

二、结构关系图

objc_class(一个类在内存里的样子)
┌─────────────────────────────────────┐
│  isa (继承自 objc_object)           │ ← isa_t union,64位
├─────────────────────────────────────┤
│  superclass                         │ ← 指向父类的 objc_class
├─────────────────────────────────────┤
│  cache                              │ ← cache_t 结构体
│    └── bucket_t[] 数组              │     每个桶存 { SEL, IMP }
├─────────────────────────────────────┤
│  bits                               │ ← class_data_bits_t(指针+标志位)
│    └── data() ───────────────────────────→ class_rw_t(运行时可写)
│                                     │        ├── methods()
│                                     │        ├── properties()
│                                     │        ├── protocols()
│                                     │        └── ro() ────────→ class_ro_t(只读)
│                                     │                             ├── name
│                                     │                             ├── baseMethods
│                                     │                             │     └── method_t[]
│                                     │                             ├── ivars
│                                     │                             │     └── ivar_t[]
│                                     │                             └── baseProperties
└─────────────────────────────────────┘

三、逐结构体解析

接下来按源码出现的顺序,逐个讲解每个结构体、每个字段的含义。


3.1 objc_object —— 所有对象的祖宗

struct objc_object {
private:
    isa_t isa;
};

这是什么?

这是 OC 里所有对象的底层表示。不管是 NSStringUIView、还是你自定义的 MyClass 实例,底层都是 objc_object

字段解析

字段 类型 含义
isa isa_t "is a" 的缩写,标识"这个对象是什么类型"。是一个 64 位的 union,里面藏了类指针 + 引用计数 + 各种标志位。isa_t 的详细结构会在第二篇展开讲解。

为什么只有一个字段?

因为 objc_object最小公共祖先。每个对象只需要知道"我是什么类型"(isa),其他的成员变量由具体的类定义,紧跟在 isa 后面存储。

内存布局示意

一个 MyClass 实例的内存:
┌────────────────┐ ← 对象起始地址
│     isa        │   8 字节(objc_object 的字段)
├────────────────┤
│    _name       │   8 字节(MyClass 自己的 ivar)
├────────────────┤
│    _age        │   4 字节(MyClass 自己的 ivar)
└────────────────┘

3.2 objc_class —— 类的完整定义

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;

    class_rw_t *data() const {
        return bits.data();
    }
};

这是什么?

这是 OC 里的底层表示。每个 @interface MyClass 在运行时都对应一个 objc_class 结构体实例。

注意它继承自 objc_object,所以"类也是对象"——类对象有自己的 isa(指向元类)。

字段逐个解析

字段 类型 含义
isa isa_t(继承来的) 类对象的 isa 指向它的元类(metaclass)。isa_t 的详细结构见第二篇。
superclass Class 父类指针。Classobjc_class * 的 typedef,即指向另一个 objc_class 的指针。NSObject 的 superclass 是 nil。;
cache cache_t 方法缓存,哈希表结构。最近调用的方法会缓存在这里,加速后续调用。
bits class_data_bits_t 一个 64 位整数,低 3 位是标志位,高位是 class_rw_t 指针

Class 是什么类型?

// objc.h
typedef struct objc_class *Class;

Class 就是 objc_class * 的别名,一个指向类对象的指针。你代码里写的所有 Class 都只是这个指针,没有额外结构:

Class cls = [MyClass class];       // 拿到 MyClass 的 objc_class * 指针
Class superCls = [cls superclass]; // 拿到父类的 objc_class * 指针

同理,id 也是:

typedef struct objc_object *id;    // id = objc_object *,指向任意实例对象

superclass 有什么用?

实现继承。当在当前类找不到方法时,runtime 会沿着 superclass 链往上找。

调用 [myObj doSomething]
    ↓
在 MyClass 的方法列表里找
    ↓ 找不到
通过 superclass 到 NSObject 里找
    ↓ 还找不到
触发消息转发

3.3 class_data_bits_t —— 指针 + 标志位的混合体

struct class_data_bits_t {
private:
    uintptr_t bits;   // 64 位整数

public:
    class_rw_t *data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

这是什么?

就是 objc_class.bits 的类型。它不是简单的指针,而是把 class_rw_t 指针几个标志位 打包进同一个 64 位整数里。

为什么能这样做?

因为 class_rw_t 在内存里是 8 字节对齐的,所以它的地址的低 3 位永远是 000。苹果就把这 3 位拿来存标志位,不浪费。

64 位的布局

class_data_bits_t.bits(64位)

 63                                3  2  1  0
┌────────────────────────────────┬──┬──┬──┐
│     class_rw_t 指针 (bit 3~63) │ 210│
└────────────────────────────────┴──┴──┴──┘
                                   │  │  │
                                   │  │  └─ FAST_IS_SWIFT_LEGACY (是旧版Swift类?)
                                   │  └──── FAST_IS_SWIFT_STABLE (是新版Swift类?)
                                   └─────── FAST_HAS_DEFAULT_RR  (有默认retain/release?)

取指针的掩码

// ARM64
#define FAST_DATA_MASK 0x00007ffffffffff8UL

// 二进制:...11111111111111111111111111111111111111000
// 作用:与运算后,低 3 位清零,剩下的就是真正的 class_rw_t 地址

data() 方法做了什么?

class_rw_t *data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
    // bits & 掩码 → 把低 3 位标志位清掉 → 得到纯净的 class_rw_t 指针
}

一句话总结

class_data_bits_tisa_t 的设计思路一样——充分利用内存对齐带来的空闲位,一个 64 位整数里塞多种信息,省内存


3.4 cache_t —— 方法缓存

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t> _maybeMask;
            uint16_t _flags;
            uint16_t _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
};

struct bucket_t {
private:
    explicit_atomic<SEL> _sel;
    explicit_atomic<uintptr_t> _imp;
};

为什么需要缓存?

每次调用方法都去 class_rw_t 的方法列表里遍历查找,太慢了。cache_t 是一个哈希表,把最近调用过的方法缓存起来。

字段解析

字段 含义
_bucketsAndMaybeMask 哈希桶数组的起始地址
_maybeMask 桶数量 - 1,用于哈希取模(hash & mask
_occupied 当前已使用的桶数量

bucket_t 是什么?

单个缓存条目,存储 SEL(方法名)和 IMP(函数指针)的映射。

字段 类型 含义
_sel SEL 方法选择子(方法名),如 @selector(viewDidLoad)
_imp uintptr_t 方法实现的函数地址

查找流程

[obj doSomething]
    ↓
计算 @selector(doSomething) 的哈希值
    ↓
hash & _maybeMask → 得到桶的索引
    ↓
取出 bucket_t,比较 _sel 是否等于 @selector(doSomething)
    ↓
相等 → 直接调用 _imp,结束(命中缓存,极快)
不相等 → 去 class_rw_t 里慢速查找

缓存什么时候会失效?

  • 调用 method_exchangeImplementations(Method Swizzle)后
  • 动态添加方法后
  • 类第一次加载时

失效时 runtime 会调用 flushCaches() 清空缓存。


3.5 class_rw_t —— 运行时可读写数据

struct class_rw_t {
    uint32_t flags;
    uint16_t witness;
    uint16_t index;

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

    const method_array_t methods() const;
    const property_array_t properties() const;
    const protocol_array_t protocols() const;
    const class_ro_t *ro() const;
};

这是什么?

rw = read-write(可读写)。这里存放运行时可以修改的数据,比如 Category 添加的方法会合并到这里。

字段解析

字段 含义
flags 各种标志位(是否已初始化、是否有 C++ 构造函数等)
ro_or_rw_ext 指向 class_ro_t(只读数据),或扩展数据
firstSubclass 指向第一个子类,形成子类链表
nextSiblingClass 指向下一个兄弟类(同一个父类的其他子类)

获取方法/属性/协议

const method_array_t methods() const;     // 返回方法列表(含 Category 方法)
const property_array_t properties() const; // 返回属性列表
const protocol_array_t protocols() const;  // 返回协议列表

这些方法返回的是合并后的列表——源码里写的 + Category 加进来的。

ro() 方法

返回 class_ro_t 指针,取出编译期确定的只读数据。


3.6 class_ro_t —— 编译期只读数据

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;

    const uint8_t * ivarLayout;
    const char * name;
    
    WrappedPtr<method_list_t, ...> baseMethods;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

这是什么?

ro = read-only(只读)。这里存放编译时就确定的数据,运行时不能修改。

字段逐个解析

字段 类型 含义
flags uint32_t 标志位
instanceStart uint32_t 实例变量在对象内存中的起始偏移(通常是 8,跳过 isa)
instanceSize uint32_t 一个实例对象占多少字节(sizeof
ivarLayout const uint8_t * 描述哪些 ivar 是强引用(ARC 用)
name const char * 类名字符串,如 "UIViewController"
baseMethods method_list_t * 源码里定义的方法列表(不含 Category)
baseProtocols protocol_list_t * 源码里遵循的协议列表
ivars ivar_list_t * 实例变量列表
weakIvarLayout const uint8_t * 描述哪些 ivar 是弱引用
baseProperties property_list_t * 源码里定义的属性列表

class_ro_t vs class_rw_t 对比

class_ro_t class_rw_t
全称 read-only read-write
什么时候确定 编译期(写进 Mach-O 二进制文件) 运行时(启动时构造)
能修改吗 ❌ 不能 ✅ 能
存什么 源码里写死的方法、属性、变量 动态添加的方法、Category 合并的方法

为什么要分两层?

因为 Category 是运行时加载的。编译期不知道会有哪些 Category,所以:

  1. 编译期:把源码里写的方法存进 class_ro_t
  2. 运行时:遍历所有 Category,把它们的方法合并class_rw_t

查找方法时,先查 class_rw_t(含 Category),它内部会访问 class_ro_t


3.7 method_t —— 单个方法

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

字段解析

字段 类型 含义 例子
name SEL 方法选择子(方法名) @selector(viewDidLoad)
types const char * 类型编码(返回值+参数的类型) "v16@0:8"
imp IMP 函数指针,指向方法的真正实现 0x100001234(代码段地址)

SEL 是什么?

typedef struct objc_selector *SEL;

本质是一个唯一化的 C 字符串。同名方法在整个程序里 SEL 值相同(指针相等),所以比较方法名只需要比较指针,极快。

SEL sel1 = @selector(doSomething);
SEL sel2 = @selector(doSomething);
// sel1 == sel2(指针相等,不是字符串比较)

IMP 是什么?

typedef void (*IMP)(id, SEL, ...);

函数指针,前两个参数固定是:

  • id self:消息接收者
  • SEL _cmd:方法选择子

这解释了为什么 OC 方法里能直接用 self_cmd——它们是函数的隐藏参数。

// 你写的:
- (void)doSomething {
    NSLog(@"%@", self);
}

// 编译器眼里的:
void doSomething(id self, SEL _cmd) {
    NSLog(@"%@", self);
}

types 字符串怎么读?

- (NSString *)nameWithPrefix:(NSString *)prefix 为例,types 是 @24@0:8@16

@    → 返回值是 id(对象)
24   → 所有参数总共占 24 字节
@    → 第1个参数是 id(self)
0    → 从第 0 字节开始
:    → 第2个参数是 SEL(_cmd)
8    → 从第 8 字节开始
@    → 第3个参数是 id(prefix)
16   → 从第 16 字节开始

这套编码叫 Type Encoding,runtime 靠它做方法签名校验。


3.8 ivar_t —— 单个实例变量

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

字段解析

字段 类型 含义 例子
offset int32_t * 偏移量的指针(不是值!) 指向存储偏移量的内存
name const char * 变量名 "_name"
type const char * 类型编码 "@"NSString""
alignment_raw uint32_t 内存对齐方式 通常是 3(2^3 = 8 字节对齐)
size uint32_t 占多少字节 指针占 8 字节

为什么 offset 是指针而不是值?

这是 Non-Fragile ABI(非脆弱 ABI)的设计。

假设父类 NSObject 有 8 字节的 isa,子类 MyClass_name 变量在 offset 8。

如果 Apple 在新系统里给 NSObject 加了一个成员变量(变成 16 字节),按老 ABI,MyClass_name 还在 offset 8,就会和 NSObject 新增的变量重叠——程序崩溃。

Non-Fragile ABI 的解决方案:

  1. offset 是指针,不是值
  2. App 启动时,runtime 检查父类大小是否变化
  3. 如果变化了,自动调整所有子类 ivar 的 offset 值
  4. 子类不需要重新编译
旧系统:NSObject 8字节,MyClass._name 在 offset 8
    ↓ Apple 升级系统
新系统:NSObject 16字节
    ↓ runtime 自动修正
MyClass._name 的 offset 从 8 改成 16

访问 ivar 的过程

// 伪代码
id value = *(id *)((char *)obj + *ivar->offset);
// 1. 取出 offset 指针指向的偏移值
// 2. 对象地址 + 偏移值 = ivar 的内存地址
// 3. 解引用得到 ivar 的值

3.9 isa_t —— 64 位里藏了很多东西

union isa_t {
    uintptr_t bits;

    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33;
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t unused            : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
};

为什么不直接存指针?

在 64 位系统上,指针只需要约 40 位就能表示所有内存地址。剩下的位"浪费"了,苹果就把引用计数和各种标志位塞进去,省内存。

这叫 Non-pointer ISA(优化过的 isa)。

每一位的含义

位域 位数 含义
nonpointer 1 是否是优化过的 isa(现代设备都是 1)
has_assoc 1 对象是否有关联对象(objc_setAssociatedObject
has_cxx_dtor 1 是否有 C++ 析构函数或 ARC 的 .cxx_destruct
shiftcls 33 类指针(右移 3 位存储,取时左移还原)
magic 6 固定值 0x1a,调试用(值不对说明内存被踩了)
weakly_referenced 1 是否被 __weak 指针指向过
unused 1 未使用,预留
has_sidetable_rc 1 引用计数是否溢出到 SideTable
extra_rc 19 存储引用计数 - 1(最大 2^19 - 1 = 524287)

如何取出类指针?

Class cls = (Class)(isa.bits & ISA_MASK);
// ISA_MASK = 0x0000000ffffffff8ULL
// 掩码取出 bit 3~35,然后隐含左移还原

四、完整内存布局示意

把所有结构体串起来,一个类在内存里长这样:

objc_class 实例(代表 MyClass 这个类)
┌─────────────────────────────────────────────────────┐
│  isa (64位 isa_t)                                   │ → 指向 Meta-MyClass(元类)
├─────────────────────────────────────────────────────┤
│  superclass (8字节)                                 │ → 指向 NSObject
├─────────────────────────────────────────────────────┤
│  cache (cache_t)                                    │
│    _bucketsAndMaybeMask → [ bucket_t, bucket_t... ] │ 每个桶: { SEL, IMP }
│    _maybeMask = N-1                                 │
│    _occupied = 已用桶数                              │
├─────────────────────────────────────────────────────┤
│  bits (class_data_bits_t)                           │ ← 低3位是标志位,高位是指针
│    data() ──────────────────────────────────────────│──→ class_rw_t
│                                                     │      ├── methods()   → [method_t, ...]
│                                                     │      ├── properties()→ [property_t, ...]
│                                                     │      ├── protocols() → [protocol_t, ...]
│                                                     │      └── ro() ───────→ class_ro_t
│                                                     │              ├── name = "MyClass"
│                                                     │              ├── instanceSize = 24
│                                                     │              ├── baseMethods
│                                                     │              │     ├── method_t { SEL, types, IMP }
│                                                     │              │     └── method_t { ... }
│                                                     │              └── ivars
│                                                     │                    ├── ivar_t { offset*, "_name", "@", 3, 8 }
│                                                     │                    └── ivar_t { offset*, "_age",  "i", 2, 4 }
└─────────────────────────────────────────────────────┘

五、小结

结构体 可否运行时修改 存放什么
objc_class 不直接改 类的容器,持有 superclass/cache/bits
isa_t 部分可改(引用计数位) 类指针 + 引用计数 + 标志位,全塞在 64 位里
class_data_bits_t 不直接改 class_rw_t 指针 + 3 个标志位,又一个"指针+标志"混合体
cache_t 是(每次调用方法后更新) 最近调用的方法 SEL → IMP 映射
class_rw_t 运行时合并后的方法、属性、协议
class_ro_t 编译期确定的方法、变量、属性,写死在二进制里
method_t IMP 可以换(Swizzle) 一个方法的名字、类型编码、实现地址
ivar_t offset 可改(Non-Fragile ABI) 一个实例变量的名字、类型、偏移量

下一篇:isa 指针深度解析、元类体系、完整继承链图

Swift 从入门到精通-终篇

作者 泉木
2026年3月14日 14:21

第14章:SwiftUI 进阶

14.1 动画

SwiftUI 动画非常简单,只需添加 .animation


struct AnimationView: View {

    @State private var isExpanded = false

    

    var body: some View {

        VStack {

            RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)

                .fill(isExpanded ? Color.blue : Color.red)

                .frame(

                    width: isExpanded ? 300 : 100,

                    height: isExpanded ? 300 : 100

                )

                .animation(.spring(response: 0.3, dampingFraction: 0.5), value: isExpanded)

            

            Button("切换") {

                isExpanded.toggle()

            }

        }

    }

}

**动画类型: **

  • .default - 默认动画

  • .linear - 线性

  • .easeIn / .easeOut / .easeInOut - 缓动

  • .spring() - 弹性动画

  • .interactiveSpring() - 交互式弹性

14.2 手势


struct GestureView: View {

    @State private var offset: CGSize = .zero

    @State private var scale: CGFloat = 1.0

    @State private var rotation: Angle = .zero

    

    var body: some View {

        Image(systemName: "star.fill")

            .font(.system(size: 100))

            .foregroundColor(.yellow)

            .offset(offset)

            .scaleEffect(scale)

            .rotationEffect(rotation)

            .gesture(

                DragGesture()

                    .onChanged { gesture in

                        offset = gesture.translation

                    }

                    .onEnded { _ in

                        withAnimation {

                            offset = .zero

                        }

                    }

            )

            .gesture(

                MagnificationGesture()

                    .onChanged { scale = $0 }

            )

            .gesture(

                RotationGesture()

                    .onChanged { rotation = $0 }

            )

    }

}

14.3 数据持久化


import SwiftUI

  


struct TodoItem: Identifiable, Codable {

    let id: UUID

    var title: String

    var isCompleted: Bool

    var createdAt: Date

}

  


class TodoStore: ObservableObject {

    @Published var todos: [TodoItem] = []

    

    private let saveKey = "todos"

    

    init() {

        load()

    }

    

    func add(_ title: String) {

        let todo = TodoItem(

            id: UUID(),

            title: title,

            isCompleted: false,

            createdAt: Date()

        )

        todos.append(todo)

        save()

    }

    

    func toggle(_ todo: TodoItem) {

        if let index = todos.firstIndex(where: { $0.id == todo.id }) {

            todos[index].isCompleted.toggle()

            save()

        }

    }

    

    func delete(at offsets: IndexSet) {

        todos.remove(atOffsets: offsets)

        save()

    }

    

    // UserDefaults 存储

    private func save() {

        if let encoded = try? JSONEncoder().encode(todos) {

            UserDefaults.standard.set(encoded, forKey: saveKey)

        }

    }

    

    private func load() {

        if let data = UserDefaults.standard.data(forKey: saveKey),

           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {

            todos = decoded

        }

    }

}


第15章:并发编程 - async/await

15.1 为什么需要并发?

想象一个场景:

  • 用户点击按钮

  • App 需要从网络加载图片

  • 如果用同步方式,界面会卡住,直到图片加载完成

  • 用户看到"假死",体验极差

**并发让 App 能同时做多件事: **

  • 主线程:响应用户操作、更新界面

  • 后台线程:网络请求、文件读写、复杂计算

15.2 旧的方式 - GCD


// 旧的写法(仍然有效,但不推荐新代码使用)

DispatchQueue.global().async {

    // 后台线程执行耗时操作

    let data = try! Data(contentsOf: url)

    

    DispatchQueue.main.async {

        // 主线程更新 UI

        imageView.image = UIImage(data: data)

    }

}

**问题: **

  • 嵌套层级深(回调地狱)

  • 错误处理麻烦

  • 代码难以阅读和维护

15.3 async/await - 新的方式


// 定义异步函数

func fetchImage(from url: URL) async throws -> UIImage {

    let (data, _) = try await URLSession.shared.data(from: url)

    guard let image = UIImage(data: data) else {

        throw ImageError.invalidData

    }

    return image

}

  


// 调用异步函数

func loadImage() async {

    do {

        let image = try await fetchImage(from: imageURL)

        imageView.image = image

    } catch {

        print("加载失败: \(error)")

    }

}

**代码看起来像同步的,实际上是异步执行的! **

15.4 在 SwiftUI 中使用


struct AsyncImageView: View {

    @State private var image: UIImage?

    @State private var isLoading = false

    

    let url: URL

    

    var body: some View {

        Group {

            if let image = image {

                Image(uiImage: image)

                    .resizable()

                    .aspectRatio(contentMode: .fit)

            } else if isLoading {

                ProgressView()

            } else {

                Image(systemName: "photo")

                    .font(.largeTitle)

                    .foregroundColor(.gray)

            }

        }

        .onAppear {

            loadImage()

        }

    }

    

    private func loadImage() {

        isLoading = true

        

        Task {

            do {

                let (data, _) = try await URLSession.shared.data(from: url)

                await MainActor.run {

                    self.image = UIImage(data: data)

                    self.isLoading = false

                }

            } catch {

                await MainActor.run {

                    self.isLoading = false

                }

            }

        }

    }

}

**Task: **创建异步任务的环境

**MainActor: **确保代码在主线程执行(用于 UI 更新)

15.5 结构化并发


// 顺序执行(慢)

func fetchSequentially() async throws -> [User] {

    let user1 = try await fetchUser(id: 1)

    let user2 = try await fetchUser(id: 2)

    let user3 = try await fetchUser(id: 3)

    return [user1, user2, user3]

}

  


// 并行执行(快!)

func fetchConcurrently() async throws -> [User] {

    async let user1 = fetchUser(id: 1)

    async let user2 = fetchUser(id: 2)

    async let user3 = fetchUser(id: 3)

    return try await [user1, user2, user3]

}

  


// 使用 TaskGroup 处理动态数量的任务

func fetchUsers(ids: [Int]) async throws -> [User] {

    try await withThrowingTaskGroup(of: User.self) { group in

        for id in ids {

            group.addTask {

                try await fetchUser(id: id)

            }

        }

        

        var users: [User] = []

        for try await user in group {

            users.append(user)

        }

        return users

    }

}

**async let: **启动并行任务

**withTaskGroup: **管理一组动态任务

15.6 异步序列


// 异步序列

let stream = AsyncStream<Int> { continuation in

    Task {

        for i in 1...5 {

            try? await Task.sleep(for: .seconds(1))

            continuation.yield(i)

        }

        continuation.finish()

    }

}

  


// 遍历

for await number in stream {

    print("收到: \(number)")

}


第16章:Actor 与数据竞争防护

16.1 数据竞争问题


class UnsafeCounter {

    var count = 0

    

    func increment() {

        count += 1  // 非线程安全!

    }

}

  


let counter = UnsafeCounter()

  


// 从多个线程同时增加

DispatchQueue.concurrentPerform(iterations: 1000) { _ in

    counter.increment()

}

  


// 结果可能小于 1000!

print(counter.count)

原因: count += 1 实际上分三步:

  1. 读取 count 的值

  2. 加 1

  3. 写回 count

如果两个线程同时执行,可能都读到 5,都变成 6,结果只增加了 1。

16.2 Actor - 隔离状态


actor SafeCounter {

    private var count = 0

    

    func increment() {

        count += 1  // 线程安全!

    }

    

    func getCount() -> Int {

        return count

    }

}

  


let counter = SafeCounter()

  


await withTaskGroup(of: Void.self) { group in

    for _ in 0..<1000 {

        group.addTask {

            await counter.increment()

        }

    }

}

  


let finalCount = await counter.getCount()

print(finalCount)  // 1000

**Actor 的特点: **

  • 同一时间只有一个线程能访问 actor 内部

  • 自动防止数据竞争

  • 访问 actor 的属性和方法需要 await

16.3 MainActor - 主线程 Actor


@MainActor

class ViewModel: ObservableObject {

    @Published var items: [Item] = []

    

    func load() async {

        items = await fetchItems()  // await 切到后台

        // 自动回到主线程

    }

}

  


// 或者只标记某个方法

class ViewModel2: ObservableObject {

    @Published var items: [Item] = []

    

    @MainActor

    func updateUI() {

        // 确保在主线程执行

        items.append(newItem)

    }

}

16.4 Sendable 协议


// 安全的值类型,可以跨 actor 传递

struct UserData: Sendable {

    let id: UUID

    let name: String

}

  


// 类也可以遵循 Sendable,但需要是 final 且属性都是 Sendable

final class SafeClass: Sendable {

    let value: Int

    init(value: Int) {

        self.value = value

    }

}

**Swift 6 会强制检查: **如果类型不是 Sendable,不能跨 actor 传递。


第一次做分享,期待更优秀的你~

Swift 从入门到精通-第四篇

作者 泉木
2026年3月14日 14:17

第13章:SwiftUI 基础 - 声明式 UI

13.1 SwiftUI 是什么?

SwiftUI 是 Apple 在 2019 年推出的声明式 UI 框架,用来替代 UIKit。它的核心理念是:

**告诉 SwiftUI "你想要什么界面",而不是 "怎么创建界面"。 **

对比:

  • **UIKit(命令式) **:创建视图 → 添加到父视图 → 设置约束 → 手动更新

  • **SwiftUI(声明式) **:描述界面状态,SwiftUI 自动处理更新

13.2 你的第一个 SwiftUI 视图

在 Xcode 中创建 SwiftUI 项目,替换 ContentView.swift:


import SwiftUI

  


struct ContentView: View {

    var body: some View {

        Text("Hello, World!")

            .font(.largeTitle)

            .foregroundColor(.blue)

            .padding()

    }

}

  


#Preview {

    ContentView()

}

**关键概念: **

  • View 协议:所有 UI 元素都遵循这个协议

  • body:计算属性,返回视图层级

  • 修饰符(modifier):用 . 链式调用,修改视图样式

13.3 状态管理 - @State

SwiftUI 中,视图是函数:输入相同的状态,输出相同的界面。


struct CounterView: View {

    // @State 标记可变状态

    @State private var count = 0

    

    var body: some View {

        VStack(spacing: 20) {

            Text("Count: \(count)")

                .font(.largeTitle)

            

            Button("Increment") {

                count += 1  // 修改状态,自动刷新界面

            }

            .buttonStyle(.borderedProminent)

        }

    }

}

** @State 的特点: **

  • 用于视图内部状态

  • SwiftUI 自动管理存储

  • 值改变时自动重绘视图

13.4 布局系统 - Stacks


struct LayoutDemoView: View {

    var body: some View {

        VStack(spacing: 20) {      // 垂直堆栈

            Text("上方")

            

            HStack(spacing: 20) {  // 水平堆栈

                Text("左")

                    .frame(maxWidth: .infinity)

                    .background(Color.red.opacity(0.3))

                Text("中")

                    .frame(maxWidth: .infinity)

                    .background(Color.green.opacity(0.3))

                Text("右")

                    .frame(maxWidth: .infinity)

                    .background(Color.blue.opacity(0.3))

            }

            

            ZStack {                // 重叠堆栈

                Circle()

                    .fill(Color.yellow)

                    .frame(width: 100, height: 100)

                Text("Z")

                    .font(.largeTitle)

            }

            

            Text("下方")

        }

        .padding()

    }

}

**三种 Stack: **

  • VStack - 垂直排列

  • HStack - 水平排列

  • ZStack - 前后叠加

13.5 常用控件


struct ControlsView: View {

    @State private var text = ""

    @State private var isOn = false

    @State private var sliderValue = 50.0

    @State private var selectedDate = Date()

    @State private var selectedColor = Color.red

    

    var body: some View {

        Form// 表单,自动处理滚动和布局

            Section("输入") {

                TextField("请输入", text: $text)

                SecureField("密码", text: $text// 密码输入

                TextEditor(text: $text// 多行文本

            }

            

            Section("选择") {

                Toggle("开关", isOn: $isOn)

                

                Slider(value: $sliderValue, in: 0...100) {

                    Text("滑动条")

                }

                

                DatePicker("日期", selection: $selectedDate)

                

                ColorPicker("颜色", selection: $selectedColor)

            }

            

            Section("按钮") {

                Button("普通按钮") {}

                

                Button(action: {}) {

                    Label("带图标的按钮", systemImage: "star.fill")

                }

                

                Button("主要按钮") {}

                    .buttonStyle(.borderedProminent)

                

                Button("胶囊按钮") {}

                    .buttonStyle(.bordered)

                    .controlSize(.large)

                    .tint(.green)

            }

        }

    }

}

注意 **$** 符号: $texttext 的绑定(Binding),双向数据流。

13.6 列表与导航


// 数据模型

struct Restaurant: Identifiable {

    let id = UUID()

    let name: String

    let cuisine: String

    let rating: Double

}

  


struct RestaurantRow: View {

    let restaurant: Restaurant

    

    var body: some View {

        HStack {

            VStack(alignment: .leading) {

                Text(restaurant.name)

                    .font(.headline)

                Text(restaurant.cuisine)

                    .font(.subheadline)

                    .foregroundColor(.secondary)

            }

            

            Spacer()

            

            HStack {

                Image(systemName: "star.fill")

                    .foregroundColor(.yellow)

                Text(String(format: "%.1f", restaurant.rating))

            }

        }

    }

}

  


struct RestaurantListView: View {

    let restaurants = [

        Restaurant(name: "川味轩", cuisine: "川菜", rating: 4.5),

        Restaurant(name: "金鼎轩", cuisine: "粤菜", rating: 4.2),

        Restaurant(name: "日料屋", cuisine: "日料", rating: 4.8)

    ]

    

    var body: some View {

        NavigationView {

            List(restaurants) { restaurant in

                NavigationLink(destination: RestaurantDetailView(restaurant: restaurant)) {

                    RestaurantRow(restaurant: restaurant)

                }

            }

            .navigationTitle("餐厅列表")

        }

    }

}

  


struct RestaurantDetailView: View {

    let restaurant: Restaurant

    

    var body: some View {

        VStack(spacing: 20) {

            Image(systemName: "fork.knife.circle.fill")

                .resizable()

                .frame(width: 100, height: 100)

                .foregroundColor(.orange)

            

            Text(restaurant.name)

                .font(.largeTitle)

            

            Text(restaurant.cuisine)

                .font(.title2)

                .foregroundColor(.secondary)

            

            HStack {

                ForEach(0..<Int(restaurant.rating), id: \.self) { _ in

                    Image(systemName: "star.fill")

                        .foregroundColor(.yellow)

                }

            }

            

            Spacer()

        }

        .padding()

        .navigationTitle("详情")

    }

}

**关键概念: **

  • Identifiable 协议:让数据可以被列表唯一标识

  • List:自动处理行、分隔线、滑动删除

  • NavigationView + NavigationLink:页面跳转

13.7 数据绑定 - @Binding

当子视图需要修改父视图的状态时:


// 子视图

struct ToggleView: View {

    @Binding var isOn: Bool  // 绑定,不是自己的状态

    

    var body: some View {

        Toggle("开关", isOn: $isOn)

    }

}

  


// 父视图

struct ParentView: View {

    @State private var lightOn = false

    

    var body: some View {

        ToggleView(isOn: $lightOn// 传递绑定

    }

}

13.8 可观察对象 - @StateObject 和 @ObservedObject

对于复杂数据模型,使用 ObservableObject:


import Combine

  


class TaskStore: ObservableObject {

    @Published var tasks: [Task] = []

    @Published var filter: TaskFilter = .all

    

    var filteredTasks: [Task] {

        switch filter {

        case .all: return tasks

        case .active: return tasks.filter { !$0.isCompleted }

        case .completed: return tasks.filter { $0.isCompleted }

        }

    }

    

    func addTask(_ title: String) {

        tasks.append(Task(title: title))

    }

    

    func toggleTask(_ task: Task) {

        if let index = tasks.firstIndex(where: { $0.id == task.id }) {

            tasks[index].isCompleted.toggle()

        }

    }

}

  


struct TaskListView: View {

    @StateObject private var store = TaskStore()  // 创建可观察对象

    

    var body: some View {

        List {

            ForEach(store.filteredTasks) { task in

                TaskRow(task: task) {

                    store.toggleTask(task)

                }

            }

        }

    }

}

** @StateObject vs @ObservedObject: **

  • @StateObject:创建并持有对象(用这个视图创建的数据)

  • @ObservedObject:引用外部传入的对象

13.9 环境值 - @Environment


struct ContentView: View {

    @Environment(\.colorScheme) var colorScheme

    @Environment(\.dismiss) var dismiss

    

    var body: some View {

        VStack {

            Text(colorScheme == .dark ? "Dark Mode" : "Light Mode")

            

            Button("关闭") {

                dismiss()  // 关闭当前页面

            }

        }

    }

}


第14章:SwiftUI 进阶

14.1 动画

SwiftUI 动画非常简单,只需添加 .animation


struct AnimationView: View {

    @State private var isExpanded = false

    

    var body: some View {

        VStack {

            RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)

                .fill(isExpanded ? Color.blue : Color.red)

                .frame(

                    width: isExpanded ? 300 : 100,

                    height: isExpanded ? 300 : 100

                )

                .animation(.spring(response: 0.3, dampingFraction: 0.5), value: isExpanded)

            

            Button("切换") {

                isExpanded.toggle()

            }

        }

    }

}

**动画类型: **

  • .default - 默认动画

  • .linear - 线性

  • .easeIn / .easeOut / .easeInOut - 缓动

  • .spring() - 弹性动画

  • .interactiveSpring() - 交互式弹性

14.2 手势


struct GestureView: View {

    @State private var offset: CGSize = .zero

    @State private var scale: CGFloat = 1.0

    @State private var rotation: Angle = .zero

    

    var body: some View {

        Image(systemName: "star.fill")

            .font(.system(size: 100))

            .foregroundColor(.yellow)

            .offset(offset)

            .scaleEffect(scale)

            .rotationEffect(rotation)

            .gesture(

                DragGesture()

                    .onChanged { gesture in

                        offset = gesture.translation

                    }

                    .onEnded { _ in

                        withAnimation {

                            offset = .zero

                        }

                    }

            )

            .gesture(

                MagnificationGesture()

                    .onChanged { scale = $0 }

            )

            .gesture(

                RotationGesture()

                    .onChanged { rotation = $0 }

            )

    }

}

14.3 数据持久化


import SwiftUI

  


struct TodoItem: Identifiable, Codable {

    let id: UUID

    var title: String

    var isCompleted: Bool

    var createdAt: Date

}

  


class TodoStore: ObservableObject {

    @Published var todos: [TodoItem] = []

    

    private let saveKey = "todos"

    

    init() {

        load()

    }

    

    func add(_ title: String) {

        let todo = TodoItem(

            id: UUID(),

            title: title,

            isCompleted: false,

            createdAt: Date()

        )

        todos.append(todo)

        save()

    }

    

    func toggle(_ todo: TodoItem) {

        if let index = todos.firstIndex(where: { $0.id == todo.id }) {

            todos[index].isCompleted.toggle()

            save()

        }

    }

    

    func delete(at offsets: IndexSet) {

        todos.remove(atOffsets: offsets)

        save()

    }

    

    // UserDefaults 存储

    private func save() {

        if let encoded = try? JSONEncoder().encode(todos) {

            UserDefaults.standard.set(encoded, forKey: saveKey)

        }

    }

    

    private func load() {

        if let data = UserDefaults.standard.data(forKey: saveKey),

           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {

            todos = decoded

        }

    }

}


Swift 从入门到精通-第三篇

作者 泉木
2026年3月13日 10:56

第9章:协议与扩展

9.1 协议 (Protocol) - 定义接口

协议定义了一组方法、属性或其他要求的蓝图:

protocol Greetable {
    var name: String { get }  // 只读属性要求
    func greet()              // 方法要求
}

protocol Describable {
    var description: String { get }
}

遵循协议:

struct User: Greetable, Describable {
    let name: String
    let email: String
    
    // 必须实现协议要求
    func greet() {
        print("Hi, I'm (name)!")
    }
    
    var description: String {
        return "User: (name) <(email)>"
    }
}

let user = User(name: "Alice", email: "alice@example.com")
user.greet()        // Hi, I'm Alice!
print(user.description)

9.2 协议扩展 - 提供默认实现

// 在扩展中提供默认实现
extension Greetable {
    func greet() {
        print("Hello, my name is (name)")
    }
}

struct Employee: Greetable {
    let name: String
    // 不需要实现 greet(),使用默认实现
}

let employee = Employee(name: "Bob")
employee.greet()  // Hello, my name is Bob

协议扩展的强大之处:

  • 可以给协议添加默认行为
  • 遵循者可以选择使用默认实现或自定义
  • 这是"面向协议编程"的核心

9.3 协议组合

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

struct Person: Named, Aged {
    var name: String
    var age: Int
}

// 函数接受同时遵循多个协议的类型
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday (celebrator.name), you're (celebrator.age)!")
}

let person = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: person)

9.4 带关联类型的协议

protocol Container {
    associatedtype Item  // 关联类型
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // 自动推断 Item = Int
    private var items: [Int] = []
    
    var count: Int { items.count }
    
    mutating func append(_ item: Int) {
        items.append(item)
    }
    
    subscript(i: Int) -> Int {
        return items[i]
    }
}

// 也可以显式指定
double Stack: Container {
    typealias Item = Double  // 显式指定
    // ...
}

9.5 扩展系统类型

// 给 Int 添加方法
extension Int {
    var squared: Int {
        return self * self
    }
    
    func times(_ action: () -> Void) {
        for _ in 0..<self {
            action()
        }
    }
}

print(5.squared)  // 25
3.times {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

// 给 Collection 添加方法
extension Collection {
    var isNotEmpty: Bool {
        return !isEmpty
    }
}

[1, 2, 3].isNotEmpty  // true

第10章:泛型编程

10.1 为什么要用泛型?

想象你要写交换两个值的函数:

// 只能交换整数
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 只能交换字符串
func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

重复代码!用泛型可以写一个通用的:

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)

<T>类型参数,代表任意类型。

10.2 泛型类型

// 泛型栈
struct Stack<Element> {
    private var items: [Element] = []
    
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element? {
        return items.popLast()
    }
    
    var topItem: Element? {
        return items.last
    }
    
    var isEmpty: Bool {
        return items.isEmpty
    }
}

var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop()!)  // 2

var stringStack = Stack<String>()
stringStack.push("hello")
stringStack.push("world")

10.3 类型约束

// 要求 T 必须遵循 Comparable 协议
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])

常用类型约束:

  • T: Equatable - 可以比较相等
  • T: Comparable - 可以比较大小
  • T: Hashable - 可以作为字典的 key
  • T: SomeProtocol - 遵循某个协议

10.4 关联类型与 where 子句

protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

// 要求两个容器的 Item 相同且可比较
func allItemsMatch<C1: Container, C2: Container>(_ container1: C1, _ container2: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {
    
    if container1.count != container2.count {
        return false
    }
    
    for i in 0..<container1.count {
        if container1[i] != container2[i] {
            return false
        }
    }
    return true
}

10.5 不透明返回类型 (some)

protocol Shape {
    func draw() -> String
}

struct Triangle: Shape {
    var size: Int
    func draw() -> String {
        var result = [String]()
        for length in 1...size {
            result.append(String(repeating: "*", count: length))
        }
        return result.joined(separator: "\n")
    }
}

struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        return Array(repeating: line, count: size).joined(separator: "\n")
    }
}

// 返回遵循 Shape 协议的某种类型,但隐藏具体是什么
func makeShape() -> some Shape {
    return Triangle(size: 3)
}

// 也可以返回不同的,只要都是 Shape
func makeRandomShape() -> some Shape {
    Bool.random() ? Triangle(size: 3) : Square(size: 3)
}

some 的好处:

  • 调用者不需要知道具体类型
  • 编译器可以进行类型优化
  • 在 SwiftUI 中非常常用(some View

第11章:错误处理

11.1 定义错误类型

enum VendingMachineError: Error {
    case invalidSelection                    // 选择无效
    case insufficientFunds(coinsNeeded: Int) // 金额不足,附带需要多少钱
    case outOfStock                          // 缺货
}

任何遵循 Error 协议的类型都可以表示错误。

11.2 抛出错误

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    
    func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        
        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }
        
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        
        // 执行购买逻辑
        coinsDeposited -= item.price
        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem
        
        print("Dispensing (name)")
    }
}

throws 标记表示这个函数可能抛出错误。

11.3 处理错误

do-catch

let vendingMachine = VendingMachine()
vendingMachine.depositCoins(8)

do {
    try vendingMachine.vend(itemNamed: "Candy Bar")
} catch VendingMachineError.invalidSelection {
    print("Invalid selection.")
} catch VendingMachineError.outOfStock {
    print("Out of stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional (coinsNeeded) coins.")
} catch {
    print("Unexpected error: (error)")
}

try? - 转换为可选值

// 成功返回结果,失败返回 nil
if let result = try? someThrowingFunction() {
    // 使用结果
} else {
    // 处理失败
}

// 等效于
do {
    let result = try someThrowingFunction()
} catch {
    // 忽略错误
}

try! - 强制解包(危险!)

// 确定不会失败时使用
let result = try! someThrowingFunction()
// 如果失败了,程序会崩溃

11.4 defer - 清理资源

func processFile(filename: String) throws -> String {
    let file = try openFile(filename)
    
    defer {
        closeFile(file)  // 无论如何都会执行
    }
    
    if filename.isEmpty {
        throw FileError.invalidName  // 先执行 defer,再抛出错误
    }
    
    return try readFile(file)
}  // 正常返回也会执行 defer

11.5 Result 类型

enum NetworkError: Error {
    case badURL
    case noData
    case decodingError
}

func fetchUser(completion: (Result<User, NetworkError>) -> Void) {
    // 模拟网络请求
    let success = Bool.random()
    if success {
        completion(.success(User(name: "Alice")))
    } else {
        completion(.failure(.noData))
    }
}

// 使用
fetchUser { result in
    switch result {
    case .success(let user):
        print("Got user: (user.name)")
    case .failure(let error):
        print("Error: (error)")
    }
}

第12章:内存管理

12.1 ARC (自动引用计数)

Swift 使用 ARC 自动管理内存:

  • 每次创建实例,引用计数 +1
  • 每次引用消失,引用计数 -1
  • 引用计数为 0,内存被释放

你不需要手动管理,但要理解引用关系。

12.2 强引用循环问题

class Person {
    let name: String
    var apartment: Apartment?  // 强引用
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?  // 强引用
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment (unit) is being deinitialized")
    }
}

var john: Person? = Person(name: "John Appleseed")
var unit4A: Apartment? = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil
unit4A = nil
// 没有打印 deinit 消息!内存泄漏了!

问题: Person 持有 Apartment,Apartment 持有 Person,形成一个环,引用计数永远不会归零。

12.3 弱引用 (Weak Reference)

class Apartment {
    let unit: String
    weak var tenant: Person?  // 弱引用!
    
    init(unit: String) {
        self.unit = unit
    }
    
    deinit {
        print("Apartment (unit) is being deinitialized")
    }
}

// 现在当 john = nil,Person 实例会被释放
// 然后 Apartment 的 tenant 自动变成 nil

弱引用的特点:

  • 不增加引用计数
  • 指向的实例释放后自动变成 nil
  • 必须是可选类型(因为可能为 nil)

什么时候用弱引用?

  • 父子关系中的子(如 Apartment 和 tenant)
  • 委托模式 (Delegate pattern)

12.4 无主引用 (Unowned Reference)

class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer  // 无主引用
    
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit {
        print("Card #(number) is being deinitialized")
    }
}

var alice: Customer? = Customer(name: "Alice")
alice!.card = CreditCard(number: 1234_5678_9012_3456, customer: alice!)

alice = nil
// 两个实例都被释放

无主引用的特点:

  • 不增加引用计数
  • 不是可选类型
  • 指向的实例释放后变成 dangling pointer(悬挂指针)

什么时候用无主引用?

  • 确定引用的实例永远比自己活得长
  • 不会造成循环引用的强关系

12.5 闭包中的循环引用

class HTMLElement {
    let name: String
    let text: String?
    
    // 闭包捕获 self,形成循环引用!
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<(self.name)>(text)</(self.name)>"
        } else {
            return "<(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "Hello")
let html = paragraph!.asHTML()
paragraph = nil
// 没有 deinit!循环引用!

解决:捕获列表

lazy var asHTML: () -> String = { [weak self] in
    guard let self = self else {
        return ""
    }
    if let text = self.text {
        return "<(self.name)>(text)</(self.name)>"
    } else {
        return "<(self.name) />"
    }
}

捕获列表语法:

  • [weak self] - 弱引用 self
  • [unowned self] - 无主引用 self
  • [x = someValue] - 捕获时复制值而不是引用

❌
❌