Swift 实现单例的原理,和OC有什么区别?
在OC 中,我们常常这样写单例:
+ (instancetype)sharedInstance {
static MyClass *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
原理分析:
-
dispatch_once保证只执行一次dispatch_once是 GCD 的 API,用底层的原子操作和内存屏障机制,保证线程安全地执行初始化代码一次。- 即使多个线程同时调用
sharedInstance,也只有一个线程能进入 block。
-
静态局部变量
static修饰的instance和onceToken只初始化一次,生命周期贯穿整个程序运行期。
-
懒加载 + 保证线程安全
- 第一次访问时创建对象,之后直接返回同一实例。
在Swift 中,单例更加简洁:
class MyManager {
static let shared = MyManager()
private init() {}
}
原理分析:
-
static let本身是线程安全的- Swift 的
static let在底层由 lazy + dispatch_once 实现。 - 即在第一次访问时初始化,且保证只初始化一次。
- 编译器自动帮你处理线程同步逻辑,不需要显式使用
dispatch_once。
- Swift 的
-
编译器层级的单例保证
- Swift 的
static存储属性会在访问时由编译器插入类似pthread_once的一次性初始化逻辑。 - 内部使用
_swift_once或_dispatch_once来确保线程安全。
- Swift 的
-
类型安全
- Swift 的单例是类型安全的,
shared明确为MyManager类型; - Objective-C 的
id和动态派发机制,使得类型在运行时才检查。
- Swift 的单例是类型安全的,
Swift 单例利用编译器生成的静态属性初始化逻辑,天然支持线程安全的“懒加载”;而 Objective-C 依赖 GCD 的 dispatch_once 来保证初始化的唯一性和线程安全性,属于运行时层面的控制。
Swift 的懒加载和 OC 的区别
OC 懒加载
@interface Person : NSObject
@property (nonatomic, strong) NSMutableArray *friends;
@end
@implementation Person
- (NSMutableArray *)friends {
if (!_friends) {
_friends = [NSMutableArray array];
}
return _friends;
}
@end
swift 懒加载
class Person {
lazy var friends: [String] = {
return [String]()
}()
}
区别
| 语言 | 懒加载写法 | 含义 |
|---|---|---|
| Objective-C | 在 getter 方法中判断 _ivar 是否为空,再初始化 | 手动控制懒加载逻辑 |
| Swift | 用 lazy var 修饰属性 | 编译器自动生成延迟初始化逻辑 |
Objective-C 的懒加载 是“手动延迟初始化”,依靠 getter 判断 _ivar 是否为 nil;
Swift 的懒加载 是“语言级延迟初始化”,由编译器自动生成包装逻辑和初始化闭包,
前者是设计模式级特性,后者是语言语法级特性。
Swift 的闭包和 OC 的闭包有什么区别?
| 特性 | Swift 闭包 | OC Block |
|---|---|---|
| 层级 | 语言级 | 对象/Runtime 级 |
| 内存优化 | 编译器处理,逃逸闭包堆,非逃逸闭包栈 | 默认栈 Block,需要 copy 到堆 |
| 类型安全 | 强类型,编译期检查 | 弱类型,运行期检查(id) |
| 捕获变量 | 自动捕获,按值/按引用 | 按值/按引用(__block) |
| 生命周期管理 | ARC + 编译器控制 | ARC + runtime 控制 |
| 语法 | { params in ... } | ^{ ... },语法糖生成对象 |
都是利用LLVM 开发的语言,Swift比 OC 利用了更多编译期特性,为什么呢?
语言设计的目标不同
-
Objective-C:
- 设计年代较早(1980s~1990s),语言特性以 动态运行时 为中心
- 大量依赖 runtime(如
objc_msgSend、isa、method swizzling) - 编译器在编译期做的优化有限,大部分类型检查、方法调用都是 运行时解析
-
Swift:
- 现代语言设计(2014 年推出),目标是 安全、高性能、静态类型
- 大量使用 编译期类型检查、泛型、值类型优化
- Swift 的类型系统复杂且丰富,尽量在编译期就做类型检查、方法调用绑定、内存布局优化
类型系统差异
| 特性 | Objective-C | Swift |
|---|---|---|
| 类型检查 | 运行时 (id, dynamic) | 编译期 + 泛型安全 |
| 可空性 (nullability) | 运行时 nil 检查 | 编译期 Optional 类型 |
| 泛型 | 通过 id + runtime 检查 | 编译期泛型,零开销 |
| 协议 & 方法调度 | runtime 消息发送 | 编译期可静态派发,支持动态派发 |
Swift 编译器可以在编译期生成更多优化代码,例如:
- 内联函数
- 泛型类型特化(specialization)
- 协议 witness table 静态绑定
LLVM 的利用程度
-
Objective-C 编译器生成的 LLVM IR 中,很多方法调用是通过
objc_msgSend,运行时才解析 -
Swift 编译器生成的 LLVM IR 中:
- 值类型(struct、enum)可以 直接在栈上操作
- 函数调用可以 静态绑定
- 泛型函数可以 专门化生成,避免运行时开销
总结:Swift 利用更多编译期特性,是因为语言设计偏向 静态类型 + 安全 + 高性能,而 Objective-C 偏向 动态性 + 兼容性 + 灵活性
内存管理与 ARC
- Objective-C 的 ARC 主要依赖 编译器插入 retain/release,仍有很多 runtime 支持
- Swift 的 ARC 更紧密结合编译期类型,编译器能静态分析对象生命周期,更少依赖 runtime