【iOS – OC】OC基礎-單例的實現 & 提醒自己注意多執行緒問題

二師兄發表於2019-01-19

做客戶端開發應當時刻考慮多執行緒問題。我最初是做前端開發的,在這方面考慮得往往不夠。謹記。

單例的常見寫法

單例的常見寫法其實就兩種

1. 依賴鎖

+ (id)sharedInstance {  
    static testClass *sharedInstance = nil;  
    @synchronized(self) {  
        if (!sharedInstance) {  
            sharedInstance = [[self alloc] init];  
        }  
    }  
    return sharedInstance;  
}

2. 依賴dispatch_once

+ (id)sharedInstance {  
    static testClass *sharedInstance = nil;  
    static dispatch_once_t once;  
    dispatch_once(&once, ^{  
        sharedInstance = [[self alloc] init];  
    });  
    return sharedInstance;  
}

dispatch_once的寫法更推薦一些。一方面是效能上好一點,另一方面是語義上更直觀。once,執行一次嘛。

不管是用鎖還是dispatch_once,本質上都是為了避免單例建立過程出現執行緒安全問題。

更進一步,我們經常會有懶載入某些屬性的寫法:

- (id<InterfaceEngineA>)engineA
{
    if (_engineA == nil) {
        _engineA = [EngineA new];
    }
    
    return _engineA;
}

其實跟單例的實現是類似的,這種時候要格外注意執行緒安全問題。如果存在多執行緒場景,一定要做好保護

- (id<InterfaceEngineA>)engineA
{
    @synchronized(self) {
        if (_engineA == nil) {
            _engineA = [EngineA new];
        }
    }
    return _engineA;
}

一些廢話

多執行緒問題的表現可能是各種各樣難以預料的。這裡我遇到的是,_engineA在多執行緒場景下小概率被重複建立,其例項1在init時註冊了網路層命令字cmd1的回包,而這個網路層框架的實現是,只接受第一個註冊這一命令字的物件。導致例項2註冊失敗。後面呼叫例項2傳送請求,回包都被例項1接收了。從日誌上看,一切都挺正常的。但是下次取資料就是取不到。

這個bug第一次提過來的時候,沒分析出根本原因,只在表面上做了保護。結果第二次提過來才真正改掉。

丟人吶。還是要好好學習才是。

相關文章