一、单例模式概述
1.1 定义
单例模式(Singleton Pattern) 是一种设计模式,确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点来获取这个实例。
1.2 核心特点
- 唯一性:类只能有一个实例
- 全局访问:通过统一的接口获取实例
- 延迟或立即初始化:根据实现方式决定何时创建实例
1.3 在iOS中的应用场景
单例模式在iOS开发中非常常见,典型应用场景包括:
- 管理全局状态:如用户登录状态管理
-
共享资源访问:如网络请求管理器(
URLSession) - 配置管理:如全局配置信息存储
- 服务类:如日志服务、数据缓存服务
1.4 单例模式的两种实现方式
单例模式根据初始化时机的不同,可以分为两种实现方式:
| 方式 | 名称 | 初始化时机 | 特点 | 适用语言 |
|---|---|---|---|---|
| 方式一 | 饱汉模式(懒加载) | 首次使用时才创建实例 | 节省内存,按需加载 | Swift / Objective-C |
| 方式二 | 饿汉模式(立即加载) | 应用启动时立即创建实例 | 启动即就绪,可能占用内存 | 仅 Objective-C |
重要说明:
-
Swift 只有一种单例实现方式:所有
static let都是懒加载(饱汉模式),不存在饿汉模式 - Objective-C 两种方式都支持:可以选择懒加载或立即加载
为什么有两种方式?
- 饱汉模式:适合初始化开销较大、或不确定是否会使用的场景
- 饿汉模式:适合需要立即就绪、或初始化开销很小的场景(仅 Objective-C)
二、饱汉模式(懒加载模式)
2.1 定义
饱汉模式(Lazy Initialization / Lazy Singleton),也称为懒汉模式,指的是在第一次使用时才创建实例,而不是在应用启动时就创建。
2.2 Swift实现示例
class LazySingleton {
// 私有初始化器,防止外部创建实例
private init() {}
// 静态属性,使用闭包实现懒加载(Swift 1.2+ 自动线程安全)
static let shared: LazySingleton = {
print("LazySingleton 首次初始化")
return LazySingleton()
}()
}
2.3 特点分析
| 特性 | 说明 |
|---|---|
| 初始化时机 | 首次访问 shared 属性时 |
| 内存效率 | 高,不需要时不占用内存 |
| 性能 | 首次访问时有初始化开销 |
| 线程安全 | Swift 1.2+ 的 static let 是线程安全的 |
2.4 使用示例
// 第一次访问时才会创建实例
let instance1 = LazySingleton.shared // 输出: "LazySingleton 首次初始化"
let instance2 = LazySingleton.shared // 不会再次初始化
print(instance1 === instance2) // true,指向同一个实例
2.5 Objective-C 实现(饱汉模式)
Objective-C 中默认使用饱汉模式,通过 dispatch_once 实现线程安全的懒加载:
// LazySingleton.h
@interface LazySingleton : NSObject
+ (instancetype)sharedInstance;
@end
// LazySingleton.m
@implementation LazySingleton
+ (instancetype)sharedInstance {
static LazySingleton *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"LazySingleton 首次初始化");
instance = [[self alloc] init];
});
return instance;
}
@end
特点:
- 实例在首次调用
sharedInstance时创建 - 使用
dispatch_once保证线程安全(必须) - 初始化逻辑放在
dispatch_once块内
三、饿汉模式(立即加载模式)
3.1 定义
饿汉模式(Eager Initialization / Eager Singleton),指的是在应用启动时就立即创建实例,无论是否会被使用。
⚠️ 重要提示:Swift 不支持饿汉模式!Swift 的所有
static let都是懒加载(饱汉模式),无法在应用启动时初始化。饿汉模式仅适用于 Objective-C。
3.2 Objective-C 实现(饿汉模式)
苹果官方推荐使用 +load 方法来实现饿汉模式:
// EagerSingleton.h
@interface EagerSingleton : NSObject
+ (instancetype)sharedInstance;
@end
// EagerSingleton.m
@implementation EagerSingleton
static EagerSingleton *instance = nil;
// +load 在应用启动时执行(所有类加载完成后、main 之前)
+ (void)load {
NSLog(@"EagerSingleton 初始化");
instance = [[self alloc] init];
}
+ (instancetype)sharedInstance {
return instance;
}
@end
3.3 特点分析
| 特性 | 说明 |
|---|---|
| 初始化时机 | 应用启动时(main 函数之前) |
| 内存效率 | 较低,启动时即占用内存 |
| 性能 | 首次访问时无初始化开销 |
| 线程安全 | 天然线程安全(系统保证) |
3.4 ❌ 不推荐使用 +initialize 的原因
错误示例:
// ❌ 不推荐!+initialize 可能被调用多次
+ (void)initialize {
instance = [[self alloc] init]; // 可能导致重复初始化或崩溃
}
原因:
-
可能被调用多次:子类访问时会触发父类的
+initialize -
线程安全问题:虽然官方保证
+initialize在每个类上只执行一次,但实现不当仍有风险 -
苹果官方不推荐:官方文档明确指出
+initialize不适合用于单例初始化
四、为什么需要线程安全?
4.1 问题场景
在多线程环境下,如果使用非线程安全的单例实现,可能会导致以下问题:
// ⚠️ 非线程安全的懒加载实现(仅作示例,不推荐使用)
class UnsafeSingleton {
private init() {}
private static var instance: UnsafeSingleton?
static func getInstance() -> UnsafeSingleton {
if instance == nil {
// 线程A和线程B可能同时进入这里
instance = UnsafeSingleton()
}
return instance!
}
}
4.2 竞态条件分析
时间线:
T1: 线程A检查 instance == nil → true
T2: 线程B检查 instance == nil → true
T3: 线程A创建实例 instance = UnsafeSingleton()
T4: 线程B创建实例 instance = UnsafeSingleton() // ❌ 创建了第二个实例!
4.3 Swift中的线程安全保证
从 Swift 1.2 开始,static let 和 class let 属性的初始化是自动线程安全的:
// ✅ Swift 推荐的线程安全单例实现
class SafeSingleton {
private init() {}
static let shared = SafeSingleton() // 自动线程安全
}
4.4 底层原理
Swift 的静态存储属性具有以下特性:
- 原子性初始化:系统保证初始化过程是原子的
-
全局 dispatch_once:底层使用
dispatch_once机制(在 Objective-C 时代常用) - 只执行一次:即使多个线程同时访问,也只会初始化一次
五、类型初始化和首次初始化时机
5.1 Swift 中的类型初始化机制
在 Swift 中,不存在 "类加载" 概念(这是 Objective-C 的术语)。Swift 使用类型初始化(type initialization)机制:
5.1.1 类型初始化时机
类型会在首次被使用时触发初始化,触发条件包括:
- 访问类型的静态属性或方法
- 创建类型的实例
- 引用类型(如
type(of:))
5.1.2 静态属性初始化顺序
class MyClass {
// 静态存储属性 - 首次访问时初始化(懒加载)
static let staticProperty = {
print("静态存储属性初始化")
return "value"
}()
// 静态计算属性 - 每次访问时计算
static var computedProperty: String {
print("计算属性访问")
return "computed"
}
// 实例属性
let instanceProperty = "instance"
}
// 首次访问静态存储属性 → 触发初始化(懒加载)
let value = MyClass.staticProperty
// 输出: "静态存储属性初始化"
✅ 重要事实:Swift 的所有
static let存储属性都是懒加载的,首次访问时才初始化,不是应用启动时初始化。
5.2 Objective-C 中的类加载机制
Objective-C 使用 类加载(class loading) 机制,与 Swift 的类型初始化机制不同:
5.2.1 类加载时机
Objective-C 使用 运行时(Runtime) 机制,类加载分为两个阶段:
-
加载阶段(load):应用启动时,runtime 会加载所有类,并调用每个类的
+load方法 -
初始化阶段(initialize):类首次被使用时,runtime 会调用
+initialize方法
@implementation MyOCClass
// load:应用启动时调用,比 main 函数还早
+ (void)load {
NSLog(@"MyOCClass loaded");
}
// initialize:类首次被使用时调用
+ (void)initialize {
NSLog(@"MyOCClass initialized");
}
@end
5.2.2 关键区别
| 特性 | Swift | Objective-C |
|---|---|---|
| 加载时机 | 首次使用时动态加载 | 应用启动时统一加载 |
| 初始化方法 | 无显式初始化方法 |
+load 和 +initialize
|
| 调用顺序 | 按需加载 | 按编译顺序加载 |
| 静态存储属性 | 首次访问时初始化(懒加载) | 无原生静态存储属性 |
5.2.3 +load vs +initialize 详细对比
这两个是 Objective-C 运行时提供的特殊类方法,在应用启动和类加载过程中扮演重要角色:
| 特性 | +load |
+initialize |
|---|---|---|
| 调用时机 | 应用启动时(main 之前) |
类首次被使用时 |
| 调用顺序 | 父类 → 子类 | 父类 → 子类 |
| 调用次数 | 保证只调用一次 | 可能被调用多次(子类触发) |
| 线程安全 | ✅ 系统保证 | ✅ 系统保证每个类只执行一次 |
| 性能影响 | 会增加启动时间 | 不影响启动时间 |
| 推荐用途 | 单例初始化、方法交换 | 延迟初始化、配置 |
| 是否可继承 | 是(但每个类独立调用) | 是(子类会触发父类) |
5.2.4 ⚠️ +initialize 的陷阱
// ❌ 错误示例
@implementation ParentClass
+ (void)initialize {
instance = [[self alloc] init]; // 如果子类没有重写,会被调用多次!
}
@end
@implementation ChildClass : ParentClass
// 没有重写 +initialize
@end
// 调用 [ChildClass sharedInstance] 会触发:
// 1. ParentClass 的 +initialize(self = ParentClass)
// 2. ChildClass 的 +initialize(self = ChildClass)→ 再次创建实例!
正确做法:
// ✅ 正确示例
+ (void)initialize {
if (self == [MyClass class]) { // 只在当前类时执行
instance = [[self alloc] init];
}
}
5.2.5 应用场景建议
| 场景 | 推荐方法 |
|---|---|
| 饿汉模式单例 |
+load(苹果官方推荐) |
| 饱汉模式单例 | dispatch_once |
| 方法交换(Method Swizzling) | +load |
| 延迟初始化 |
+initialize(需加类判断) |
| 启动时必须就绪的服务 | +load |
| 按需加载的配置 |
+initialize 或懒加载 |
5.3 Swift 类型初始化 vs Objective-C 类加载对比
Swift:
┌─────────────────────────────────────────────────────────────┐
│ 应用启动 │
│ ↓ │
│ 类型不初始化,静态属性不初始化 │
│ ↓ │
│ 首次访问 static let(如 MyClass.shared) │
│ ↓ │
│ 触发类型初始化 + 静态属性初始化 → 返回实例 │
└─────────────────────────────────────────────────────────────┘
Objective-C:
┌─────────────────────────────────────────────────────────────┐
│ 应用启动 │
│ ↓ │
│ 加载所有类 → 调用 +load 方法 │
│ ↓ │
│ 类已加载,但单例实例未创建(饱汉模式) │
│ ↓ │
│ 首次调用 [MyClass sharedInstance] │
│ ↓ │
│ dispatch_once 执行 → 创建实例(饱汉模式) │
└─────────────────────────────────────────────────────────────┘
✅ 重要事实:Swift 没有"应用启动时初始化静态成员"的行为,所有
static let都是在首次访问时才初始化。
5.4 饿汉模式 vs 饱汉模式的初始化时机对比
| 模式 | 初始化时机 | 触发条件 | 适用语言 |
|---|---|---|---|
| 饿汉模式 | 应用启动时(main 之前) |
应用启动 | 仅 Objective-C |
| 饱汉模式 | 首次使用时 | 首次访问特定的静态属性(如 shared) |
Swift / Objective-C |
✅ 重要提示:Swift 中不存在饿汉模式,所有
static let都是饱汉模式(懒加载)。
5.5 延迟初始化的语法支持
Swift 提供了 lazy 关键字来实现延迟初始化:
class LazyClass {
// 实例属性的懒加载
lazy var lazyProperty: ExpensiveObject = {
return ExpensiveObject()
}()
// 静态属性的懒加载(闭包形式)
static let shared: LazyClass = {
let instance = LazyClass()
// 可以添加额外的初始化逻辑
return instance
}()
}
六、iOS单例的最佳实践
6.1 推荐的单例实现方式
final class AppManager {
// 1. 私有初始化器,防止外部创建
private init() {
// 初始化逻辑
setup()
}
// 2. 静态共享实例(线程安全)
static let shared = AppManager()
// 3. 私有设置方法
private func setup() {
// 配置初始化
}
// 4. 业务方法
func doSomething() {
// ...
}
}
6.2 使用 final 关键字的原因
- 防止子类化:避免子类破坏单例的唯一性
- 性能优化:编译器可以进行更多优化
6.3 常见误区
❌ 错误:使用全局变量
// 不推荐 - 无法控制初始化时机
let globalManager = AppManager()
❌ 错误:使用 static var 而非 static let
// 不推荐 - 可变属性可能被意外修改
class BadSingleton {
static var shared = BadSingleton() // var 是可变的
private init() {}
}
// 可能被恶意修改
BadSingleton.shared = BadSingleton() // ❌ 破坏了单例
✅ 正确:使用 static let
class GoodSingleton {
static let shared = GoodSingleton() // let 是不可变的
private init() {}
}
八、总结
8.1 模式选择建议
| 场景 | 推荐模式 | 适用语言 |
|---|---|---|
| 需要延迟加载、节省内存 | 饱汉模式 | Swift / Objective-C |
| 初始化开销小、需要立即就绪 | 饿汉模式 | 仅 Objective-C |
| 多线程环境 | 饱汉模式(Swift 自动线程安全) | Swift / Objective-C |
✅ 重要结论:Swift 只有一种单例模式——饱汉模式(懒加载)。饿汉模式仅适用于 Objective-C。
8.2 关键点回顾
- 单例模式确保类只有一个实例
- 饱汉模式:首次使用时初始化(懒加载)——Swift 和 Objective-C 都支持
- 饿汉模式:应用启动时初始化——仅 Objective-C 支持,Swift 不支持
-
线程安全:Swift 的
static let自动保证;Objective-C 需要使用dispatch_once - Swift 静态成员:永远是懒加载,首次访问时才初始化
8.3 设计原则
单例模式虽然方便,但过度使用可能导致:
- 测试困难:单例难以进行单元测试的 mock
- 耦合度高:全局状态增加了代码耦合
- 内存问题:单例生命周期与应用相同
建议:仅在真正需要全局唯一实例时使用单例模式。
8.4 Swift vs Objective-C 综合对比
| 特性 | Swift | Objective-C |
|---|---|---|
| 饱汉模式 | static let shared = MyClass() |
dispatch_once + 静态变量 |
| 饿汉模式 | 不支持 |
+load 方法(苹果官方推荐) |
| 线程安全 |
static let 自动保证 |
必须使用 dispatch_once
|
| 代码简洁度 | 一行代码搞定 | 需要手动管理多个部分 |
| 类型初始化时机 | 首次访问时动态初始化 | 应用启动时统一加载 |
| 静态成员初始化 | 首次访问时(懒加载) | 无原生静态存储属性 |
附录:完整示例代码
Swift 单例实现(饱汉模式)
import Foundation
// MARK: - Swift 饱汉模式(懒加载)
final class AppManager {
// 私有初始化器,防止外部创建实例
private init() {
print("AppManager initialized at \(Date())")
}
// 静态属性,首次访问时初始化(自动线程安全)
static let shared = AppManager()
// 业务方法示例
func setup() {
print("AppManager setup completed")
}
}
// MARK: - 使用示例
func swiftExample() {
// 首次访问时才初始化
let manager1 = AppManager.shared // 输出初始化信息
let manager2 = AppManager.shared // 不再初始化
print(manager1 === manager2) // true,同一个实例
}
// 执行示例
swiftExample()
Objective-C 单例实现
// SingletonManager.h
@interface SingletonManager : NSObject
+ (instancetype)sharedInstance;
@end
// SingletonManager.m
@implementation SingletonManager
+ (instancetype)sharedInstance {
static SingletonManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"SingletonManager initialized");
instance = [[self alloc] init];
});
return instance;
}
@end
Objective-C 饿汉模式(立即加载)
苹果官方推荐使用 +load 方法:
// EagerManager.h
@interface EagerManager : NSObject
+ (instancetype)sharedInstance;
@end
// EagerManager.m
@implementation EagerManager
static EagerManager *instance = nil;
// +load 在应用启动时执行(main 函数之前)
+ (void)load {
NSLog(@"EagerManager initialized (before main)");
instance = [[self alloc] init];
}
+ (instancetype)sharedInstance {
return instance;
}
@end