iOS 底层知识入门
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
的完整作用介绍
一、对象生命周期管理
- 对象内存分配与释放
- 通过
objc_allocateClassPair
和class_createInstance
动态创建类和对象。 - 管理对象的引用计数(Retain/Release),包括自动引用计数(ARC)的底层实现。
1
2
3id obj = class_createInstance([NSObject class], 0); // 创建实例
objc_retain(obj); // 增加引用计数
objc_release(obj); // 释放对象
- 通过
- 类与元类(Metaclass)系统
- 维护
Class
和Meta Class
的继承链,确保方法查找的层级正确性。 - 每个类的
isa
指针指向其元类,元类的isa
指向根元类(Root Metaclass)。
- 维护
二、消息传递机制(Messaging)
- 动态方法调用
- 实现
objc_msgSend
函数(汇编优化),负责方法查找、缓存和跳转。 - 方法缓存(Method Cache)加速高频调用的方法。
- 实现
- 消息转发(Message Forwarding)
- 处理未实现方法的动态解析(
resolveInstanceMethod:
)、备用接收者(forwardingTargetForSelector:
)和完整转发(forwardInvocation:
)。 - 支持
@dynamic
属性的延迟绑定(如 Core Data 模型)。
- 处理未实现方法的动态解析(
三、运行时类型系统 (Runtime Type System)
- 类型信息注册与查询
- 管理类的属性(
class_copyPropertyList
)、方法(class_copyMethodList
)、协议(class_copyProtocolList
)和成员变量(class_copyIvarList
)。 - 支持运行时动态创建/修改类(
objc_registerClassPair
)。
- 管理类的属性(
- 类型编码(Type Encoding)
- 将 Objective-C 类型转换为 C 字符串编码(如
@encode(NSInteger)
→"q"
),用于序列化和反射。 - 实现
NSMethodSignature
的方法签名解析。
- 将 Objective-C 类型转换为 C 字符串编码(如
四、内存管理
- 自动释放池(Autorelease Pool)
- 通过
objc_autoreleasePoolPush
和objc_autoreleasePoolPop
管理延迟释放的对象。 - 每个线程的自动释放池栈由
libobjc
维护。
- 通过
- 弱引用(Weak References)
- 管理弱引用表(Weak Table),实现
__weak
变量的自动置nil
。1
id __weak weakObj = strongObj; // 弱引用表插入记录
- 管理弱引用表(Weak Table),实现
- 关联对象(Associated Objects)
- 通过
objc_setAssociatedObject
动态绑定键值对到对象(类似“扩展属性”)。1
objc_setAssociatedObject(obj, key, value, OBJC_ASSOCIATION_RETAIN);
- 通过
五、协议与分类(Protocol & Category)
- 协议动态注册
- 支持运行时添加协议方法(
protocol_addMethodDescription
)。
- 支持运行时添加协议方法(
- 分类(
Category
)合并- 在
attachCategories
函数中,将分类的方法、属性和协议合并到主类。 - 分类的方法会覆盖主类的同名方法。
- 在
六、异常处理
- Objective-C 异常模型
- 实现
@try/@catch/@finally
的异常捕获机制。 - 与 C++ 异常交互(通过
OBJC_USE_OBJC_EXCEPTIONS
配置)。
- 实现
- 错误处理桥接
- 将
NSError
的errorWithDomain:code:userInfo:
映射到底层异常。
- 将
七、底层性能优化
- 方法缓存(Method Cache)
- 每个类维护一个哈希表缓存已查找的方法(
objc_cache
)。
- 每个类维护一个哈希表缓存已查找的方法(
- Tagged Pointer 优化
- 将小对象(如
NSNumber
、NSDate
)直接编码到指针中,避免堆内存分配。
- 将小对象(如
- 非脆弱实例变量(Non-Fragile IVars)
- 允许子类安全扩展父类的实例变量,避免二进制兼容性问题。
八、与 Swift 的交互
- Swift 类的 Objective-C 兼容性
- 为
@objc
修饰的 Swift 类生成 Objective-C 元数据(如_TtC
前缀的类名)。
- 为
- 动态特性支持
- 实现 Swift 的
dynamic
方法调用(通过objc_msgSend
)。
- 实现 Swift 的
- 内存模型桥接
- Swift 引用计数与 Objective-C ARC 共享同一套底层计数器。
九、调试与内省(Introspection)
- 动态调试工具支持
- 提供
class_getName
、method_getImplementation
等函数供 LLDB 使用。
- 提供
- 运行时环境变量
- 通过
OBJC_PRINT_LOAD_METHODS
等环境变量输出运行时日志。
- 通过
- 逆向工程基础
class-dump
等工具依赖libobjc
的类信息导出功能。
总结
libobjc.A.dylib
是 Objective-C 动态特性的实现核心,其作用覆盖:
- 对象模型:从内存分配到消息传递的完整生命周期管理
- 动态性:方法转发、关联对象、分类合并等运行时魔法
- 性能:方法缓存、Tagged Pointer、非脆弱 IVars 等底层优化
- 跨语言:为 Swift 提供动态能力与内存模型兼容性
即使 Apple 推动 Swift 取代 Objective-C,libobjc
在可预见的未来仍不可替代。它如同操作系统的“神经系统”,将高级语言代码翻译为可执行的动态行为。理解其原理,是掌握 iOS/macOS 开发生态的关键。
关于运行时
运行时(Runtime) 是程序在运行时所依赖的环境和机制,负责管理内存、方法调用、类型检查、异常处理、动态行为等底层操作。它的核心作用是在程序执行期间提供动态支持,而不仅仅是编译时静态确定的逻辑。
为什么 Objective-C 需要运行时?
Objective-C 是一种动态语言,它的许多特性依赖运行时实现:
- 动态消息分发:
Objective-C 的方法调用(如[obj doSomething]
)本质是通过运行时调用发送消息函数(objc_msgSend
)。运行时在程序执行期间动态查找方法实现(甚至允许动态添加/替换方法),而非编译时绑定。ß∑ - 运行时类型检查和反射:
支持通过NSClassFromString
动态加载类、isKindOfClass
检查类型、respondsToSelector
判断方法是否存在。 - 方法交换(Method Swizzling):
允许在运行时交换两个方法的实现(常用于调试或 AOP 编程)。 - 动态协议和类扩展:
通过运行时 API(如class_addMethod
)动态添加方法或属性到已有类。
所有语言都需要一个运行时吗?
是的,但运行时的形式和复杂度差异极大:
- 低级语言(如 C):
运行时极简,通常仅包含标准库(如libc
),负责内存分配(malloc
)、文件操作等基础功能。动态行为(如多态)需手动实现。 - 静态编译语言(如 C++、Rust):
运行时主要处理异常、虚函数表(vtable)等,大部分逻辑在编译时确定,性能高但灵活性较低。 - 动态语言(如 Python、JavaScript):
运行时复杂,负责解释执行、垃圾回收、动态类型检查等。例如 Python 的sys
模块、JS 引擎(如 V8)都是运行时的核心。 - 虚拟机语言(如 Java、C#):
依赖虚拟机(JVM、CLR)作为运行时环境,提供跨平台执行、即时编译(JIT)、垃圾回收等高级功能。 - 特殊场景语言(如 Go):
Go 的运行时管理协程(goroutine)、垃圾回收和网络轮询器,但最终编译为静态二进制文件。
总结
- 运行时存在的必要性:所有语言都需要运行时支持,只是其职责和复杂度因语言设计目标而异。
- Objective-C 的特殊性:其动态特性(如消息传递、反射)高度依赖运行时,而类似 C++ 的静态语言则在编译时完成更多工作。
- 性能与灵活性的权衡:运行时越复杂(如动态语言),开发效率越高,但可能牺牲性能;反之,运行时越简单(如 C),性能更高但灵活性受限。
Clang 是如何处理 Objective-C 源码的
Clang 是 LLVM 项目的一部分,主要用于 C、C++ 和 Objective-C 的编译。Objective-C 的类和对象在经过 Clang 编译后确实会被转换为 C 语言的结构体和函数,但这种转换并不是简单的“一对一映射”,而是通过 Objective-C Runtime(运行时机制) 的复杂设计实现的。以下是具体细节:
- 对象和类的本质:C 结构体
所有 Objective-C 对象和类的底层实现都基于 C 结构体:- 实例:本质是
objc_object
结构体,核心成员是 isa 指针(指向所属的类)。1
2
3struct objc_object {
Class isa; // 指向所属的类
} - 类(Class):本质是
objc_class
结构体,继承自objc_object
,因此类本身也是一个对象(称为“类对象”)。1
2
3
4
5
6struct objc_class : objc_object {
Class superclass; // 父类指针
cache_t cache; // 方法缓存
class_data_bits_t bits; // 存储方法列表、属性列表等
// ...方法
};
- 实例:本质是
- 成员变量(ivar)和属性的处理
- 成员变量:直接存储在对象的结构体中。
例如,定义一个Person
类:编译后,成员变量1
2
3
4
5@interface Person : NSObject {
NSString *_name;
}
@property (nonatomic) NSInteger age;
@end_name
和属性自动合成的_age
会被合并到Person
对象的结构体中:1
2
3
4
5struct 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;
}
- 成员变量:直接存储在对象的结构体中。
- 对象方法和类方法的处理
- 对象方法:被编译为 C 函数,并存储在类的方法列表中(
class_data_bits_t
中的method_list_t
)。
例如,一个Person
类的方法:会被编译为:1
2
3\- (void)sayHello {
NSLog(@"Hello!");
}该方法会被添加到1
2
3void Person_sayHello(Person *self, SEL _cmd) {
NSLog(@"Hello!");
}Person
类的method_list_t
中。 - 类方法:一样被编译为 C 函数,但存储在
元类(Meta Class)
的方法列表中。
元类是类对象的类,其结构体与objc_class
一致,但方法列表存储的是类方法。
- 对象方法:被编译为 C 函数,并存储在类的方法列表中(
- 消息发送(方法调用)的底层实现
Objective-C 的方法调用(如[obj method]
)会被编译器转换为objc_msgSend
函数:1
2
3
4
5// 源码中的方法调用
[person sayHello];
// 编译后转换为:
objc_msgSend(person, @selector(sayHello));objc_msgSend
的底层逻辑是动态查找方法实现,以下只是个精简版本的大概步骤:- 通过
isa
指针找到对象的类 - 在类的方法缓存中查找
sayHello
方法 - 在类的方法列表中查找
sayHello
方法 - 若未找到,沿着继承链向父类查找
- 后续处理…
- 通过