iOS 单例模式

一、单例模式概述

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];  // 可能导致重复初始化或崩溃
}

原因:

  1. 可能被调用多次:子类访问时会触发父类的 +initialize
  2. 线程安全问题:虽然官方保证 +initialize 在每个类上只执行一次,但实现不当仍有风险
  3. 苹果官方不推荐:官方文档明确指出 +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 letclass let 属性的初始化是自动线程安全的:

// ✅ Swift 推荐的线程安全单例实现
class SafeSingleton {
    private init() {}
    static let shared = SafeSingleton()  // 自动线程安全
}

4.4 底层原理

Swift 的静态存储属性具有以下特性:

  1. 原子性初始化:系统保证初始化过程是原子的
  2. 全局 dispatch_once:底层使用 dispatch_once 机制(在 Objective-C 时代常用)
  3. 只执行一次:即使多个线程同时访问,也只会初始化一次

五、类型初始化和首次初始化时机

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) 机制,类加载分为两个阶段:

  1. 加载阶段(load):应用启动时,runtime 会加载所有类,并调用每个类的 +load 方法
  2. 初始化阶段(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 关键点回顾

  1. 单例模式确保类只有一个实例
  2. 饱汉模式:首次使用时初始化(懒加载)——Swift 和 Objective-C 都支持
  3. 饿汉模式:应用启动时初始化——仅 Objective-C 支持,Swift 不支持
  4. 线程安全:Swift 的 static let 自动保证;Objective-C 需要使用 dispatch_once
  5. 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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、核心概念 单例模式一个类在应用生命周期内只创建唯一一个实例,提供全局统一访问入口。 线程安全多线程并发访问时,...
    巴糖阅读 98评论 0 0
  • 本文内容:iOS的单例模式NSUserDefaults的使用总结:iOS单例模式 and NSUserDefaul...
    Kingsleeeey阅读 1,818评论 0 5
  • 单例模式,由于其简单好用容易理解、同时在出问题时也容易定位的特点,在开发中经常用到的一个设计模式,本文主要分享我在...
    小時間光阅读 50,484评论 13 71
  • IOS单例模式(Singleton)单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例...
    Living_元宝儿阅读 1,376评论 1 6
  • 简介: 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统...
    RunnerFL阅读 746评论 0 0

友情链接更多精彩内容