OC基礎-單例的實現 & 提醒自己注意多執行緒問題
做客戶端開發應當時刻考慮多執行緒問題。我最初是做前端開發的,在這方面考慮得往往不夠。謹記。
單例的常見寫法
單例的常見寫法其實就兩種
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第一次提過來的時候,沒分析出根本原因,只在表面上做了保護。結果第二次提過來才真正改掉。
丟人吶。還是要好好學習才是。