Lock
無並發,不編碼。而只要一說到多線程或者並發的代碼,我們可能就很難繞開對於鎖的討論。簡單來說,為了在不同線程中安全地訪問同一個資源,我們需要這些訪問順序進行。Cocoa 和 Objective-C 中加鎖的方式有很多,但是其中在日常開發中最常用的應該是 @synchronized,這個關鍵字可以用來修飾一個變數,並為其自動加上和解除互斥鎖。這樣,可以保證變數在作用範圍內不會被其他線程改變。舉個例子,如果我們有一個方法接受參數,需要這個方法是安全執行緒的話,就需要在參數上加鎖:
- (void)myMethod:(id)anObj {
@synchronized(anObj) {
// 在括弧內 anObj 不會被其他線程改變
}
}
如果沒有鎖的話,一旦 anObj 的內容被其他線程修改的話,這個方法的行為很可能就無法預測了。
但是加鎖和解鎖都是要消耗一定效能的,因此我們不太可能為所有的方法都加上鎖。另外其實在一個 app 中可能會涉及到多線程的部分是有限的,我們也沒有必要為所有東西加上鎖。過多的鎖不僅沒有意義,而且對於多線程編程來說,可能會產生很多像死結這樣的陷阱,也難以調試。因此在使用多線程時,我們應該盡量將保持簡單作為第一要務。
扯遠了,我們回到 @synchronized 上來。雖然這個方法很簡單好用,但是很不幸的是在 Swift 中它已經 (或者是暫時) 不存在了。其實 @synchronized 在幕後做的事情是調用了 objc_sync 中的 objc_sync_enter 和 objc_sync_exit 方法,並且加入了一些異常判斷。因此,在 Swift 中,如果我們忽略掉那些異常的話,我們想要 lock 一個變數的話,可以這樣寫:
func myMethod(anObj: AnyObject!) {
objc_sync_enter(anObj)
// 在 enter 和 exit 之間 anObj 不會被其他線程改變
objc_sync_exit(anObj)
}
更進一步,如果我們喜歡以前的那種形式,甚至可以寫一個全域的方法,並接受一個閉包,來將 objc_sync_enter 和 objc_sync_exit 封裝起來:
func synchronized(lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
再結合 Swift 的尾隨閉包的語言特性,這樣,使用起來的時候就和 Objective-C 中很像了:
func myMethodLocked(anObj: AnyObject!) {
synchronized(anObj) {
// 在括弧內 anObj 不會被其他線程改變
}
}