iOS 單例模式 線程安全

一、核心概念

  1. 單例模式
    一個(gè)類在應(yīng)用生命周期內(nèi)只創(chuàng)建唯一一個(gè)實(shí)例,提供全局統(tǒng)一訪問入口。
  2. 線程安全
    多線程并發(fā)訪問時(shí),只創(chuàng)建一個(gè)實(shí)例,且屬性/方法讀寫不崩潰、數(shù)據(jù)不亂序。

二、單例模式 優(yōu)缺點(diǎn)(面試必背)

? 優(yōu)點(diǎn)

  1. 全局唯一:避免重復(fù)創(chuàng)建對象,節(jié)省內(nèi)存與資源
  2. 全局共享:跨頁面、跨模塊方便共享數(shù)據(jù)/狀態(tài)(用戶信息、網(wǎng)絡(luò)管理等)
  3. 統(tǒng)一管理:集中管理配置、資源、請求,便于維護(hù)
  4. 生命周期穩(wěn)定:程序運(yùn)行期間常駐內(nèi)存,不用頻繁創(chuàng)建銷毀

? 缺點(diǎn)

  1. 耦合度高:全局共享導(dǎo)致模塊強(qiáng)依賴,不利于解耦
  2. 測試?yán)щy:單例常駐內(nèi)存,單元測試數(shù)據(jù)無法隔離
  3. 內(nèi)存常駐:實(shí)例不會(huì)自動(dòng)釋放,過多單例會(huì)占用內(nèi)存
  4. 線程風(fēng)險(xiǎn):創(chuàng)建安全 ≠ 使用安全,可變屬性仍需加鎖
  5. 擴(kuò)展性差:不支持多實(shí)例,繼承與改造難度大

三、線程安全問題根源(錯(cuò)誤示范)

多線程并發(fā)時(shí)會(huì)創(chuàng)建多個(gè)實(shí)例,違背單例原則。

@implementation Singleton
+ (instancetype)sharedInstance {
    static Singleton *instance = nil;
    // 多線程可同時(shí)通過判斷,導(dǎo)致多次初始化
    if (instance == nil) {
        instance = [[self alloc] init];
    }
    return instance;
}
@end

問題核心:無線程同步,多線程同時(shí)判空,多次執(zhí)行 init。


四、生產(chǎn)級標(biāo)準(zhǔn)實(shí)現(xiàn)(官方推薦|絕對線程安全)

1. Objective-C 最優(yōu)實(shí)現(xiàn)(dispatch_once)

SafeSingleton.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SafeSingleton : NSObject <NSCopying, NSMutableCopying>

/// 單例全局訪問入口
+ (instancetype)sharedInstance;

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (instancetype)alloc NS_UNAVAILABLE;
+ (instancetype)allocWithZone:(struct _NSZone *)zone NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END

SafeSingleton.m

#import "SafeSingleton.h"

@implementation SafeSingleton

+ (instancetype)sharedInstance {
    static SafeSingleton *instance = nil;
    static dispatch_once_t onceToken;
    
    // 蘋果官方線程安全方案,代碼塊只執(zhí)行一次
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:nil] init];
    });
    
    return instance;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return self;
}

@end

2. Swift 最優(yōu)實(shí)現(xiàn)(static let)

import Foundation

class SafeSingleton: NSObject, NSCopying, NSMutableCopying {
    /// 編譯器自動(dòng)保證線程安全
    static let shared = SafeSingleton()
    
    private override init() {
        super.init()
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        return self
    }
    
    func mutableCopy(with zone: NSZone? = nil) -> Any {
        return self
    }
    
    @available(*, unavailable)
    class func new() -> Self {
        fatalError("禁止使用 new")
    }
}

五、單例屬性/方法 線程安全(高頻面試點(diǎn))

重要結(jié)論
實(shí)例創(chuàng)建線程安全 ≠ 業(yè)務(wù)方法線程安全!
單例中的可變屬性、讀寫操作必須加鎖!

DataManager(完整可運(yùn)行)

DataManager.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface DataManager : NSObject
+ (instancetype)sharedManager;
- (void)addData:(id)data;
- (void)removeDataAtIndex:(NSUInteger)index;
- (NSArray *)fetchAllData;
@end

NS_ASSUME_NONNULL_END

DataManager.m

#import "DataManager.h"

@interface DataManager ()
@property (nonatomic, strong) NSMutableArray *dataList;
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@end

@implementation DataManager

+ (instancetype)sharedManager {
    static DataManager *instance = nil;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        instance = [[super allocWithZone:nil] init];
        instance->_dataList = [NSMutableArray array];
        instance->_serialQueue = dispatch_queue_create("com.example.DataManager", DISPATCH_QUEUE_SERIAL);
    });
    return instance;
}

- (void)addData:(id)data {
    if (!data) return;
    dispatch_sync(self.serialQueue, ^{
        [self.dataList addObject:data];
    });
}

- (void)removeDataAtIndex:(NSUInteger)index {
    dispatch_sync(self.serialQueue, ^{
        if (index < self.dataList.count) {
            [self.dataList removeObjectAtIndex:index];
        }
    });
}

- (NSArray *)fetchAllData {
    __block NSArray *result;
    dispatch_sync(self.serialQueue, ^{
        result = [self.dataList copy];
    });
    return result;
}

@end

六、鎖實(shí)現(xiàn)方案(兼容舊版,不推薦)

1. @synchronized

@implementation LockSingleton
+ (instancetype)sharedInstance {
    static LockSingleton *instance = nil;
    @synchronized (self) {
        if (instance == nil) {
            instance = [[super allocWithZone:nil] init];
        }
    }
    return instance;
}
@end

2. pthread_mutex

#import <pthread/pthread.h>
@implementation PthreadSingleton
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+ (instancetype)sharedInstance {
    static PthreadSingleton *instance = nil;
    pthread_mutex_lock(&mutex);
    if (instance == nil) {
        instance = [[super allocWithZone:nil] init];
    }
    pthread_mutex_unlock(&mutex);
    return instance;
}
@end

七、iOS 單例 線程安全 高頻面試題(完整版)

1. 為什么普通單例不是線程安全的?

  • 多線程同時(shí)進(jìn)入 if (instance == nil) 分支
  • 多個(gè)線程同時(shí)執(zhí)行 [[self alloc] init]
  • 最終生成多個(gè)實(shí)例,破壞單例唯一性

2. iOS 中如何實(shí)現(xiàn)線程安全的單例?

  • OC:使用 dispatch_once(官方首選)
  • Swift:使用 static let(編譯器底層等價(jià) dispatch_once)
  • 不推薦:@synchronized、NSLock 等鎖方案(性能差)

3. dispatch_once 為什么線程安全?

  • 底層基于CPU 原子指令實(shí)現(xiàn)
  • 無鎖設(shè)計(jì),只在第一次執(zhí)行初始化
  • 后續(xù)線程直接讀取結(jié)果,性能極高
  • 保證代碼塊一生只執(zhí)行一次

4. Swift 單例為什么用 static let 就線程安全?

  • Swift 編譯器對 static let 做了安全優(yōu)化
  • 底層自動(dòng)插入 dispatch_once 邏輯
  • 保證初始化原子性、唯一性
  • 懶加載 + 線程安全,一步到位

5. 單例創(chuàng)建線程安全,就代表所有方法都安全嗎?

不是。

  • 單例創(chuàng)建安全只保證實(shí)例唯一
  • 內(nèi)部可變對象(NSMutableArray、NSMutableDictionary)仍非線程安全
  • 多線程同時(shí)讀寫會(huì)導(dǎo)致崩潰、數(shù)據(jù)錯(cuò)亂
  • 解決方案:加鎖 / GCD 串行隊(duì)列

6. 為什么不推薦用雙重檢查鎖(DCL)?

  • OC 內(nèi)存模型不保證 static 變量賦值的原子性
  • 可能出現(xiàn)半初始化對象,導(dǎo)致野指針、崩潰
  • 蘋果官方明確不推薦,存在高風(fēng)險(xiǎn)

7. 如何防止別人通過 alloc / init / copy 創(chuàng)建新實(shí)例?

  • OC:NS_UNAVAILABLE 禁用 init/new/alloc
  • OC:重寫 copyWithZone / mutableCopyWithZone 返回 self
  • Swift:private init() 私有化構(gòu)造方法

8. 單例的生命周期?

  • 單例被靜態(tài)變量持有,存儲(chǔ)在靜態(tài)存儲(chǔ)區(qū)
  • 生命周期伴隨整個(gè) App 進(jìn)程
  • App 退出前不會(huì)釋放
  • 無法手動(dòng)銷毀,慎用大量單例

9. 單例好還是全局變量好?

  • 單例:封裝性好、可控、支持延遲初始化、線程安全易實(shí)現(xiàn)
  • 全局變量:無封裝、污染全局作用域、不安全
  • 開發(fā)中優(yōu)先單例

10. 你在項(xiàng)目中怎么使用單例?

常用場景:

  • 用戶信息管理(UserManager)
  • 網(wǎng)絡(luò)請求管理(NetworkManager)
  • 音頻/播放器管理
  • 全局配置管理
  • 數(shù)據(jù)庫管理

八、避坑總結(jié)

  1. OC 首選 dispatch_once,Swift 首選 static let
  2. 禁止裸判空實(shí)現(xiàn)單例
  3. 禁止雙重檢查鎖
  4. 可變屬性必須用串行隊(duì)列/鎖保護(hù)
  5. 禁止外部手動(dòng)創(chuàng)建實(shí)例
  6. 單例不要濫用,避免內(nèi)存與耦合問題

九、最終一句話總結(jié)

  • 創(chuàng)建安全:OC = dispatch_once,Swift = static let
  • 使用安全:可變屬性讀寫必須串行化
  • 面試核心:線程安全原理、dispatch_once 機(jī)制、單例優(yōu)缺點(diǎn)、可變對象安全處理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容