第六章 block與GCD(下)

41.多用派發(fā)隊列,少用同步鎖

在Objective-C中陷猫,如果有多個線程要執(zhí)行同一份代碼秫舌,那么有時可能會出問題的妖。這種情況下,通常要使用鎖來實現(xiàn)某種同步機制足陨。在GCD出現(xiàn)之前嫂粟,有兩種辦法,第一種是采用內(nèi)置的“同步塊”(synchronization block):

//第一種方式:@synchronized
-(void)synchronizedMethod{
    @synchronized (self) {
        //Safe
    }
}

這種寫法會根據(jù)給定的對象墨缘,自動創(chuàng)建一個鎖星虹,并等待塊中的代碼執(zhí)行完畢。執(zhí)行到這段代碼結(jié)尾處镊讼,鎖就釋放了宽涌。在本例中,同步行為所針對的對象是self蝶棋。這么寫通常沒錯卸亮,因為它可以保證每個對象實例都能不受干擾地運行其synchronizedMethod方法。然而玩裙,濫用@synchronized(self)則會降低代碼效率兼贸,因為共用同一個鎖的那些同步塊,都必須按順序執(zhí)行吃溅。若是在self對象上頻繁加鎖溶诞,那么程序可能要等另一段與此無關的代碼執(zhí)行完畢,才能繼續(xù)執(zhí)行當前代碼决侈,這樣做其實并沒有必要螺垢。

另一個辦法是直接使用NSLock對象:

//第二種方式:NSLock
-(void)synchronizedMethod{
    [_lock lock];
    //Safe
    [_lock unlock];
}

也可以使用NSRecursiveLock這種“遞歸鎖”,線程能夠多次持有該鎖赖歌,而不會出現(xiàn)死鎖現(xiàn)象甩苛。

這兩種方法都很好,不過也有其缺陷俏站。比方說,在極端情況下痊土,同步塊會導致死鎖肄扎,另外,其效率也不見得很高赁酝,而如果直接使用鎖對象的話犯祠,一旦遇到死鎖,就會非常麻煩酌呆。

替代方案就是使用GCD衡载,它能以更簡單、更高效的形式為代碼加鎖隙袁。比方說痰娱,屬性就是開發(fā)者經(jīng)常需要同步的地方弃榨,這種屬性需要做成“原子的”。用atomic特質(zhì)來修飾屬性梨睁,即可實現(xiàn)這一點(第6條)鲸睛。而開發(fā)者如果想自己來編寫訪問方法的話,那么通常會這樣寫:

-(NSString *)someString{
    @synchronized (self) {
        return _someString;
    }
}

-(void)setSomeString:(NSString *)someString{
    @synchronized (self) {
        _someString  = someString;
    }
}

濫用@syncronized(self)會很危險坡贺,因為所有同步塊都會彼此搶奪同一個鎖官辈。要是有很多屬性都這么寫的話,那么每個屬性的同步塊都要等其他所有同步塊執(zhí)行完畢才能執(zhí)行遍坟,這也許并不是開發(fā)者想要的效果拳亿。我們只是想令每個屬性各自獨立地同步。

這么做雖然能提供某種程度的“線程安全”愿伴,但卻無法保證訪問該對象時絕對是線程安全的肺魁。當然,訪問屬性的操作確實是“原子的”公般。使用屬性時万搔,必定能從中獲取到有效值,然而在同一個線程上多次調(diào)用獲取方法官帘,每次獲取到的結(jié)果卻未必相同瞬雹。在兩次訪問操作之間,其他線程可能會寫入新的屬性值刽虹。

有種簡單而高效的辦法可以代替同步塊或鎖對象酗捌,那就是使用“串行同步隊列”(serial synchronization queue)。將讀取操作及寫入操作都安排在同一個隊列里涌哲,即可保證數(shù)據(jù)同步胖缤。其用法如下:

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);


-(NSString *)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

-(void)setSomeString:(NSString *)someString{
    dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

此模式的思路是:把設置操作與獲取操作都安排在序列化的隊列里執(zhí)行,這樣的話阀圾,所有針對屬性的訪問操作就都同步了哪廓。為了使塊代碼能夠設置局部變量,獲取方法中用到了__block語法初烘。全部加鎖任務都在GCD中處理涡真,而GCD是在相當深的底層來實現(xiàn)的,于是能夠做許多優(yōu)化肾筐。

還可以進一步優(yōu)化哆料,設置方法并不一定非得是同步的。設置實例變量所用的塊吗铐,并不需要向設置方法返回什么值东亦。也就是說,設置方法的代碼可以改成這樣:

-(void)setSomeString:(NSString *)someString{
    dispatch_async(_syncQueue, ^{
        _someString = someString;
    });
}

這次只是把同步派發(fā)改成了異步派發(fā)唬渗,從調(diào)用者的角度來看典阵,這個小改動可以提升設置方法的執(zhí)行速度奋渔,而讀取操作與寫入操作依然會按順序執(zhí)行。但這么改有個壞處:如果你測一下程序性能萄喳,那么可能會發(fā)現(xiàn)這種寫法比原來慢卒稳,因為執(zhí)行異步派發(fā)時,需要拷貝塊他巨。若拷貝塊所用的時間明顯超過執(zhí)行塊所花的時間充坑,則這種做法比原來慢。由于這里所舉的例子很簡單染突,所以改完之后很可能會變慢捻爷。然而,若是派發(fā)給隊列的塊要執(zhí)行更為繁重的任務份企,那么仍然可以考慮這種備選方案也榄。

多個獲取方法可以并發(fā)執(zhí)行,而獲取方法與設置方法之間不能并發(fā)執(zhí)行司志,利用這個特點甜紫,還能寫出更快一些的代碼來。此時正可以體現(xiàn)出GCD寫法的好處骂远。用同步塊或鎖對象囚霸,是無法輕易實現(xiàn)出下面這種方案的。這次不用串行隊列激才,而改用并發(fā)隊列(concurrent queue):

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


-(NSString *)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

-(void)setSomeString:(NSString *)someString{
    dispatch_async(_syncQueue, ^{
        _someString = someString;
    });
}

像現(xiàn)在這樣寫代碼拓型,還無法正確實現(xiàn)同步。所有讀取操作與寫入操作都會在同一個隊列上執(zhí)行瘸恼,不過由于是并發(fā)隊列劣挫,所以讀取與寫入操作可以隨時執(zhí)行。而我們恰恰不想讓這些操作隨意執(zhí)行东帅。此問題用一個簡單的GCD功能即可解決压固。它就是柵欄(barrier)。下列函數(shù)可以向隊列中派發(fā)塊靠闭,將其作為柵欄使用:

void dispatch_barrier_async(dispatch_queue_t queue,
 dispatch_block_t block);

void dispatch_barrier_sync(dispatch_queue_t queue,
 dispatch_block_t block);

在隊列中邓夕,柵欄塊必須單獨執(zhí)行,不能與其他塊并行阎毅。這只對并發(fā)隊列有意義,因為串行隊列中的塊總是按順序逐個來執(zhí)行的点弯。并發(fā)隊列如果發(fā)現(xiàn)接下來要處理的塊是個柵欄塊(barrier block),那么就一直要等當前所有并發(fā)塊都執(zhí)行完畢扇调,才會單獨執(zhí)行這個柵欄。待柵欄執(zhí)行過后抢肛,再按正常方式繼續(xù)向下處理狼钮。

在本例中碳柱,可以用柵欄塊來實現(xiàn)屬性的設置方法。在設置方法中使用了柵欄塊之后熬芜,對屬性的讀取操作依然可以并發(fā)執(zhí)行莲镣,但是寫入操作卻必須單獨執(zhí)行了。下圖演示的這個隊列中涎拉,有許多讀取操作瑞侮,而且還有一個寫入操作:

實現(xiàn)代碼很簡單:

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

-(NSString *)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

-(void)setSomeString:(NSString *)someString{
    dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}

這種做法肯定比使用串行隊列要快。注意鼓拧,設置函數(shù)也可以改用同步的柵欄塊來實現(xiàn)半火,那樣做可能會更高效。最好還是測一測每種做法的性能季俩,然后從中選出最適合當前場景的方案钮糖。

要點:

  • 派發(fā)隊列可用來表述同步語義,這種做法要比使用@synchronized塊或NSLock對象更簡單酌住。
  • 將同步與異步派發(fā)結(jié)合起來店归,可以實現(xiàn)與普通加鎖機制一樣的同步行為,而這么做卻不會阻塞執(zhí)行異步派發(fā)的線程酪我。
  • 使用同步隊列及柵欄塊消痛,可以令同步行為更加高效。

42.多用GCD祭示,少用preformSelector系列方法

Objective-C本質(zhì)上是一門非常動態(tài)的語言肄满,NSObject定義了幾個方法,令開發(fā)者可以隨意調(diào)用任何方法质涛。這幾個方法可以推遲執(zhí)行方法調(diào)用稠歉,也可以指定運行方法所用的線程。這些功能原來很有用汇陆,但是在出現(xiàn)了大中樞派發(fā)及塊這樣的新技術(shù)之后怒炸,就顯得不那么必要了。雖然有些代碼還是會經(jīng)常用到它們毡代,但是盡量避開為好阅羹。

這其中最簡單的是“performSelector:”。該方法的簽名如下教寂,它接受一個參數(shù)捏鱼,就是要執(zhí)行的那個選擇子:

- (id)performSelector:(SEL)aSelector;

該方法與直接調(diào)用選擇子等效。所以下面兩行代碼的執(zhí)行效果相同:

[object performSelector:@selector(selectorName)];
[object selectorName];

這種方式看上去似乎多余酪耕。如果某個方法只是這么來調(diào)用的話导梆,那么此方式確實多余。然而,如果選擇子是在運行期決定的看尼,那么就能體現(xiàn)出此方法的強大之處了递鹉。這就等于在動態(tài)綁定之上再次使用動態(tài)綁定,因而可以實現(xiàn)出下面這種功能:

SEL selecotr;
if(/*some condition*/){
    selecotr = @selector(foo);
}else if (/*some other condition*/){
    selecotr = @selector(bar);
}else{
    selecotr = @selector(baz);
}
[object performSelector:selecotr];

這種編程方式極為靈活藏斩,經(jīng)初锝幔可用來簡化復雜的代碼。還有一種用法狰域,就是先把選擇子保存起來媳拴,等某個事件發(fā)生之后再調(diào)用。不管哪種用法北专,編譯器都不知道要執(zhí)行的選擇子是什么禀挫,這必須到了運行期才能確定。然而拓颓,使用此特性的代價是语婴,如果在ARC下編譯代碼,那么編譯器就會發(fā)出如下警告信息:

warning:performSelector may cause a leak because its selector
is unknown [-Warc-performSelector-leaks]

原因在于:編譯器并不知道將要調(diào)用的選擇子是什么驶睦,因此砰左,也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚场航。而且缠导,由于編譯器不知道方法名,所以就沒辦法運用ARC的內(nèi)存管理規(guī)則來判定返回值是不是應該釋放溉痢。鑒于此僻造,ARC采用了比較謹慎的做法,就是不添加釋放操作孩饼。然而這么做可能導致內(nèi)存泄露髓削,因為方法在返回對象時可能已經(jīng)將其保留了。

考慮下面這段代碼:

SEL selecotr;
if(/*some condition*/){
    selecotr = @selector(newObject);
}else if (/*some other condition*/){
    selecotr = @selector(copy);
}else{
    selecotr = @selector(someProperty);
}
id ret = [object performSelector:selecotr];

如果調(diào)用的是前兩個選擇子之一镀娶,那么ret對象應由這段代碼來釋放立膛,而如果是第三個選擇子,則無須釋放梯码。不僅在ARC環(huán)境下應該如此宝泵,而在在非ARC環(huán)境下也應該這么做,這樣才算嚴格遵循了方法的命名規(guī)范轩娶。如果不使用ARC(此時編譯器就不發(fā)警告信息了)儿奶,那么在前兩種情況下需要手動釋放ret對象,而在后一種情況下則不需要釋放鳄抒。這個問題很容易忽視廓握,而且就算用靜態(tài)分析器搅窿,也很難偵測到隨后的內(nèi)存泄露。performSelector系列的方法之所以要謹慎使用隙券,這就是其中一個原因。

這些方法不甚理想闹司,另一個原因在于:返回值只能是void或?qū)ο箢愋陀樽小1M管所要執(zhí)行的選擇子也可以返回void,但是performSelector方法的返回值類型畢竟是id游桩。如果想返回整數(shù)或浮點等類型的值牲迫,那么就需要執(zhí)行一些復雜的轉(zhuǎn)換操作了,而這種轉(zhuǎn)換很容易出錯借卧。由于id類型表示指向任意Objective-C對象的指針盹憎,所以從技術(shù)上來講,只要返回值的大小和指針所占大小相同就行铐刘,也就是說:在32位架構(gòu)的計算機上陪每,可以返回任意32位大小的類型;而在64位架構(gòu)的計算機上镰吵,則可返回任意64位大小的類型檩禾。若返回值的類型為C語言結(jié)構(gòu)退,則不可使用performSelector方法疤祭。

performSelector還有如下幾個版本盼产,可以在發(fā)消息時順便傳遞參數(shù):

- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

比方說,可以用下面這個版本來設置對象中名為value的屬性值:

id object = /* an object with a property called value */;
id newValue = /* new value for the property */;
[object performSelector:@selector(setValue:) withObject:newValue];

這些方法貌似有用勺馆,但其實局限頗多戏售。由于參數(shù)類型是id,所以傳入的參數(shù)必須是對象才行草穆。如果選擇子所接受的參數(shù)是整數(shù)或浮點數(shù)灌灾,那就不能采用這些方法了。此外续挟,選擇子最多只能接受兩個參數(shù)紧卒,也就是調(diào)用“performSelector: withObject: withObject:”這個版本。而在參數(shù)不止兩個的情況下诗祸,則沒有對應的performSelector方法能夠執(zhí)行此種選擇子跑芳。

performSelector系列方法haunted有個功能,就是可以延后執(zhí)行選擇子直颅,或?qū)⑵浞旁诹硪粋€線程上執(zhí)行博个。下面列出了此方法中一些更為常用的版本:

- (void)performSelector:(SEL)aSelector 
    withObject:(nullable id)anArgument 
    afterDelay:(NSTimeInterval)delay;

- (void)performSelector:(SEL)aSelector 
    onThread:(NSThread *)thr 
    withObject:(nullable id)arg 
    waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector 
   withObject:(nullable id)arg 
   waitUntilDone:(BOOL)wait;

然而很快就會發(fā)覺,這些方法太過局限了功偿。例如盆佣,具備延后執(zhí)行功能的那些方法都無法處理帶有兩個參數(shù)的選擇子往堡。而能夠指定執(zhí)行線程的那些方法,則與之類似共耍,所以也不是特別通用虑灰。如果要用這些方法,就得把許多參數(shù)都打包到字典中痹兜,然后在受調(diào)用的方法里將其提取出來穆咐,這樣會增加開銷,而且還可能出bug字旭。

如果改用其他替代方案对湃,那就不受這些限制了。最主要的替代方案就是使用塊遗淳。而且拍柒,performSelector系列方法所提供的線程功能,都可以通過在大中樞派發(fā)機制中使用塊來實現(xiàn)屈暗。延后執(zhí)行可以用dispatch_after來實現(xiàn)拆讯,在另一個線程上執(zhí)行任務則可以通過dispatch_sync及dispatch_async來實現(xiàn)。

例如恐锦,要延后執(zhí)行某項任務往果,可以有下面兩種實現(xiàn)方式,而我們應該優(yōu)先考慮第二種:

//Using performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];
    
//Using dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0*NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    [self doSomething];
});

想把任務放在主線程上執(zhí)行一铅,也可以有下面兩種方式陕贮,而我們還是應該優(yōu)選后者:

//Using performSelectorOnMainThread: withObject: waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
    
//Using dispatch_async
//(or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

waitUntilDone為NO時相當于使用dispatch_async;waitUntilDone為YES時相當于使用dispatch_sync潘飘。

要點:

  • performSelector系列方法在內(nèi)存管理方面容易有疏失肮之。它無法確定將要執(zhí)行的選擇子具體是什么,因而ARC編譯器也就無法插入適當?shù)膬?nèi)存管理方法卜录。
  • performSelector系列方法所能處理的選擇子太過局限了戈擒,選擇子的返回值類型及發(fā)送給方法的參數(shù)個數(shù)都受到限制。
  • 如果想把任務放在另一個線程上執(zhí)行艰毒,那么最好不要用performSelector系列方法筐高,而是應該把任務封裝到塊里,然后調(diào)用大中樞派發(fā)機制的相關方法來實現(xiàn)丑瞧。

43.掌握GCD及操作隊列的使用時機

GCD技術(shù)確實很棒柑土,不過有時候采用標準系統(tǒng)庫的組件,效果會更好绊汹。一定要了解每項技巧的使用時機稽屏,如果選錯了工具,那么編出來的代碼就會難于維護西乖。

很少有其他技術(shù)能與GCD的同步機制想媲美狐榔。對于那些只需執(zhí)行一次的代碼來說坛增,也是如此,使用GCD的dispatch_once最為方便薄腻。然而收捣,在執(zhí)行后臺任務時接谨,GCD并不一定是最佳方式稀火。還有一種技術(shù)叫做NSOperationQueue,它雖然與GCD不同脸哀,但是卻與之相關嫁乘,開發(fā)者可以把操作以NSOperation子類的形式放在隊列中,而這些操作也能夠并發(fā)執(zhí)行球碉。其與GCD派發(fā)隊列有相似之處蜓斧,這并非巧合≌龆“操作隊列”(operation queue)在GCD之前就有了挎春,其中某些設計原理操作隊列而流行,GCD就是基于這些原理構(gòu)建的豆拨。實際上直奋,從iOS4與Mac OS X 10.6開始,操作隊列在底層是用GCD來實現(xiàn)的施禾。

在兩者的諸多差別中脚线,首先要注意:GCD是純C的API,而操作隊列則是Objective-C的對象弥搞。在GCD中邮绿,任務用塊來表示,而塊是個輕量級數(shù)據(jù)結(jié)構(gòu)攀例。與之相反船逮,“操作”(operation)則是個更為重量級的Objective-C對象。雖說如此粤铭,但GCD并不總是最佳方案挖胃。有時候采用對象所帶來的開銷微乎其微,使用完整對象所帶來的好處反而大大超過其缺點梆惯。

使用NSOperationQueue類的“addOperationWithBlock:”方法搭配NSBlockOperation類來使用操作隊列酱鸭,其語法與純GCD方式非常相似。使用NSOperation及NSOperationQueue的好處如下:

  • 取消某個操作加袋。如果使用操作隊列凛辣,那么想要取消操作是很容易的。運行任務之前职烧,可以在NSOperation對象上調(diào)用cancel方法扁誓,該方法會設置對象內(nèi)的標志位防泵,用以表明此任務不需執(zhí)行,不過蝗敢,已經(jīng)啟動的任務無法取消捷泞。若是不是使用操作隊列,而是把塊安排到GCD隊列寿谴,那就無法取消了锁右。那套架構(gòu)是“安排好任務之后就不管了”(fire and forget)。開發(fā)者可以在應用程序?qū)幼约簛韺崿F(xiàn)取消功能讶泰,不過這樣做需要編寫很多代碼咏瑟,而那些代碼其實已經(jīng)由操作隊列實現(xiàn)好了。
  • 指定操作間的依賴關系痪署。一個操作可以依賴其他多個操作码泞。開發(fā)者能夠指定操作之間的依賴體系,使特定的操作必須在另外一個操作順利執(zhí)行完畢后方可執(zhí)行狼犯。比方說余寥,從服務器端下載并處理文件的動作,可以用操作來表示悯森,而在處理其他文件之前宋舷,必須先下載“清單文件”(manifest file)。后續(xù)的下載操作瓢姻,都要依賴于先下載清單文件這一操作祝蝠。如果操作隊列允許并發(fā)的話,那么后續(xù)的多個下載操作就可以同時執(zhí)行汹来,但前提是它們所依賴的那個清單文件下載操作已經(jīng)執(zhí)行完畢续膳。
  • 通過鍵值觀測機制監(jiān)控NSOperation對象的屬性。NSOperation對象有許多屬性都適合通過鍵值觀測機制(KVO)來監(jiān)聽收班,比如可以通過isCancelled屬性來判斷任務是否已取消坟岔,又比如可以通過isFinished屬性來判斷任務是否已完成。如果想在某個任務變更其狀態(tài)時得到通知摔桦,或是想用比GCD更為精細的方式來控制所要執(zhí)行的任務社付,那么鍵值觀測機制會很有用。
  • 指定操作的優(yōu)先級邻耕。操作的優(yōu)先級表示此操作與隊列中的其他操作之間的優(yōu)先級關系鸥咖。優(yōu)先級高的操作先執(zhí)行,優(yōu)先級低的后執(zhí)行兄世。操作隊列的調(diào)度算法雖“不透明”啼辣,但必然是經(jīng)過一番深思熟慮才寫成的。反之御滩,GCD則沒有直接實現(xiàn)此功能的辦法鸥拧。GCD的隊列確實有優(yōu)先級党远,不過那是針對整個隊列來說的,而不是針對每個塊來說的富弦。而令開發(fā)者在GCD之上自己來編寫調(diào)度算法沟娱,又不太合適。因此腕柜,在優(yōu)先級這一點上济似,操作隊列所提供的功能要比GCD更為便利。NSOperation對象也有“線程優(yōu)先級”(thread priority)盏缤,這決定了運行此操作的線程處在何種優(yōu)先級上砰蠢。用GCD也可以實現(xiàn)此功能,然而采用操作隊列更簡單唉铜,只需設置一個屬性娩脾。
  • 重用NSOperation對象。系統(tǒng)內(nèi)置了一些NSOperation的子類(比附NSBlockOperation)供開發(fā)者調(diào)用打毛,要是不想用這些固有子類的話,那就得自己來創(chuàng)建了俩功。這些類就是普通的Objective-C對象幻枉,能夠存放任何信息。對象在執(zhí)行時可以充分利用存放于其中的信息诡蜓,而且還可以隨意調(diào)用定義在類中的方法熬甫。這就比派發(fā)隊列中那些簡單的塊要強大許多。這些NSOperation類可以在代碼中多次使用蔓罚,它們符合軟件開發(fā)中的“不重復”(Don’t Repeat Yourself,DRY)原則椿肩。

操作隊列有很多地方勝過派發(fā)隊列。操作隊列提供了多種執(zhí)行任務的方式豺谈,而且都是寫好了的郑象,直接就能使用。開發(fā)者不用再編寫復雜的調(diào)度器茬末,也不用自己來實現(xiàn)取消操作或者指定操作優(yōu)先級的功能厂榛,這些事情操作隊列都已經(jīng)實現(xiàn)好了。

有一個API選用了操作隊列而非派發(fā)隊列丽惭,這就是NSNotificationCenter击奶,開發(fā)者可通過其中的方法來注冊監(jiān)聽器,以便在發(fā)生相關事件時得到通知责掏,而這個方法接受的參數(shù)是塊柜砾,不是選擇子。方法原型如下:

- (id <NSObject>)addObserverForName:(nullable NSString *)name 
                               object:(nullable id)obj 
                               queue:(nullable NSOperationQueue *)queue 
                          usingBlock:(void (^)(NSNotification *note))block ;

某些功能確實可以用高層的Objective-C方法來做换衬,但這并不等于說它就一定比底層實現(xiàn)方案好痰驱。要想確定哪種方案更佳证芭,最好還是測試一下性能。

要點:

  • 在解決多線程與任務管理問題時萄唇,派發(fā)隊列并非唯一方案檩帐。
  • 操作隊列提供了一套高層的Objective-C API,能實現(xiàn)純GCD所具備的絕大部分功能另萤,而且還能完成一些更為復雜的操作湃密,那些操作若改用GCD來實現(xiàn),則需另外編寫代碼四敞。

44.通過Dispatch Group機制泛源,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務

dispatch group(派發(fā)分組,調(diào)度組)是GCD的一項特性忿危,能夠把任務分組达箍。調(diào)用者可以等待這組任務執(zhí)行完畢,也可以在提供回調(diào)函數(shù)之后繼續(xù)往下執(zhí)行铺厨,這組任務完成時缎玫,調(diào)用者會得到通知。這個功能有許多用途解滓,其中最重要赃磨、最值得注意的用法,就是把將要并發(fā)執(zhí)行的多個任務合為一個組洼裤,于是調(diào)用者就可以知道這些任務何時才能全部執(zhí)行完畢邻辉。比方說,可以把壓縮一系列文件的任務表示成dispatch group腮鞍。

下面這個函數(shù)可以創(chuàng)建dispatch group:

dispatch_group_t
dispatch_group_create(void);

dispatch group就是一個簡單的數(shù)據(jù)結(jié)構(gòu)值骇,這種數(shù)據(jù)結(jié)構(gòu)彼此之間沒什么區(qū)別,它不像派發(fā)隊列移国,后者還有個用來區(qū)別身份的標識符吱瘩。想把任務編組,有兩種辦法迹缀。第一種是用下面這個函數(shù):

void
dispatch_group_async(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);

它是普通dispatch_async函數(shù)的變體搅裙,比原來多一個參數(shù),用于表示待執(zhí)行的塊所屬的組裹芝。還有種辦法能夠指定任務所屬的dispatch group部逮,那就是使用下面這一對函數(shù):

void
dispatch_group_enter(dispatch_group_t group);

void
dispatch_group_leave(dispatch_group_t group);

前者能夠使分組里正要執(zhí)行的任務數(shù)遞增,而后者則使之遞減嫂易。由此可知兄朋,調(diào)用了dispatch_group_enter以后,必須有與之對應的dispatch_group_leave才行。這與引用計數(shù)相似颅和,要使用引用計數(shù)傅事,就必須令保留操作與釋放操作彼此對應,以防內(nèi)存泄露峡扩。而在使用dispatch_group時蹭越,如果調(diào)用enter之后,沒有相應的leave操作教届,那么這一組任務就永遠執(zhí)行不完响鹃。

下面這個函數(shù)可用于等待dispatch group執(zhí)行完畢

long
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

此函數(shù)接受兩個參數(shù),一個是要等待的group案训,另一個是代表等待時間的timeout值买置。timeout參數(shù)表示函數(shù)在等待dispatch group執(zhí)行完畢時,應該阻塞多久强霎。如果執(zhí)行dispatch group所需的時間小于timeout忿项,則返回0,否則返回非0值城舞。此函數(shù)也可以取常量DISPATCH_TIME_FOREVER轩触,這表示函數(shù)會一直等著dispatch group執(zhí)行完,而不會超時家夺。

除了可以用上面那個函數(shù)等待dispatch group執(zhí)行完畢之外怕膛,也可以換個辦法,使用下列函數(shù):

void
dispatch_group_notify(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);

與wait函數(shù)略有不同的是:開發(fā)者可以向此函數(shù)傳入塊秦踪,等dispatch group執(zhí)行完畢之后,塊會在特定的線程上執(zhí)行掸茅。假如當前線程不應阻塞椅邓,而開發(fā)者又想在那些任務全部完成時得到通知,那么此做法就很有必要了昧狮。比方說景馁,在Mac OS X與iOS系統(tǒng)中,都不應阻塞主線程逗鸣,因為所有UI繪制及事件處理都要在主線程上執(zhí)行合住。

如果想令數(shù)組中的每個對象都執(zhí)行某項任務,并且想等待所有任務執(zhí)行完畢撒璧,那么就可以使用這個GCD特性來實現(xiàn)透葛。代碼如下:

dispatch_queue_t queue =
dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for(id object in collection){
    dispatch_group_async(dispatchGroup,
                        queue, ^{
                             [object performTask];
                         });
}
    
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks

若當前線程不應阻塞,則可以用notify函數(shù)來取代wait:

dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,
                    notifyQueue, ^{
                         //Continue processing after completing tasks
                    });

notify回調(diào)時所選用的隊列卿樱,完全應該根據(jù)具體情況來定僚害。這里使用了主隊列,這是種常見寫法繁调,也可以用自定義的串行隊列或全局并發(fā)隊列萨蚕。

本例中靶草,所有任務都派發(fā)到同一個隊列之中。但實際上未必一定要這樣做岳遥。也可以把某些任務放在優(yōu)先級高的線程上執(zhí)行奕翔,同時仍然把所有任務都歸入同一個dispatch group,并在執(zhí)行完畢時獲得通知

dispatch_queue_t lowPriorityQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
dispatch_queue_t highPriorityQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
dispatch_group_t dispatchGroup = dispatch_group_create();
for(id object in lowPriorityObjects){
    dispatch_group_async(dispatchGroup,
                        lowPriorityQueue,
                        ^{
                            [object performTask];
                        });
}
    
for(id object in highPriorityObjects){
    dispatch_group_async(dispatchGroup,
                     highPriorityQueue,
                    ^{
                         [object performTask];
                     });
}
    
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,
                    notifyQueue,
                    ^{
                       //Countinue processing after completing tasks
                    });

除了像上面這樣把任務提交到并發(fā)隊列之外浩蓉,也可以把任務提交至各個串行隊列中派继,并用dispatch group跟蹤其執(zhí)行狀況。然而妻往,如果所有任務都排在同一個串行隊列里面互艾,那么dispatch group就用處不大了。因為此時任務總要逐個執(zhí)行讯泣,所以只需在提交完全部任務之后再提交一個塊即可纫普,這樣做與通過notify函數(shù)等待dispatch group執(zhí)行完畢然后再回調(diào)塊是等效的:

dispatch_queue_t queue =
dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
    
for(id object in collections){
    dispatch_async(queue, ^{
        [object performTask];
    });
}
    
dispatch_async(queue, ^{
    //Continue processing after completing tasks
});

上面這段代碼表明,開發(fā)者未必總是需要使用dispatch group好渠。有時候采用單個隊列搭配標準的異步派發(fā)昨稼,也可以實現(xiàn)同樣效果。

為了執(zhí)行隊列中的塊拳锚,GCD會在適當?shù)臅r機自動創(chuàng)建新線程或復用舊線程假栓。如果使用并發(fā)隊列,那么其中有可能會有多個線程霍掺,這也就意味著多個塊可以并發(fā)執(zhí)行匾荆。在并發(fā)隊列中,執(zhí)行任務所用的并發(fā)線程數(shù)量杆烁,取決于各種因素牙丽,而GCD主要是根據(jù)系統(tǒng)資源狀況來判斷這些因素的。加入CPU有多個核心兔魂,并發(fā)隊列中有大批任務等待執(zhí)行烤芦,那么GCD就可能會給該隊列配置多個線程。通過dispatch group所提供的這種簡便方式析校,既可以并發(fā)執(zhí)行一系列給定的任務构罗,又能在全部任務結(jié)束時得到通知。由于GCD有并發(fā)隊列機制智玻,所以能夠根據(jù)可用的系統(tǒng)資源狀況來并發(fā)執(zhí)行任務遂唧。而開發(fā)者則可用專注于業(yè)務邏輯代碼,無須再為了處理并發(fā)任務而編寫復雜的調(diào)度器吊奢。

在前面的例子中蠢箩,我們遍歷某個collection,并在其每個元素上執(zhí)行任務,而這也可以用另外一個GCD函數(shù)來實現(xiàn):

void
dispatch_apply(size_t iterations, dispatch_queue_t queue,
        void (^block)(size_t));

此函數(shù)會將塊反復執(zhí)行一定的次數(shù)谬泌,每次傳給塊的參數(shù)值都會遞增滔韵,從0開始,直至”iterations-1“掌实。其用法如下:

dispatch_queue_t queue =
    dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
    
    dispatch_apply(10, queue, ^(size_t i) {
        //Perform task
    });

采用簡單的for循環(huán)陪蜻,從0遞增至9,也能實現(xiàn)同樣的效果:

for(int i=0;i<10;i++){
    //Perform task
}

注意:dispatch_apply所用的隊列可以是并發(fā)隊列贱鼻。如果采用并發(fā)隊列宴卖,那么系統(tǒng)就可以根據(jù)資源狀況來并行執(zhí)行這些塊了,這與使用dispatch group的那段代碼一樣邻悬。上面這個for循環(huán)要處理的collection若是數(shù)組症昏,則可以用dispatch_apply改寫成:

dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
dispatch_apply(array.count, queue, ^(size_t i) {
    id object = array[i];
    [object performTask];
});

這個例子再次表明:未必總要使用dispatch_group。然而父丰,dispatch_apply會持續(xù)阻塞肝谭,直到所有任務都執(zhí)行完畢未知。由此可見:假如把塊派發(fā)給了當前隊列(或者體系中高于當前隊列的某個串行隊列)蛾扇,將導致死鎖攘烛。若想在后臺執(zhí)行任務,則應使用dispatch group镀首。

** 要點:**

  • 一系列任務可歸入一個dispatch group之中坟漱。開發(fā)者可以在這組任務執(zhí)行完畢時獲得通知。
  • 通過dispatch group更哄,可以在并發(fā)式派發(fā)隊列里同時執(zhí)行多項任務芋齿。此時GCD會根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務。

45.使用dispatch_once來執(zhí)行只需執(zhí)行一次的線程安全代碼

單例模式(singleton)對Objective-C開發(fā)者來說并不陌生成翩,常見的實現(xiàn)方式為:在類中編寫名為sharedInstance的方法觅捆,該方法只會返回全類共用的單例實例,而不會在每次調(diào)用時都創(chuàng)建新的實例捕传。假設有個類叫EOCClass,那么這個共享實例的方法一般都會這樣寫:

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

為保證線程安全扩劝,上述代碼將創(chuàng)建單例實例的代碼包裹在同步塊里庸论。

不過,GCD引入了一項特性棒呛,能使單例實現(xiàn)起來更為容易聂示。所用的函數(shù)是:

void
dispatch_once(dispatch_once_t *predicate, 
            dispatch_block_t block);

此函數(shù)接受類型為dispatch_once_t的特殊參數(shù),作者稱其為“標記”(token)簇秒,此外還接受塊參數(shù)鱼喉。對于給定的標記來說,該函數(shù)保證相關的塊必定會執(zhí)行,其僅執(zhí)行一次扛禽。首次調(diào)用該函數(shù)時锋边,必然會執(zhí)行塊中的代碼,最重要的一點在于编曼,此操作完全是線程安全的豆巨。請注意,對于只需執(zhí)行一次的塊來說掐场,每次調(diào)用函數(shù)時傳入的標記都必須完全相同往扔。因此,開發(fā)者通常將標記變量聲明在static或global作用域里熊户。

剛才實現(xiàn)單例模式所用的sharedInstance方法萍膛,可以用此函數(shù)來改寫:

+(instancetype)sharedInstance{
    static EOCClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc]init];
    });
    return sharedInstance;
}

使用dispatch_once可以簡化代碼并且徹底保證線程安全,開發(fā)者根本無須擔心加鎖或同步嚷堡。所有問題都由GCD在底層處理蝗罗。由于每次調(diào)用時都必須使用完全相同的標記,所以標記要聲明稱static麦到。把該變量定義在static作用域里绿饵,可以保證編譯器在每次執(zhí)行sharedInstance方法時都會復用這個變量,而不會創(chuàng)建新變量瓶颠。

此外拟赊,dispatch_once更高效。它沒有使用重量級的同步機制粹淋,若是那樣的話吸祟,每次運行代碼錢都要獲取鎖,相反桃移,此函數(shù)采用“原子訪問”(atomic access)來查詢標記屋匕,以判斷其所對應的代碼原來是否已經(jīng)執(zhí)行過。

要點:

  • 經(jīng)常需要編寫“只需執(zhí)行一次的線程安全代碼”(thread-safe single-code execution)借杰。通過GCD所提供的dispatch_once函數(shù)过吻,很容易就能實現(xiàn)此功能。
  • 標記應該聲明在static或global作用域中蔗衡,這樣的話纤虽,在把只需執(zhí)行一次的塊傳給dispatch_once函數(shù)時,傳進去的標記也是相同的绞惦。

46.不要使用dispatch_get_current_queue

使用GCD時逼纸,經(jīng)常需要判斷當前代碼正在哪個隊列上執(zhí)行,向多個隊列派發(fā)任務時济蝉,更是如此杰刽。

dispatch_queue_t
dispatch_get_current_queue(void);

此函數(shù)返回當前正在執(zhí)行代碼的隊列菠发,不過用的時候要小心。從iOS系統(tǒng)6.0版本起贺嫂,已經(jīng)將其廢棄了滓鸠。

該函數(shù)有種典型的錯誤用法(antipattern,“反模式”)涝婉,就是用它檢測當前隊列是不是某個特定的隊列哥力,試圖以此來避免執(zhí)行同步派發(fā)時可能遭遇的死鎖問題《胀洌考慮下面這兩個存取方法吩跋,其代碼用隊列來保證對實例變量的訪問操作是同步的:

-(NSString *)someString{
    __block NSString *localSomeString;
    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
    return localSomeString;
}

-(void)setSomeString:(NSString *)someString{
    dispatch_async(_syncQueue, ^{
        _someString = someString;
    });
}

這種寫法的問題在于,獲取方法可能會死鎖渔工,假如調(diào)用獲取方法的隊列恰好是同步操作所針對的隊列(本例中是_syncQueue)锌钮,那么dispatch_sync就一直不會返回,直到塊執(zhí)行完畢為止引矩×呵穑可是,應該執(zhí)行塊的那個目標隊列卻是當前隊列旺韭,而當前隊列的dispatch_sync又一直阻塞著氛谜,它在等待目標隊列把這個塊執(zhí)行完,這樣一來区端,塊就永遠沒機會執(zhí)行了值漫。像someString這種方法,就是“不可重入的”织盼。

看了dispatch_get_current_queue的文檔后杨何,你也許會覺得可以用它改寫這個方法,令其變得“可重入”沥邻,只需檢測當前隊列是否為同步操作所針對的隊列危虱,如果是,就不派發(fā)了唐全,直接執(zhí)行塊即可:

-(NSString *)someString{
    __block NSString *localSomeString;
    dispatch_block_t accessorBlock = ^{
        localSomeString = _someString;
    };
    
    if(dispatch_get_current_queue()==_syncQueue){
        accessorBlock();
    }else{
        dispatch_sync(_syncQueue, accessorBlock);
    }
    return localSomeString;
}

這種做法可以處理一些簡單情況埃跷。不過仍然有死鎖的危險。為說明其原因邮利,考慮下面這段代碼弥雹,其中有兩個串行派發(fā)隊列:

dispatch_queue_t queueA =
dispatch_queue_create("com.effectiveobjectivec.queueA", NULL);
dispatch_queue_t queueB =
dispatch_queue_create("com.effectiveobjectivec.queueB", NULL);
    dispatch_sync(queueA, ^{
        dispatch_sync(queueB, ^{
            dispatch_sync(queueA, ^{
                //Deadlock
        });
    });
});

這段代碼執(zhí)行到最內(nèi)層的派發(fā)操作時,總會死鎖近弟,因為此操作是針對queueA隊列的缅糟,所以必須等最外層的dispatch_sync執(zhí)行完畢才行挺智,而最外層的那個dispatch_sync又不可能執(zhí)行完畢祷愉,因為它要等最內(nèi)層的dispatch_sync執(zhí)行完窗宦,于是就死鎖了。現(xiàn)在按照剛才的辦法二鳄,使用dispatch_get_current_queue來檢測:

dispatch_sync(queueA, ^{
        dispatch_sync(queueB, ^{
            dispatch_block_t block = ^{/*...*/};
            if(dispatch_get_current_queue()==queueA){
                block();
            }else{
                dispatch_sync(queueA, block);
            }
        });
    });

然而這樣做依然死鎖赴涵,因為dispatch_get_current_queue返回的是當前隊列,在本例中就是queueB订讼。這樣的話髓窜,針對queueA的同步派發(fā)操作依然會執(zhí)行,于是和剛才一樣欺殿,還是死鎖了寄纵。

在這種情況下,正確的做法是:不要把存取方法做成可重入的脖苏,而是應該確保同步操作所用的隊列絕不會訪問屬性程拭,也就是絕不會調(diào)用someString方法。這種隊列只應該用來同步屬性棍潘。由于派發(fā)隊列是一種極為輕量級的機制恃鞋,所以,為了確保每項屬性都有專用的同步隊列亦歉,我們不妨創(chuàng)建多個隊列恤浪。

使用隊列時還要注意另外一個問題,而那個問題會在你意想不到的地方導致死鎖肴楷。隊列之間會形成一套層級體系水由,這意味著排在某條隊列中的塊,會在其上級隊列(parent queue阶祭,也叫“父隊列”)里執(zhí)行绷杜。層級里地位較高的那個隊列總是“全局并發(fā)隊列”。下圖描述了一套簡單的隊列體系:

排在隊列B或隊列C中的塊濒募,稍后會在隊列A里依次執(zhí)行鞭盟。于是,排在隊列A瑰剃、B齿诉、C中的塊總是要彼此錯開執(zhí)行。然而晌姚,安排在隊列D中的塊粤剧,則有可能與隊列A里的塊(也包括隊列B與C里的塊)并行,因為A與D的目標隊列是個并發(fā)隊列挥唠。若有必要抵恋,并發(fā)隊列可以用多個線程并行執(zhí)行多個塊,而是否會這樣做宝磨,則需根據(jù)CPU的核心數(shù)量等系統(tǒng)資源狀況來定弧关。

由于隊列見有層級關系盅安,所以“檢查當前隊列是否為執(zhí)行同步派發(fā)所用的隊列”這種辦法,并不總是奏效世囊。比方說别瞭,排在隊列C里的塊,會認為當前隊列就是隊列C株憾,而開發(fā)者可能會據(jù)此認為:在隊列A上能夠安全地執(zhí)行同步派發(fā)操作蝙寨。但實際上,這么做依然會像前面那樣導致死鎖嗤瞎。

有的API可令開發(fā)者指定運行回調(diào)塊時所用的隊列墙歪,但實際上卻把回調(diào)塊安排在內(nèi)部的串行同步隊列上,而內(nèi)部隊列的目標隊列又是開發(fā)者所提供的那個隊列贝奇,在此情況下箱亿,也許就要出現(xiàn)剛才說的那種問題了。使用這種API的開發(fā)者可能誤以為:在回調(diào)塊里調(diào)用dispatch_get_current_queue所返回的“當前隊列”弃秆,總是其調(diào)用API時指定的那個届惋。但實際上返回的卻是API內(nèi)部的那個同步隊列。

要解決這個問題菠赚,最好的辦法就是通過GCD所提供的功能來設定“隊列特有數(shù)據(jù)”(queue-specific data)脑豹,此功能可以把任意數(shù)據(jù)以鍵值對的形式關聯(lián)到隊列里。最重要之處在于衡查,假如根據(jù)指定的鍵獲取不到關聯(lián)數(shù)據(jù)瘩欺,那么系統(tǒng)就會沿著層級體系向上查找,直至找到數(shù)據(jù)或到達根隊列為止拌牲。看下面這個例子:

dispatch_queue_t queueA =
dispatch_queue_create("com.effectiveobjectivec.queueA", NULL);
dispatch_queue_t queueB =
dispatch_queue_create("com.effectiveobjectivec.queueB", NULL);
    
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueA");
dispatch_queue_set_specific(queueA,
                            &kQueueSpecific,
                            (void*)queueSpecificValue,
                            (dispatch_function_t)CFRelease);
    
dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{NSLog(@"No deadlock!");};
        
    CFStringRef retrievedValue =
    dispatch_get_specific(&kQueueSpecific);
        
    if(retrievedValue){
        block();
    }else{
        dispatch_sync(queueA, block);
    }
});

本例創(chuàng)建了兩個隊列俱饿。代碼中將隊列B的目標隊列設為隊列A,而隊列A的目標隊列仍然是默認優(yōu)先級的全局并發(fā)隊列塌忽。然后使用下列函數(shù)拍埠,在隊列A上設置“隊列特定值”:

void
dispatch_queue_set_specific(dispatch_queue_t queue, 
                            const void *key,
                            void *context, 
                            dispatch_function_t destructor);

此函數(shù)的首個參數(shù)表示待設置數(shù)據(jù)的隊列,其后兩個參數(shù)是鍵與值土居。鍵與值都是不透明的void指針枣购。對于鍵來說,有個問題一定要注意:函數(shù)是按指針值來比較鍵的擦耀,而不是按照其內(nèi)容棉圈。所以,“隊列特定數(shù)據(jù)”更像是“關聯(lián)引用”眷蜓。值(在函數(shù)中原型里叫context)也是不透明的void指針分瘾,于是可以在其中存放任意數(shù)據(jù)。然而吁系,必須管理該對象的內(nèi)存德召。這使得在ARC環(huán)境下很難使用Objective-C對象作為值痊远。范例代碼使用CoreFoundation字符串作為值,因為ARC并不會自動管理CoreFoundation對象的內(nèi)存氏捞。所以說,這種對象非常適合充當“隊列特定數(shù)據(jù)”冒版,它們可以根據(jù)需要與相關的Objective-C Foundation類無縫銜接液茎。

函數(shù)最后一個參數(shù)是“析構(gòu)函數(shù)”,對于給定的鍵來說辞嗡,當隊列所占內(nèi)存為系統(tǒng)所回收捆等,或者有新的值與鍵相關聯(lián)時,原有的值對象就會移除续室,而析構(gòu)函數(shù)也會與于此時執(zhí)行魄眉。dispatch_function_t類的定義如下:

typedef void (*dispatch_function_t)(void *);

由此可知洪乍,析構(gòu)函數(shù)只能帶有一個指針參數(shù)且返回值必須為void。范例代碼采用CFRelease做析構(gòu)函數(shù),此函數(shù)符合要求列疗,不過也可以采用開發(fā)者自定義的函數(shù),在其中調(diào)用CFRelease以清理舊值喷户,并完成其他必要的清理工作隙轻。

于是,“隊列特定數(shù)據(jù)”所提供的這套簡單易用的機制瞳购,就避免了使用dispatch_get_current_queue時經(jīng)常遭遇的一個陷阱话侄。此外,調(diào)試程序時也許會經(jīng)常用到dispatch_get_current_queue学赛。在此情況下年堆,可以放心使用這個已經(jīng)廢棄的方法,只是別把它編譯到發(fā)行版的程序里就行盏浇。

要點:

  • dispatch_get_current_queue函數(shù)的行為常常與開發(fā)者所預期的不同变丧。此函數(shù)已經(jīng)廢棄,只應做調(diào)試之用绢掰。
  • 由于派發(fā)隊列是按層級來組織的锄贷,所以無法單用某個隊列對象來描述“當前隊列”這一概念。
  • dispatch_get_current_queue函數(shù)用于解決由不可重入的代碼所引發(fā)的死鎖曼月,然而能用此函數(shù)解決的問題谊却,通常也能改用“隊列特定數(shù)據(jù)”來解決。

轉(zhuǎn)載請注明出處:第六章 block與GCD(下)

參考:《Effective Objective-C 2.0》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哑芹,一起剝皮案震驚了整個濱河市炎辨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聪姿,老刑警劉巖碴萧,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乙嘀,死亡現(xiàn)場離奇詭異,居然都是意外死亡破喻,警方通過查閱死者的電腦和手機虎谢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曹质,“玉大人婴噩,你說我怎么就攤上這事∮鸬拢” “怎么了几莽?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宅静。 經(jīng)常有香客問我章蚣,道長,這世上最難降的妖魔是什么姨夹? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任纤垂,我火速辦了婚禮,結(jié)果婚禮上磷账,老公的妹妹穿的比我還像新娘洒忧。我一直安慰自己,他們只是感情好够颠,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布熙侍。 她就那樣靜靜地躺著,像睡著了一般履磨。 火紅的嫁衣襯著肌膚如雪蛉抓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天剃诅,我揣著相機與錄音巷送,去河邊找鬼。 笑死矛辕,一個胖子當著我的面吹牛笑跛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播聊品,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飞蹂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翻屈?” 一聲冷哼從身側(cè)響起陈哑,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惊窖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刽宪,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年界酒,在試婚紗的時候發(fā)現(xiàn)自己被綠了圣拄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡毁欣,死狀恐怖庇谆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情署辉,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布岩四,位于F島的核電站哭尝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剖煌。R本人自食惡果不足惜材鹦,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耕姊。 院中可真熱鬧桶唐,春花似錦、人聲如沸茉兰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规脸。三九已至坯约,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莫鸭,已是汗流浹背闹丐。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留被因,地道東北人卿拴。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像梨与,于是被迫代替她去往敵國和親堕花。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容