iOS 底层知识入门

很多讲 iOS 底层知识的课程,视频,文章,讲师上来就是一个 struct objc_object {...} 结构体或者是 struct objc_class : objc_object {...} 的结构体,我不知道其他人是怎么想的,但是对于我本人来说,第一次接触的感觉是一脸懵逼,这两个结构体是干什么的?平时开发过程中从来没有接触和使用过它们啊?

还有很多讲师为了卖自己的课程,宣称学习 iOS 底层知识完全不需要 C++ 语言知识。跟着他的课程就能掌握。。。我作为一个过来人,真的是想骂人,如果你是个完全没有任何一点 C++ 基础的 iOS 开发者想要深入学习 iOS 的底层知识,我的建议还是先将 C++ 的一些基础知识过一遍再说吧。对于一个有开发经验的程序员来说,学习一门编程语言的基础语法真的不算一件难事。。。何况即使是有 C++ 基础的人也依旧看不明白系统的底层源码,为什么?因为不仅仅需要 C++ 语言的基础知识,还需要掌握 dyld,MachO,动态库共享缓存等底层相关的知识和概念,你才能真正看明白底层代码到底在做些什么。

要回答第一个问题,就不得不提到 Objective-C 语言的基石 –> libobjc.A.dylib 库,也叫 Objective-C 的运行时库,主要由 Objective-C,Objective-C++,C++ 编写,一小部分由汇编,C,Perl 等其他语言编写。可以这么说,如果没有 libobjc.A.dylib 库,就没有 Objective-C 语言。Objective-C 语言无法离开 libobjc.A.dylib 库。当一个 Objective-C 程序启动时,dyld 会先加载 libobjc.A.dylib,并执行其初始化代码,然后才能开始执行程序的 main 函数,没有 libobjc.A.dylib 库 Objective-C 代码无法运行。因为在 Objective-C 中,类的加载、分类的加载、+load 方法等都需要运行时库的支持,这些都是在程序的 main 函数之前就完成的。还有在程序运行过程中的对象内存管理,关联对象,消息发送,消息转发等重要又基础的功能都是由该库完成的。

libobjc.A.dylib 是 Objective-C 语言的核心运行时库,也是 iOS/macOS 生态中所有 Objective-C 和 Swift 应用的底层基石(少了它不行)。它的作用远不止“支持语法”,而是构建了整个动态对象模型和消息传递机制。struct objc_object {...}struct objc_class : objc_object {...} 两个结构体都是在这个库里面声明和实现的 C++ 结构体。在 Objective-C 1.0 时,它们完全用 C 实现,在后续的 Objective-C 2.0 中使用 C++ 重构以提升性能和添加新的功能。

NSObject 类的实现代码也是在该库里面,苹果开源了这个库的部分实现,你可以在 objc4 下载到源码工程,objc4 工程的编译产物就是 libobjc.A.dylib 库。不过苹果开源的这个工程的是无法正常运行起来的,需要通过一些步骤的配置才能成功运行起来,好在有一个开源的 项目 帮我们解决了配置 objc4 源码项目的繁琐的步骤,这样就可以在这个源码项目中运行调试底层了。查看 NSObject 的实现代码就会发现,大部分的实现都是调用了 struct objc_object {...} 的方法或者 struct objc_class : objc_object {...} 的方法,是的 C++ 中的结构体和类一样,可以有成员变量可以有对象方法,类方法。也就是说 struct objc_object {...} 是所有的 Objective-C 实例对象的底层结构,struct objc_class : objc_object {...} 是所有的 Objective-C 类对象的底层结构。

libobjc.A.dylib 的完整作用介绍

一、​​对象生命周期管理

  1. 对象内存分配与释放
    • 通过 objc_allocateClassPairclass_createInstance 动态创建类和对象。
    • 管理对象的引用计数(Retain/Release),包括自动引用计数(ARC)的底层实现。
      1
      2
      3
      id obj = class_createInstance([NSObject class], 0); // 创建实例
      objc_retain(obj); // 增加引用计数
      objc_release(obj); // 释放对象
  2. 类与元类(Metaclass)系统
    • 维护 ClassMeta Class 的继承链,确保方法查找的层级正确性。
    • 每个类的 isa 指针指向其元类,元类的 isa 指向根元类(Root Metaclass)。

二、​​消息传递机制(Messaging)​

  1. 动态方法调用
    • 实现 objc_msgSend 函数(汇编优化),负责方法查找、缓存和跳转。
    • 方法缓存(Method Cache)加速高频调用的方法。
  2. 消息转发(Message Forwarding)
    • 处理未实现方法的动态解析(resolveInstanceMethod:)、备用接收者(forwardingTargetForSelector:)和完整转发(forwardInvocation:)。
    • 支持 @dynamic 属性的延迟绑定(如 Core Data 模型)。

三、运行时类型系统 (Runtime Type System)

  1. 类型信息注册与查询
    • 管理类的属性(class_copyPropertyList)、方法(class_copyMethodList)、协议(class_copyProtocolList)和成员变量(class_copyIvarList)。
    • 支持运行时动态创建/修改类(objc_registerClassPair)。
  2. 类型编码(Type Encoding)
    • 将 Objective-C 类型转换为 C 字符串编码(如 @encode(NSInteger)"q"),用于序列化和反射。
    • 实现 NSMethodSignature 的方法签名解析。

四、内存管理

  1. 自动释放池(Autorelease Pool)
    • 通过 objc_autoreleasePoolPushobjc_autoreleasePoolPop 管理延迟释放的对象。
    • 每个线程的自动释放池栈由 libobjc 维护。
  2. 弱引用(Weak References)
    • 管理弱引用表(Weak Table),实现 __weak 变量的自动置 nil
      1
      id __weak weakObj = strongObj; // 弱引用表插入记录
  3. 关联对象(Associated Objects)
    • 通过 objc_setAssociatedObject 动态绑定键值对到对象(类似“扩展属性”)。
      1
      objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN);

五、协议与分类(Protocol & Category)​

  1. 协议动态注册
    • 支持运行时添加协议方法(protocol_addMethodDescription)。
  2. 分类(Category)合并
    • attachCategories 函数中,将分类的方法、属性和协议合并到主类。
    • 分类的方法会覆盖主类的同名方法。

六、异常处理

  1. Objective-C 异常模型
    • 实现 @try/@catch/@finally 的异常捕获机制。
    • 与 C++ 异常交互(通过 OBJC_USE_OBJC_EXCEPTIONS 配置)。
  2. 错误处理桥接
    • NSErrorerrorWithDomain:code:userInfo: 映射到底层异常。

七、底层性能优化

  1. 方法缓存(Method Cache)
    • 每个类维护一个哈希表缓存已查找的方法(objc_cache)。
  2. Tagged Pointer 优化
    • 将小对象(如 NSNumberNSDate)直接编码到指针中,避免堆内存分配。
  3. 非脆弱实例变量(Non-Fragile IVars)
    • 允许子类安全扩展父类的实例变量,避免二进制兼容性问题。

八、​​与 Swift 的交互

  1. Swift 类的 Objective-C 兼容性
    • @objc 修饰的 Swift 类生成 Objective-C 元数据(如 _TtC 前缀的类名)。
  2. 动态特性支持
    • 实现 Swift 的 dynamic 方法调用(通过 objc_msgSend)。
  3. 内存模型桥接
    • Swift 引用计数与 Objective-C ARC 共享同一套底层计数器。

九、​​调试与内省(Introspection)​

  1. 动态调试工具支持
    • 提供 class_getNamemethod_getImplementation 等函数供 LLDB 使用。
  2. 运行时环境变量
    • 通过 OBJC_PRINT_LOAD_METHODS 等环境变量输出运行时日志。
  3. 逆向工程基础
    • class-dump 等工具依赖 libobjc 的类信息导出功能。

总结

libobjc.A.dylib 是 Objective-C 动态特性的实现核心,其作用覆盖:

  • 对象模型​​:从内存分配到消息传递的完整生命周期管理
  • 动态性​​:方法转发、关联对象、分类合并等运行时魔法
  • 性能​​:方法缓存、Tagged Pointer、非脆弱 IVars 等底层优化
  • 跨语言​​:为 Swift 提供动态能力与内存模型兼容性

即使 Apple 推动 Swift 取代 Objective-C,libobjc 在可预见的未来仍不可替代。它如同操作系统的“神经系统”,将高级语言代码翻译为可执行的动态行为。理解其原理,是掌握 iOS/macOS 开发生态的关键。

关于运行时

运行时(Runtime)​​ 是程序在运行时所依赖的环境和机制,负责管理内存、方法调用、类型检查、异常处理、动态行为等底层操作。它的核心作用是在程序执行期间提供动态支持,而不仅仅是编译时静态确定的逻辑。

为什么 Objective-C 需要运行时?​

Objective-C 是一种​​动态语言​​,它的许多特性依赖运行时实现:

  1. 动态消息分发​​:
    Objective-C 的方法调用(如 [obj doSomething])本质是通过运行时调用发送消息函数(objc_msgSend)。运行时在程序执行期间动态查找方法实现(甚至允许动态添加/替换方法),而非编译时绑定。ß∑
  2. ​​运行时类型检查和反射​​:
    支持通过 NSClassFromString 动态加载类、isKindOfClass 检查类型、respondsToSelector 判断方法是否存在。
  3. ​​方法交换(Method Swizzling)​​:
    允许在运行时交换两个方法的实现(常用于调试或 AOP 编程)。
  4. 动态协议和类扩展​​:
    通过运行时 API(如 class_addMethod)动态添加方法或属性到已有类。

所有语言都需要一个运行时吗?​

是的,但运行时的形式和复杂度差异极大:

  1. 低级语言(如 C)​​:
    运行时极简,通常仅包含标准库(如 libc),负责内存分配(malloc)、文件操作等基础功能。动态行为(如多态)需手动实现。
  2. 静态编译语言(如 C++、Rust)​​:
    运行时主要处理异常、虚函数表(vtable)等,大部分逻辑在编译时确定,性能高但灵活性较低。
  3. ​​动态语言(如 Python、JavaScript)​​:
    运行时复杂,负责解释执行、垃圾回收、动态类型检查等。例如 Python 的 sys 模块、JS 引擎(如 V8)都是运行时的核心。
  4. 虚拟机语言(如 Java、C#)​​:
    依赖虚拟机(JVM、CLR)作为运行时环境,提供跨平台执行、即时编译(JIT)、垃圾回收等高级功能。
  5. 特殊场景语言(如 Go)​​:
    Go 的运行时管理协程(goroutine)、垃圾回收和网络轮询器,但最终编译为静态二进制文件。

总结

  • ​​运行时存在的必要性​​:所有语言都需要运行时支持,只是其职责和复杂度因语言设计目标而异。
  • Objective-C 的特殊性​​:其动态特性(如消息传递、反射)高度依赖运行时,而类似 C++ 的静态语言则在编译时完成更多工作。
  • 性能与灵活性的权衡​​:运行时越复杂(如动态语言),开发效率越高,但可能牺牲性能;反之,运行时越简单(如 C),性能更高但灵活性受限。

Clang 是如何处理 Objective-C 源码的

Clang 是 LLVM 项目的一部分,主要用于 C、C++ 和 Objective-C 的编译。Objective-C 的类和对象在经过 Clang 编译后确实会被转换为 C 语言的结构体和函数,但这种转换并不是简单的“一对一映射”,而是通过 ​​Objective-C Runtime(运行时机制)​​ 的复杂设计实现的。以下是具体细节:

  1. ​​对象和类的本质:C 结构体​
    所有 Objective-C 对象和类的底层实现都基于 C 结构体:
    • 实例:本质是 objc_object 结构体,核心成员是 isa 指针(指向所属的类)。
      1
      2
      3
      struct objc_object {
      Class isa; // 指向所属的类
      }
    • 类(Class):本质是 objc_class 结构体,继承自 objc_object,因此类本身也是一个对象(称为“类对象”)。
      1
      2
      3
      4
      5
      6
      struct objc_class : objc_object {
      Class superclass; // 父类指针
      cache_t cache; // 方法缓存
      class_data_bits_t bits; // 存储方法列表、属性列表等
      // ...方法
      };
  2. ​​成员变量(ivar)和属性的处理​
    • 成员变量​​:直接存储在对象的结构体中。
      例如,定义一个 Person 类:
      1
      2
      3
      4
      5
      @interface Person : NSObject {
      NSString *_name;
      }
      @property (nonatomic) NSInteger age;
      @end
      编译后,成员变量 _name 和属性自动合成的 _age 会被合并到 Person 对象的结构体中:
      1
      2
      3
      4
      5
      struct Person_IMPL {
      Class isa; // 继承自 objc_object
      NSString *_name; // 成员变量
      NSInteger _age; // 属性合成的成员变量
      };
    • 属性​​:编译器会自动生成 ​​getter/setter 方法​​,并转换为 C 函数。例如:
      1
      2
      3
      4
      5
      6
      7
      // 自动生成的 getter 和 setter 函数
      NSInteger Person_getAge(Person *self, SEL _cmd) {
      return self->_age;
      }
      void Person_setAge(Person *self, SEL _cmd, NSInteger age) {
      self->_age = age;
      }
  3. ​​对象方法和类方法的处理​
    • 对象方法​​:被编译为 C 函数,并存储在类的方法列表中(class_data_bits_t 中的 method_list_t)。
      例如,一个 Person 类的方法:
      1
      2
      3
      \- (void)sayHello {
      NSLog(@"Hello!");
      }
      会被编译为:
      1
      2
      3
      void Person_sayHello(Person *self, SEL _cmd) {
      NSLog(@"Hello!");
      }
      该方法会被添加到 Person 类的 method_list_t 中。
    • 类方法:一样被编译为 C 函数,但存储在 元类(Meta Class) 的方法列表中。
      元类是类对象的类,其结构体与 objc_class 一致,但方法列表存储的是类方法。
  4. ​​消息发送(方法调用)的底层实现
    Objective-C 的方法调用(如 [obj method])会被编译器转换为 objc_msgSend 函数:
    1
    2
    3
    4
    5
    // 源码中的方法调用
    [person sayHello];

    // 编译后转换为:
    objc_msgSend(person, @selector(sayHello));
    objc_msgSend 的底层逻辑是动态查找方法实现,以下只是个精简版本的大概步骤:
    1. 通过 isa 指针找到对象的类
    2. 在类的方法缓存中查找 sayHello 方法
    3. 在类的方法列表中查找 sayHello 方法
    4. 若未找到,沿着继承链向父类查找
    5. 后续处理…