[iOS]關(guān)于《Effective OC 2.0:編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》這本書一些有趣的東西(下)

第 23 條:通過(guò)委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間通信

  1. Objective-C 可以使用 “委托模式”(Delegate pattern)的編程設(shè)計(jì)模式來(lái)實(shí)現(xiàn)對(duì)象間的通信:定義一套接口,某對(duì)象若想接受另一個(gè)對(duì)象的委托,則需遵從此接口,以便成為其 “委托對(duì)象”(delegate)。Objective-C 一般利用 “協(xié)議” 機(jī)制來(lái)實(shí)現(xiàn)此模式。

  2. 定義協(xié)議:

@protocol EOCNetworkingFetcherDelegate
@optional

  • (void)newworkingFetcher:(EOCNetworkingFetcher *)fetcher
    didRecevieData:(NSData *)data;
  • (void)newworkingFetcher:(EOCNetworkingFetcher *)fetcher
    didFailWithError:(NSError *)error;
    @end

@interface EOCNetworkingFetcher : NSObject
@property (nonatomic,weak) id<EOCNetworkingFetcherDelegate> delegate;
@end

委托協(xié)議名通常時(shí)在相關(guān)的類名加上Delegate 一詞,也是采用 “駝峰法” 來(lái)命名。

類可以用一個(gè)屬性存放其委托對(duì)象,屬性要用weak 來(lái)修飾,避免產(chǎn)生 “保留環(huán)”(retain cycle)。

某類若要遵從某委托協(xié)議,可以在其接口中聲明,也可以在"class-continuation 分類" 中聲明,如果要向外界公布此類實(shí)現(xiàn)了某協(xié)議,就在接口中聲明,如果這個(gè)協(xié)議是個(gè)委托協(xié)議,通常只會(huì)在這個(gè)類的內(nèi)部使用,這樣子就在分類中聲明就好了。

  1. 如果要在委托對(duì)象上調(diào)用可選方法,那么必須提前使用類型信息查詢方法,判斷這個(gè)委托對(duì)象能否響應(yīng)相關(guān)的選擇子。

NSData *data;
if([_delegate respondsToSelector:@selector(networkFetcher:didRecevieData:)]){
[_delegate networkFetcher:self didRecevieData:data];
}

在調(diào)用delegate 對(duì)象中的方法時(shí),總應(yīng)該把發(fā)起委托的實(shí)例也一并傳入方法中,這樣子,delegate 對(duì)象在實(shí)現(xiàn)相關(guān)方法時(shí),就能根據(jù)傳入的實(shí)例分別執(zhí)行不同的代碼了。

  1. delegate 里的方法也可以用于從委托對(duì)象中獲取信息(數(shù)據(jù)源模式)。

  2. 在實(shí)現(xiàn)委托模式和數(shù)據(jù)源模式的時(shí),協(xié)議中的方法是可選的,我們就會(huì)寫出大量這種判斷代碼:

if([_delegate respondsToSelector:@selector(networkFetcher:didRecevieData:)]){
[_delegate networkFetcher:self didRecevieData:data];
}

每次調(diào)用方法都會(huì)判斷一次,其實(shí)除了第一次檢測(cè)的結(jié)構(gòu)有用,后續(xù)的檢測(cè)很有可能都是多余的,因?yàn)槲袑?duì)象本身沒(méi)變,不太可能會(huì)一下子不響應(yīng),一下子響應(yīng)的,所以我們這里可以把這個(gè)委托對(duì)象能否響應(yīng)某個(gè)協(xié)議方法記錄下來(lái),以優(yōu)化程序效率。

將方法響應(yīng)能力緩存起來(lái)的最佳途徑是使用 “位段”(bitfield)數(shù)據(jù)類型。我們可以把結(jié)構(gòu)體中某個(gè)字段所占用的二進(jìn)制位個(gè)數(shù)設(shè)為特定的值。

位段,C語(yǔ)言允許在一個(gè)結(jié)構(gòu)體中以位為單位來(lái)指定其成員所占內(nèi)存長(zhǎng)度,這種以位為單位的成員稱為“位段”或稱“位域”( bit field) 。

struct data {

unsigned int filedA : 8;

unsigned int filedB : 4;

unsigned int filedC : 2;

unsigned int filedD : 1;

}

filedA 位段占用8個(gè)二進(jìn)制位,filedB 位段占用4個(gè)二進(jìn)制位,filedC 位段占用2個(gè)二進(jìn)制位,filedD位段占用1個(gè)二進(jìn)制位。filedA 就可以表示0至255之間的值,而filedD 則可以表示0或1這兩個(gè)值。

我們可以像filedD 這樣子,創(chuàng)建大小只有1的位段,這樣子就可以把Boolean 值塞入這一小塊數(shù)據(jù)里面,這里很適合這樣子做。

利用位段就可以清楚的表示delegate 對(duì)象是否能響應(yīng)協(xié)議中的方法。

@interface EOCNetworkingFetcher ()
struct {
unsigned int didReceiveData : 1;
unsigned int didFailWithError : 1;
unsigned int didUpdateProgressTo : 1;
} _delegateFlags
@end

//使用
//set flag
_delageteFlags.didReceiveData = 1;

//check flag
if(_delageteFlags.didReceiveData){
//YES
}

可以在delegate 屬性的設(shè)置方法里面寫實(shí)現(xiàn)緩存功能所用的代碼。

這樣子,每次調(diào)用delegate 的相關(guān)方法之前,就不用檢測(cè)委托對(duì)象是否能響應(yīng)給定的選擇子了,而是直接查詢結(jié)構(gòu)體里面的標(biāo)志。

在相關(guān)方法需要調(diào)用很多次時(shí),就要思考是否有必要進(jìn)行優(yōu)化,分析代碼性能,找出瓶頸,使用這個(gè)位段這個(gè)技術(shù)可以提供執(zhí)行速度。

委托模式為對(duì)象提供了一套接口,使其可由此將相關(guān)事件告知其他對(duì)象。

將委托對(duì)象應(yīng)該支持的接口定義成協(xié)議,在協(xié)議中把可能需要處理的事件定義成方法。

當(dāng)某對(duì)象需要從另外一個(gè)對(duì)象中獲取數(shù)據(jù)時(shí),可以使用委托模式。這種情境下,該模式亦稱 “數(shù)據(jù)源協(xié)議”(data source protocal)。

若有必要,可實(shí)現(xiàn)含有位段的結(jié)構(gòu)體,將委托對(duì)象是否能響應(yīng)相關(guān)協(xié)議方法這一信息緩存至其中。

第 24 條:將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類之中

一個(gè)類經(jīng)常有很多方法,盡管代碼寫的比較規(guī)范,這個(gè)文件還是會(huì)越來(lái)越大,定位問(wèn)題以及閱讀上都會(huì)造成不便。我們可以通過(guò) “分類” 機(jī)制來(lái)把代碼按邏輯劃分到幾個(gè)分區(qū)中。

通過(guò)分類機(jī)制,可以把類代碼分成很多個(gè)易于管理的小塊,以便單獨(dú)檢視。

可以考慮創(chuàng)建Private 分類,將一些不是公共API 的方法,隱藏起來(lái)。寫程序庫(kù)的時(shí)候,加上不暴露頭文件,使用者就不知道庫(kù)里還有這些私有方法。

使用分類機(jī)制把類的實(shí)現(xiàn)代碼劃分成易于管理的小塊。

將應(yīng)該視為 “私有” 的方法歸入為叫Private 的分類中,以隱藏實(shí)現(xiàn)細(xì)節(jié)。

第 25 條:總是為第三方類的分類名稱加前綴

分類機(jī)制常用于向無(wú)源碼的既有類中新增新功能,但是在使用的時(shí)候要十分小心,不然很容易產(chǎn)生Bug。因?yàn)檫@個(gè)機(jī)制時(shí)在運(yùn)行期系統(tǒng)加載分類時(shí),將其方法直接加到原類中,這里要注意方法重名的問(wèn)題,不然會(huì)覆蓋原類中的同名方法。

一般用前綴來(lái)區(qū)分各個(gè)分類的名稱與其中所定義的方法。

不要輕易去利用分類來(lái)覆蓋方法,這里需要慎重考慮。

向第三方類中添加分類時(shí),總應(yīng)該給其名稱加上你專用的前綴。

向第三方類中添加分類時(shí),總應(yīng)給其中的方法名加上你專用的前綴

第 26 條:勿在分類中聲明屬性

可以利用運(yùn)行期的關(guān)聯(lián)對(duì)象機(jī)制,為分類聲明屬性,但是這種做法要盡量避免,因?yàn)槌?"class-continuation 分類" 之外,其他分類都無(wú)法向類中新增實(shí)例變量,因此,他們無(wú)法把實(shí)現(xiàn)屬性所需的實(shí)例變量合成出來(lái)。

在分類定義屬性的時(shí)候,會(huì)報(bào)警告,表明此分類無(wú)法合成該屬性相關(guān)的實(shí)例變量,所以開發(fā)者需要在分類中為該屬性實(shí)現(xiàn)存取方法。

利用關(guān)聯(lián)對(duì)象機(jī)制可以解決分類中不能合成實(shí)例變量的問(wèn)題。自己實(shí)現(xiàn)存取方法,但是要注意該屬性的內(nèi)存管理語(yǔ)義(屬性特質(zhì))。

@property (nonatomic,copy) NSString *name;

static const void *kViewControllerName = &kViewControllerName;

  • (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, kViewControllerName, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }

  • (NSString *)name {
    NSString *myName = objc_getAssociatedObject(self, kViewControllerName);
    return myName;
    }

  1. 在可以修改源代碼的情況下,盡量把屬性定義在主接口中,這里是唯一能夠定義實(shí)例變量的地方,屬性只是定義實(shí)例變量及相關(guān)存取方法所用的 “語(yǔ)法糖”。

  2. 由于實(shí)現(xiàn)屬性所需的全部方法都已實(shí)現(xiàn),所以不會(huì)再為該屬性自動(dòng)合成實(shí)例變量了。

盡量把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里。

在 “class-continuation 分類” 之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。

第 27 條:使用 ”class-continuation 分類“ 隱藏實(shí)現(xiàn)細(xì)節(jié)

  1. ”class-continuation 分類“ 必須定義在本身類的實(shí)現(xiàn)文件中,而且這里是唯一可以聲明實(shí)例變量的分類,而且此分類沒(méi)有特定的實(shí)現(xiàn)文件,這個(gè)分類也沒(méi)有名字。這里可以定義實(shí)例變量的原因是 “ 穩(wěn)固的ABI” 機(jī)制,我們無(wú)須知道對(duì)象的大小就可以直接使用它。

@interface EOCPerson ()

@end

  1. 可以將不需要要暴露給外界知道的實(shí)例變量及方法寫在 “class-continuation 分類” 中。

  2. 編寫Objective-C++ 代碼時(shí)候,使用 “class-continuation 分類” 會(huì)十分方便。因?yàn)閷?duì)于引用了C++的文件的實(shí)現(xiàn)文件需要用.mm 為擴(kuò)展名,表示編譯器應(yīng)該將此文件按照Objective-C++ 來(lái)編譯。C++ 類必須完全引入,編譯器要完整地解析其定義才能得知這個(gè)C++ 對(duì)象的實(shí)例變量大小。如果把對(duì)C++ 類的引用寫在頭文件的話,其他引用到這個(gè)類也會(huì)引用到這個(gè)C++ 類,就也需要編譯成Objective-C++ 才行,這樣子很容易失控。

這里可以利用 “class-continuation 分類” 把引用C++ 類的細(xì)節(jié)寫到實(shí)現(xiàn)文件中,這樣子別的類引用這個(gè)類就不會(huì)受到影響,甚至都不知道這個(gè)類底層實(shí)現(xiàn)混有C++ 代碼。

  1. 使用 “class-continuation 分類” 還可以將頭文件聲明 “只讀” 的屬性擴(kuò)展成 “可讀寫”,以便在類的內(nèi)部可以設(shè)置其值。

  2. 我們通常不直接訪問(wèn)實(shí)例變量,而是通過(guò)設(shè)置方法來(lái)做,因?yàn)檫@樣子可以觸發(fā) “鍵值觀測(cè)” (Key-Value Observing,KVO)通知。

  3. 若對(duì)象所遵循的協(xié)議只應(yīng)視為私有,也可以同過(guò)“class-continuation 分類” 來(lái)隱藏。

通過(guò) “class-continuation 分類” 向類中新增實(shí)例變量。

如果某屬性在主接口中聲明為 “只讀”,而類的內(nèi)部又要用設(shè)置方法修改此屬性,那么就在 “class-continuation 分類” 中將其擴(kuò)展為 “可讀寫”。

把私有方法的原型聲明在 “class-contiunation 分類” 里面。

若想使類所遵循的協(xié)議不為人所知,則可于 “class-contiunation 分類” 中聲明。

第 28 條:通過(guò)協(xié)議提供匿名對(duì)象

@property (nonatomic,weak) id<EOCDelegate> delegate;

該屬性類型是id<EOCDelegate> 的,所以實(shí)際上任何類的都能充當(dāng)這一屬性,即便該類不繼承NSObject 也可以,只要遵循EOCDelegae 協(xié)議就可以了,對(duì)于具備此屬性的類來(lái)說(shuō),delegate 就是 “匿名的”。

協(xié)議可在某種程度上提供匿名類型。具體的對(duì)象類型可以淡化成遵從某協(xié)議的id 類型,協(xié)議里規(guī)定了對(duì)象所應(yīng)實(shí)現(xiàn)的方法。

使用匿名對(duì)象來(lái)隱藏類型名稱(或類名)。

如果具體類型不重要,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里的)特定方法,那么可使用匿名對(duì)象來(lái)表示。


內(nèi)存管理

第 29 條:理解引用計(jì)數(shù)

引用計(jì)數(shù)工作原理

Objective-C 語(yǔ)言使用引用計(jì)數(shù)來(lái)管理內(nèi)存,每個(gè)對(duì)象都有個(gè)可以遞增遞減的計(jì)數(shù)器,用以表示當(dāng)前有多少個(gè)事物想令此對(duì)象繼續(xù)存活下去。

NSObject 協(xié)議聲明下面三個(gè)方法用于操作計(jì)數(shù)器,以遞增或遞減其值:

retain 遞增保留計(jì)數(shù)

release 遞減保留計(jì)數(shù)

autorelease 待稍后清理 “自動(dòng)釋放池” 時(shí),再遞減保留計(jì)數(shù)

  1. 在調(diào)用release 之后,對(duì)象所占的內(nèi)存可能會(huì)被回收,這樣子在調(diào)用對(duì)象的方法就可能使程序崩潰,這里 “可能” 的意思是對(duì)象所占的內(nèi)存在 “解除分配” (deallocated)之后,只是放回 “可用內(nèi)存池”(avaiable pool)。若果執(zhí)行方法時(shí)尚未覆寫對(duì)象,那么對(duì)象仍然有效。

  2. 為避免在不經(jīng)意間使用無(wú)效對(duì)象,一般在調(diào)用完release 之后都會(huì)清空指針,保證不會(huì)出現(xiàn)可能指向無(wú)效對(duì)象的指針,這種指針通常被稱為 “懸掛指針”(dangling pointer)。

自動(dòng)釋放池

調(diào)用release 會(huì)立刻遞減對(duì)象的保留計(jì)數(shù)(這里可能會(huì)令系統(tǒng)回收此對(duì)象),調(diào)用autorelease 方法,是在稍后遞減計(jì)數(shù),通常是在下一次 “事件循環(huán)” 時(shí)遞減。

此特性很有用,尤其是在返回對(duì)象時(shí)更應(yīng)該用它

  • (NSString *)stringValue {
    NSString *str = [[NSString alloc]
    initWithFormat:@"I am this %@",self];
    return str;
    }

這里返回的str 對(duì)象的保留計(jì)數(shù)會(huì)比期望值多1,因?yàn)檎{(diào)用alloc 會(huì)令保留計(jì)數(shù)+1,這里又沒(méi)有對(duì)應(yīng)的釋放操作,這樣子就意味著調(diào)用者要負(fù)責(zé)處理這多出來(lái)的保留操作。在這個(gè)方法又不能釋放str,否則還沒(méi)等方法返回,str 這個(gè)對(duì)象就被釋放了。這里應(yīng)該用autorelease ,它會(huì)在稍后釋放對(duì)象,保證這里可以保證調(diào)用者可以先用這個(gè)str 對(duì)象。

  1. autorelease 能延長(zhǎng)對(duì)象聲明周期,使其在跨越方法調(diào)用邊界后依然可以存活一段時(shí)間。

保留環(huán)

呈環(huán)狀相互引用的多個(gè)對(duì)象,相互持有,這將導(dǎo)致內(nèi)存泄漏,這里循環(huán)中的對(duì)象其保留計(jì)數(shù)不會(huì)降為0。

通常采用 “弱引用” 來(lái)解決此問(wèn)題,或者從外界命令某個(gè)對(duì)象不再保留另外一個(gè)對(duì)象來(lái)打破保留環(huán),從而避免內(nèi)存泄漏。

引用計(jì)數(shù)機(jī)制通過(guò)可以遞增遞減的計(jì)數(shù)器來(lái)管理內(nèi)存。對(duì)象創(chuàng)建好之后,其保留計(jì)數(shù)至少為1。若保留計(jì)數(shù)為正,則對(duì)象繼續(xù)存活。當(dāng)保留計(jì)數(shù)降為0時(shí),對(duì)象就被銷毀了。

在對(duì)象生命期中,其余對(duì)象通過(guò)引用來(lái)保留或釋放此對(duì)象。保留與釋放操作分別會(huì)遞增及遞減保留計(jì)數(shù)。

第 30 條:以 ARC 簡(jiǎn)化引用計(jì)數(shù)

內(nèi)存泄漏:沒(méi)有正確的釋放已經(jīng)不再使用的內(nèi)存。

自用引用計(jì)數(shù)預(yù)先加入適當(dāng)?shù)谋A艋蜥尫挪僮鱽?lái)避免內(nèi)存泄漏,使用ARC 時(shí),引用計(jì)數(shù)實(shí)際上還是要執(zhí)行的,只是保留與釋放操作是由ARC 自動(dòng)添加的。

ARC 會(huì)自動(dòng)執(zhí)行retain、release、autorelease、dealloc等操作,所以在ARC 下調(diào)用這些內(nèi)存管理方法是非法的。因?yàn)锳RC 會(huì)分析何處應(yīng)該自動(dòng)調(diào)用內(nèi)存管理方法,所以我們?cè)偈謩?dòng)調(diào)用的話,會(huì)干擾其工作。

實(shí)際上,ARC 在調(diào)用這些方法時(shí),并不是普通的Objective-C 消息派發(fā)機(jī)制,而是直接調(diào)用其底層的C 語(yǔ)言函數(shù),這樣子性能會(huì)更好。

使用ARC 時(shí)必須遵循的方法命名規(guī)則

  1. 將內(nèi)存管理語(yǔ)義在方法名中表示出來(lái),若方法名以下列詞語(yǔ)開頭,則返回的對(duì)象歸
    調(diào)用者所有:

alloc

new

copy

mutableCopy

  1. 將內(nèi)存管理交由編譯器和運(yùn)行期組件來(lái)做,可以使代碼得到多種優(yōu)化。
    變量的內(nèi)存管理語(yǔ)義

ARC 也會(huì)處理局部變量與實(shí)例變量的內(nèi)存管理。
我們通常會(huì)給局部變量加上修飾符來(lái)打破 “塊”(block)所引入的 “保留環(huán)”(retain cycle)。

ARC 如何清理實(shí)例變量

對(duì)實(shí)例變量進(jìn)行內(nèi)存管理,必須在 “回收分配給對(duì)象的內(nèi)存” 時(shí)生成必要的清理代碼。凡事具備強(qiáng)引用的變量,都必須釋放,ARC 會(huì)在dealloc 方法中插入這些代碼。

ARC 會(huì)借用Objective-C++ 的一項(xiàng)特性來(lái)生成清理代碼,在回收對(duì)象時(shí),待回收對(duì)象會(huì)調(diào)用所有C++ 對(duì)象的析構(gòu)函數(shù),編譯器如果發(fā)現(xiàn)某個(gè)對(duì)象里含有C++ 對(duì)象,就會(huì)生成名為.cxx_desteuct 的方法,ARC 借助此特性,在該方法中生成清理內(nèi)存所需的代碼。

對(duì)于非Objective-C 的對(duì)象,然后需要我們手動(dòng)清理。CFRelease();

覆寫內(nèi)存管理方法

非ARC 時(shí)可以覆寫內(nèi)存管理方法,在ARC 下禁止覆寫內(nèi)存管理方法,會(huì)干擾到ARC 分析對(duì)象生命周期的工作。

有ARC 之后,程序員就無(wú)需擔(dān)心內(nèi)存管理問(wèn)題了。使用ARC 來(lái)編程,可省去類中的許多 “樣板代碼”。

ARC 管理對(duì)象生命周期的辦法基本上是:在適合的地方插入 “保留” 及 “釋放” 操作。在ARC 環(huán)境下,變量的內(nèi)存管理語(yǔ)義可以通過(guò)修飾符指明,而原來(lái)則需要手工執(zhí)行 “保留”及 “釋放” 操作。

ARC 只負(fù)責(zé)管理Objective-C 對(duì)象的內(nèi)存。尤其要注意:CoreFoundation 對(duì)象不歸ARC 管理,開發(fā)者必須適時(shí)調(diào)用CFRetain/CFRelease。

第 31 條:在 dealloc 方法中只釋放引用并解除監(jiān)聽

對(duì)象在經(jīng)歷生命周期后,最終會(huì)為系統(tǒng)回收,這時(shí)候就要執(zhí)行dealloc 方法。每個(gè)對(duì)象生命周期內(nèi),此方法只會(huì)調(diào)用一次,也就是保留計(jì)數(shù)為0 的時(shí)候,絕對(duì)不能自己調(diào)用dealloc 方法,運(yùn)行期會(huì)在適當(dāng)?shù)臅r(shí)候調(diào)用,一旦調(diào)用,對(duì)象就不再有效了,后續(xù)的方法調(diào)用均是無(wú)效的。

dealloc 方法主要是釋放對(duì)象所擁有的引用,也就是把Objective-C 對(duì)象都釋放掉,ARC 會(huì)通過(guò)自動(dòng)生成的.cxx_desteuct 方法,在dealloc 中為你自動(dòng)添加這些釋放代碼。但是其他非Objective-C 對(duì)象就需要自己手動(dòng)釋放了。

dealloc 方法通常還需要把原來(lái)配置過(guò)的觀測(cè)行為都清理掉,例如通知等。

對(duì)于開銷較大或者系統(tǒng)內(nèi)稀缺的資源不應(yīng)該等到dealloc 才清理(文件描述符、套接字、大塊內(nèi)存等),因?yàn)閐ealloc 并不會(huì)在特定的時(shí)機(jī)調(diào)用,因?yàn)橛锌赡苓€有別的對(duì)象持有它。應(yīng)該自己實(shí)現(xiàn)一個(gè)方法,當(dāng)應(yīng)用程序用完資源對(duì)象后,就調(diào)用此方法,這樣子對(duì)象的生命周期就更加明確了。

調(diào)用dealloc 方法的那個(gè)線程會(huì)執(zhí)行 “最終的釋放操作”,令對(duì)象保留計(jì)數(shù)為0,而某些方法必須在特定的線程調(diào)用,若在dealloc 中調(diào)用那么方法,無(wú)法保證當(dāng)前的線程就是那個(gè)方法所需的線程。在dealloc 里盡量不要去調(diào)用方法,包括屬性的存取方法,因?yàn)樵谶@些方法可能會(huì)被覆寫,并在其中做一些無(wú)法在回收階段安全執(zhí)行的操作。

在dealloc 方法里,應(yīng)該做的事情就是釋放指向其他對(duì)象的引用,并取消原來(lái)訂閱的 “鍵值觀測(cè)”(KVO)或NSNotification 等通知,不要做其他事情。

如果對(duì)象持有文件描述符等系統(tǒng)資源,那么應(yīng)該專門編寫一個(gè)方法來(lái)釋放此種資源。這樣的類要和其使用者約定:用完資源后必須調(diào)用close 方法。

執(zhí)行異步任務(wù)的方法不應(yīng)在dealloc 里調(diào)用;只有在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在dealloc 里調(diào)用,因?yàn)榇藭r(shí)對(duì)象已處于回收的狀態(tài)。

第 32 條:編寫 “異常安全代碼” 時(shí)留意內(nèi)存管理問(wèn)題

純C 中沒(méi)有異常,C++與Objective-C 都支持異常,在運(yùn)行期系統(tǒng)中C++與Objective-C 異常相互兼容,也就是說(shuō),從其中一門語(yǔ)言里拋出的異常能用另外一門語(yǔ)言所編寫的 “異常處理程序” 來(lái)捕獲。

Objective-C 錯(cuò)誤模型表明,異常只應(yīng)發(fā)生嚴(yán)重錯(cuò)誤后拋出,發(fā)生異常如何管理內(nèi)存很重要,在try 塊中保留某個(gè)對(duì)象的,但是在釋放它之前拋出異常了,這時(shí)候就無(wú)法正常釋放了,這時(shí)候需要借助@finally 塊來(lái)保證釋放對(duì)象的代碼一定會(huì)執(zhí)行,且只執(zhí)行一次。

在ARC 不會(huì)自動(dòng)生成處理異常中的代碼,因?yàn)檫@樣子需要加入大量的樣板代碼,以便追蹤待清理的對(duì)象,從而在拋出異常時(shí)將其釋放。可以這段代碼會(huì)嚴(yán)重運(yùn)行期的性能,還會(huì)增加應(yīng)用程序的大小。

可以通過(guò)-fobjc-arc-exceptions 這個(gè)編譯編織來(lái)開啟這個(gè)功能,但是這個(gè)功能不應(yīng)該作為生成這種安全處理異常所用的附加代碼,應(yīng)該是讓代碼處于Objective-C++模式。

捕獲異常時(shí),一定要注意將try 塊內(nèi)創(chuàng)建的對(duì)象清理干凈。

在默認(rèn)情況下,ARC 不生成安全處理異常所需的清理代碼。開啟編譯標(biāo)志后,可生成這種代碼,不過(guò)會(huì)導(dǎo)致應(yīng)用程序變大,而且會(huì)降低運(yùn)行效率。

第 33 條:以弱引用避免保留環(huán)

幾個(gè)對(duì)象都已某種方式互相引用,從而形成 “環(huán)”,這種情況通常會(huì)泄漏內(nèi)存,因?yàn)闆](méi)有東西引用環(huán)中對(duì)象,這樣子環(huán)里的對(duì)象互相引用,不會(huì)被系統(tǒng)回收。

避免保留環(huán)的最佳方式就是弱引用,來(lái)表示 “非擁有關(guān)系”,unsafe_unretained、weak 修飾都是可以達(dá)到的。unsafe_unretained 表示屬性值可能不安全,有可能系統(tǒng)把屬性所指的對(duì)象回收了,但是這個(gè)屬性依然指向那塊地址,那么再調(diào)用它的方法可能會(huì)使程序崩潰,用weak 修飾的時(shí)候,在所指對(duì)象被回收的時(shí)候,會(huì)將屬性的指針置為nil。

一般來(lái)說(shuō),如果不擁有某對(duì)象,就不要保留它,這條規(guī)則對(duì)collection 例外,collection 雖然不直接擁有其內(nèi)容,但是它要代表自己所屬的那個(gè)對(duì)象來(lái)保留這些元素。

將某些引用設(shè)為weak,可避免出現(xiàn) “保留環(huán)”。

weak 引用可以自動(dòng)清空,也可以不自動(dòng)清空。自動(dòng)清空是隨著ARC 而引入的新特性,由運(yùn)行期系統(tǒng)來(lái)實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上,可以隨意讀取其數(shù)據(jù),因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過(guò)的對(duì)象。

第 34 條:以 “自動(dòng)釋放池塊” 降低內(nèi)存峰值

  1. 釋放對(duì)象有兩種方式:

一種是調(diào)用release 方法,使其保留計(jì)數(shù)立即遞減

一種是調(diào)用autorelease 方法,將對(duì)象放入 “自動(dòng)釋放池” 中,自動(dòng)釋放池用于存放那些需要稍后某個(gè)時(shí)刻釋放的對(duì)象,清空(drain)自動(dòng)釋放池時(shí),系統(tǒng)會(huì)向其中的對(duì)象發(fā)送release 消息。

  1. 創(chuàng)建自動(dòng)釋放池,系統(tǒng)會(huì)自動(dòng)創(chuàng)建一些線程,這些線程默認(rèn)都有自動(dòng)釋放池,每次執(zhí)行 “事件循環(huán)”時(shí),都會(huì)將其清空。自動(dòng)釋放池于左邊花括號(hào)創(chuàng)建,并于對(duì)應(yīng)的右花括號(hào)自動(dòng)清空。位于自動(dòng)釋放池范圍內(nèi)的對(duì)象,會(huì)在末尾處受到release 消息。

@autoreleasepool {
//...
}

  1. 內(nèi)存峰值:是指應(yīng)用程序在某個(gè)特定時(shí)段內(nèi)的最大內(nèi)存用量。

  2. 對(duì)象有可能會(huì)放在自動(dòng)釋放池里面,需要等到線程執(zhí)行下一次事件循環(huán)才會(huì)清空,這里會(huì)導(dǎo)致應(yīng)用程序所占內(nèi)存會(huì)持續(xù)增加,等到臨時(shí)對(duì)象釋放的時(shí)候,內(nèi)存用量又會(huì)突然下降。我們現(xiàn)在就想把這個(gè)內(nèi)存峰值給降低下來(lái)。

  3. 可以增加一個(gè)自動(dòng)釋放池來(lái)解決這個(gè)問(wèn)題:這樣子對(duì)象就會(huì)加入到這個(gè)釋放池,而不是線程的主池中,每次循環(huán)都創(chuàng)建和釋放這個(gè)釋放池。

for (int i = 0;i < 100000;i++){
@autorelease{
NSObject *object = [NSObject new];
}
}

  1. 自動(dòng)釋放池機(jī)制就像 “棧” 一樣。系統(tǒng)創(chuàng)建好自動(dòng)釋放池之后,就將其推入棧中,而清空自動(dòng)釋放池,則相當(dāng)于將其從棧中彈出。在對(duì)象上執(zhí)行自動(dòng)釋放操作,就等于將其放入棧頂?shù)哪莻€(gè)池。

  2. 對(duì)于是否需要用池來(lái)優(yōu)化效率,這個(gè)得考慮清楚來(lái),因?yàn)樽詣?dòng)釋放池的創(chuàng)建還是有一丟丟開銷的,所以盡量不要建立額外的自動(dòng)釋放池。

自動(dòng)釋放池排布在棧中,對(duì)象收到autorelease 消息后,系統(tǒng)將其放入到最頂端的池里。

合理運(yùn)用自動(dòng)釋放池,可降低應(yīng)用程序的內(nèi)存峰值。

@autoreleasepool 這種新式寫法能創(chuàng)建出更為輕便的自動(dòng)釋放池。


第 35 條:用 “僵尸對(duì)象” 調(diào)試內(nèi)存管理問(wèn)題

向已回收的對(duì)象發(fā)送消息是不安全的,是否崩潰這個(gè)是看對(duì)象所占的內(nèi)存有沒(méi)有為其他內(nèi)容所覆寫。

Cocoa 提供 “僵尸對(duì)象”(Zombie Object)這個(gè)非常方便的功能,開啟后,運(yùn)行期系統(tǒng)會(huì)把已經(jīng)回收的實(shí)例轉(zhuǎn)換成特殊的 “僵尸對(duì)象”,而不會(huì)真正回收它們。這個(gè)對(duì)象所在的核心內(nèi)無(wú)法重用,因此不可能遭到覆寫,僵尸對(duì)象收到消息后,會(huì)拋出異常。

使用:Xcode Scheme 中的Enable Zombie Objects 選項(xiàng),打開會(huì)將NSZombieEnabled 環(huán)境變量設(shè)成YES。

系統(tǒng)在即將回收時(shí),會(huì)執(zhí)行一個(gè)附加步驟,將對(duì)象轉(zhuǎn)換成僵尸對(duì)象,而不徹底回收。僵尸類是從名為NSZombie 的模版類復(fù)制出來(lái)的。NSZombie 類并未實(shí)現(xiàn)任何方法,此類沒(méi)有超類,因此跟NSObject 一樣,也是一個(gè) "根類",該類只有一個(gè)實(shí)例變量,叫做isa,所以發(fā)給他的消息都要經(jīng)過(guò) “完整的消息轉(zhuǎn)發(fā)機(jī)制” 。

在完整的消息轉(zhuǎn)發(fā)機(jī)制中,forwarding 是核心,檢查接受消息的對(duì)象所屬的類名,若是NSZombie ,則表示消息接受者是僵尸對(duì)象,需要特殊處理。

系統(tǒng)在回收對(duì)象時(shí),可以不將其真的回收,而是把它轉(zhuǎn)化成僵尸對(duì)象。通過(guò)環(huán)境變量NSZombieEnabled 可開啟此功能。

系統(tǒng)會(huì)修改對(duì)象的isa 指針,令其指向特殊的僵尸類,從而使該對(duì)象變成僵尸對(duì)象。僵尸類能夠響應(yīng)所有的選擇子,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受者的消息,然后終止應(yīng)用程序。

第 36 條:不要使用 retainCount

每個(gè)對(duì)象都有一個(gè)計(jì)數(shù)器,其表明還有多少個(gè)其他對(duì)象想令此對(duì)象繼續(xù)存活。在ARC retainCount 這個(gè)方法已經(jīng)廢棄了,但是在非ARC 中也不應(yīng)該調(diào)用這個(gè)方法,因?yàn)檫@個(gè)保留計(jì)數(shù)只是返回某個(gè)時(shí)間點(diǎn)的值,并不會(huì)聯(lián)系上下文給出真正有用的值。

retainCount 可能永遠(yuǎn)不返回0,因?yàn)橄到y(tǒng)有時(shí)候會(huì)優(yōu)化對(duì)象的釋放行為,在保留計(jì)數(shù)為1的時(shí)候就把它回收了。

不應(yīng)該依靠保留計(jì)數(shù)的具體址來(lái)編碼。

對(duì)象的保留計(jì)數(shù)看似有用,實(shí)則不然,因?yàn)槿魏谓o定時(shí)間點(diǎn)上的 “絕對(duì)保留計(jì)數(shù)”(absolute retain count)都無(wú)法反映對(duì)象生命期的全貌。

引入ARC 之后,retainCount 方式就正式廢止了,在ARC 下調(diào)用方法會(huì)導(dǎo)致編譯器報(bào)錯(cuò)。


塊與大中樞派發(fā)

第 37 條:理解 “塊” 這一概念

塊可以實(shí)現(xiàn)閉包。

塊的基礎(chǔ)知識(shí)

  1. 塊用 “^” 符號(hào)來(lái)表示,后面跟著一對(duì)花括號(hào),括號(hào)里面是塊的實(shí)現(xiàn)代碼。塊其實(shí)就是個(gè)值,而且自有其相關(guān)類型,可以賦值給變量;塊類型的語(yǔ)法和函數(shù)指針類似。

^{
//block implementation herer
}

//這里定義了名為someBlock 的變量
//塊類型的語(yǔ)法結(jié)構(gòu)如下
//return_type (^block_name)(parameters)
void (^someBlock)() = ^{
//block implementation herer
}

  1. 在聲明塊的范圍內(nèi),所有變量都可以被其捕獲。默認(rèn)情況下被塊捕獲的變量是不可以在塊里修改的,不過(guò)可以在聲明變量的時(shí)候加上__block 修飾符,這樣子就可以在塊內(nèi)修改了。

  2. 如果塊所捕獲的變量是對(duì)象類型,那么就會(huì)自動(dòng)保留它,在系統(tǒng)釋放這個(gè)塊的時(shí)候,也會(huì)將其一并釋放。

  3. 塊總能修改實(shí)例變量,所以在聲明時(shí)無(wú)須加__block。不過(guò)如果通過(guò)讀取或?qū)懭氩僮鞑东@了實(shí)例變量,那么也會(huì)自動(dòng)把self 變量一并捕獲了,因?yàn)閷?shí)例變量是與self 所指代的實(shí)例關(guān)聯(lián)在一起的。

塊的內(nèi)部結(jié)構(gòu)

  1. 塊本身也是對(duì)象,在存放塊對(duì)象的內(nèi)存區(qū)域中,首個(gè)變量是指向Class 對(duì)象的指針(isa 指針)。
  1. invoke 變量是這個(gè)函數(shù)指針,指向塊的實(shí)現(xiàn)代碼。函數(shù)原型至少要接受一個(gè)void* 型的參數(shù),此參數(shù)代表塊。為什么要把塊對(duì)象作為參數(shù)傳進(jìn)來(lái)呢,因?yàn)樵趫?zhí)行塊的時(shí)候,要從內(nèi)存中把這些捕獲到的變量讀出來(lái)。

descriptor 變量是指向結(jié)構(gòu)體的指針,這個(gè)結(jié)構(gòu)體包含塊的一些信息。

全局塊、棧塊及堆塊

  1. 定義塊的時(shí)候,其所占的內(nèi)存區(qū)域是分配在棧中,意思就是,塊只在定義它的那個(gè)范圍內(nèi)有效。

void (^block)();
if(***){
block = ^(){
NSLog(@"Block A");
};
}else{
block = ^(){
NSLog(@"Block B");
};
}
block();

/*定義在if else 語(yǔ)句中的兩個(gè)塊都分配在棧內(nèi)存中,編譯器會(huì)給每個(gè)塊分配好棧內(nèi)存,然而等離開了相應(yīng)的范圍之后,編譯器有可能把分配給塊內(nèi)存覆寫掉。所以這里執(zhí)行block() 有危險(xiǎn)。

為了解決這個(gè)問(wèn)題,可以給塊發(fā)送copy 消息以拷貝之。這樣子的話,就可以把塊從棧復(fù)制到堆可。一旦復(fù)制到堆上,塊就成了帶引用計(jì)數(shù)的對(duì)象了,后續(xù)的復(fù)制操作都不會(huì)真的執(zhí)行復(fù)制,只是遞增塊對(duì)象的引用計(jì)數(shù)。
*/

  1. 全局塊聲明在全局內(nèi)存里,而且也不能被系統(tǒng)回收,相當(dāng)于單例。由于運(yùn)行該塊所需的全部信息在編譯期確定,所以可以把它作為全局塊,這是一種優(yōu)化技術(shù):若把如此簡(jiǎn)單的塊當(dāng)成復(fù)雜的塊來(lái)處理,那就會(huì)在復(fù)制及丟棄該塊時(shí)執(zhí)行一些無(wú)謂的操作。

塊是C、C++、Objective-C 中的詞法閉包。

塊可接受參數(shù),也可返回值。

塊可以分配在棧和堆上,也可以是全局的。分配在棧上的塊可以拷貝到堆里,這樣的話,就和標(biāo)準(zhǔn)的Objective-C 對(duì)象一樣,具備引用計(jì)數(shù)了。

第 38 條:為常用的塊類型創(chuàng)建 typedef

每個(gè)塊都具備其 “ 固定類型”,因而可將其賦值給適當(dāng)類型的變量。

由于塊類型的語(yǔ)法比較復(fù)雜難記,我們可以給塊類型起個(gè)別名。用C 語(yǔ)言中的 “ 類型定義” 的特性。typedef 關(guān)鍵字用于給類型起個(gè)易讀的別名。

typedef int(^EOCSomeBlock)(BOOL flag, int value);

EOCSomeBlock block = ^(BOOL flag, int value){
//to do
};

以typedef 重新定義塊類型,可令塊變量用起來(lái)更加簡(jiǎn)單。

定義新類型時(shí)應(yīng)遵從現(xiàn)有的命名習(xí)慣,勿使其名稱與別的類型向沖突。

不妨為同一個(gè)塊簽名定義多個(gè)類型別名。如果要重構(gòu)的代碼使用了塊類型的某個(gè)別名,那么只需要修改相應(yīng)depedef 中的塊簽名即可,無(wú)須改動(dòng)其他typedef。

第 39 條:用handle 塊降低代碼分散程度

場(chǎng)景:異步方法執(zhí)行完任務(wù),需要以某種手段通知相關(guān)代碼。經(jīng)常使用的技巧是設(shè)計(jì)一個(gè)委托協(xié)議,令關(guān)注此事件的對(duì)象遵從該協(xié)議,對(duì)象成了delegate 之后,就可以在相關(guān)事件發(fā)生時(shí)得到通知了。

使用塊來(lái)寫的話,代碼會(huì)更清晰,使得代碼更加緊致。

在創(chuàng)建對(duì)象時(shí),可以使用內(nèi)聯(lián)的handle 塊將相關(guān)業(yè)務(wù)邏輯一并聲明。

在有多個(gè)實(shí)例需要監(jiān)控時(shí),如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對(duì)象來(lái)切換,若改用handle 塊來(lái)實(shí)現(xiàn),則可直接將塊與相關(guān)對(duì)象放在一起。

設(shè)計(jì)API 時(shí)如果用到handle 塊,那么可以增加一個(gè)參數(shù),使調(diào)用者可通過(guò)此參數(shù)來(lái)決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行。

第 40 條:用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)

如果塊所捕獲的對(duì)象直接或間接地保留了塊本身,那么就得當(dāng)心保留環(huán)問(wèn)題。

一定要找個(gè)合適的時(shí)機(jī)解除保留環(huán),而不能把責(zé)任推給API 的調(diào)用者。

第 41 條:多用派發(fā)隊(duì)列,少用同步鎖

  1. 如果有多個(gè)線程要執(zhí)行同一份代碼,那么有時(shí)可能會(huì)出問(wèn)題,這種情況下,通常要使用鎖來(lái)實(shí)現(xiàn)某種同步機(jī)制。在GCD 出現(xiàn)之前,有兩種辦法:

采用內(nèi)置的 “同步塊”(synchronization block)

  • (void)synchronizedMethod {
    @synchronized(self){
    //safe
    }
    }

/*

這種寫法會(huì)根據(jù)給定的對(duì)象,自動(dòng)創(chuàng)建一個(gè)鎖,并等待塊中的代碼執(zhí)行完畢,執(zhí)行到代碼結(jié)尾,鎖就釋放了。

但是,濫用 @synchronized(self) 則會(huì)降低代碼效率,因?yàn)楣灿猛粋€(gè)鎖的那些同步塊,都必須按順序執(zhí)行。
*/

直接使用NSLock 對(duì)象,也可以使用NSRecursiveLock “遞歸鎖”,線程能多次持有該鎖,而且不會(huì)出現(xiàn)死鎖。

_lock = [[NSLock alloc] init];

  • (void)synchronizedMethod {
    [_lock lock];
    //safe
    [_lock unlock];
    }
  1. 對(duì)于上面兩種方法,有些缺陷,同步塊會(huì)導(dǎo)致死鎖,直接使用鎖對(duì)象,遇到死鎖,就會(huì)非常麻煩。

  2. GCD 以更簡(jiǎn)單、更高效的形式為代碼加鎖。

例子:屬性是開發(fā)者經(jīng)常需要同步的地方,可以使用atomic 特質(zhì)來(lái)修飾屬性,來(lái)保證其原子性,每次肯定可以從中獲取到有效值,然而在同一個(gè)線程上多次調(diào)用獲取方法(getter),每次獲取到結(jié)果未必相同,在兩次訪問(wèn)操作之間,其他線程可能會(huì)寫入新的屬性值。

使用 “串行同步隊(duì)列”,將讀取操作及寫入操作都安排在同一個(gè)隊(duì)列里,即可保證數(shù)據(jù)同步。

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

  • (NSString *)name {
    __block NSString *tempName;
    dispatch_sync(_syncQueue, ^{
    tempName = _name;
    });
    return tempName;
    }

  • (void)setName:(NSString *)name {
    dispatch_sync(_syncQueue, ^{
    _name = name;
    });
    }

/*
上面是用串行同步隊(duì)列來(lái)保證數(shù)據(jù)同步:把設(shè)置操作與獲取操作都安排在序列化的隊(duì)列里執(zhí)行,這樣子,所有針對(duì)屬性的訪問(wèn)操作都是同步的了。
*/

/*
進(jìn)一步優(yōu)化,設(shè)置方法不一定非得是同步的,因?yàn)椴恍枰祷刂怠_@樣子可以提高設(shè)置方法的執(zhí)行速度,而讀取操作與寫入操作依然會(huì)按照順序執(zhí)行。

但是這里可能發(fā)現(xiàn)這種寫法比原來(lái)慢,因?yàn)閳?zhí)行異步派發(fā)時(shí),需要拷貝塊。
*/

  • (void)setName:(NSString *)name {
    dispatch_async(_syncQueue, ^{
    _name = name;
    });
    }

/*
我們現(xiàn)在目的就是要做到:多個(gè)獲取方法可以并發(fā)執(zhí)行,而獲取方法與設(shè)置方法不能并發(fā)執(zhí)行。

我們還可以使用并發(fā)隊(duì)列來(lái)實(shí)現(xiàn),現(xiàn)在都是在并發(fā)隊(duì)列上面執(zhí)行任務(wù),但是順序不能控制,我們可以用柵欄(barrier)來(lái)解決。

這兩個(gè)函數(shù)可以向隊(duì)列派發(fā)塊,將其作為柵欄來(lái)使用:
dispatch_barrier_sync(dispatch_queue_t queue,^(void)block)
dispatch_barrier_async(dispatch_queue_t queue,^(void)block)

在隊(duì)列中,柵欄塊必須單獨(dú)執(zhí)行,不能與其他塊并行,這只對(duì)并發(fā)隊(duì)列有意義,因?yàn)榇嘘?duì)列中的塊總是按照順序逐個(gè)執(zhí)行的。并發(fā)隊(duì)列如果發(fā)現(xiàn)接下來(lái)要處理的塊是柵欄塊,那么就一直要等到當(dāng)前所有的并發(fā)塊都執(zhí)行完畢,才會(huì)單獨(dú)執(zhí)行這個(gè)柵欄塊。執(zhí)行完?yáng)艡趬K,再按照正常方式向下處理。
*/

-----> 現(xiàn)在并發(fā)隊(duì)列 還不能滿足要求
_syncQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  • (NSString *)name {
    __block NSString *tempName;
    dispatch_sync(_syncQueue1, ^{
    tempName = _name;
    });
    return tempName;
    }

  • (void)setName:(NSString *)name {
    dispatch_async(_syncQueue1, ^{
    _name = name;
    });
    }

-----> 轉(zhuǎn)換寫法 用柵欄塊控制屬性的設(shè)置方法 不能并行
_syncQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  • (NSString *)name {
    __block NSString *tempName;
    dispatch_sync(_syncQueue1, ^{
    tempName = _name;
    });
    return tempName;
    }

  • (void)setName:(NSString *)name {
    dispatch_barrier_async(_syncQueue1, ^{
    _name = name;
    });
    }

派發(fā)隊(duì)列可用來(lái)表述同步語(yǔ)義(synchronization semantic),這種做法要比使用@synchronized 塊或則NSLock 對(duì)象更簡(jiǎn)單。

將同步與異步派發(fā)結(jié)合起來(lái),可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為,而這么做卻不會(huì)阻塞執(zhí)行異步派發(fā)的線程。

使用同步隊(duì)列及柵欄塊,可以使同步行為更加高效。

第 42 條:多用GCD,少用performSelector 方法

performSelector 可以任意調(diào)用方法,還可以延遲調(diào)用,還可以指定運(yùn)行方法所用的線程。

但是如果是動(dòng)態(tài)來(lái)調(diào)用performSelector 方法的時(shí)候,編譯器都不知道執(zhí)行的選擇子是什么,必須到了運(yùn)行期才能確定,這種情況在ARC 下會(huì)報(bào)警告,因?yàn)榫幾g器不知道方法名,所以不能運(yùn)用ARC 內(nèi)存管理規(guī)則來(lái)判定返回值是否應(yīng)該釋放,對(duì)于這種情況ARC 不會(huì)幫我們添加任何釋放操作。

performSelector 方法調(diào)用的時(shí)候?qū)τ诜祷仡愋椭荒苁莢oid或?qū)ο箢愋停瑢?duì)于有返回值的需要自己做多次轉(zhuǎn)換,對(duì)于參數(shù)的也最多只能傳2個(gè),介于此performSelector 還是比較不方便的。

對(duì)于performSelector 遇到的問(wèn)題,我們都可以用GCD 解決。

performSeletor 系列方法在內(nèi)存管理方面容易有疏忽。它無(wú)法確定將要執(zhí)行的選擇子具體是什么,因而ARC 編譯器也就無(wú)法插入適當(dāng)?shù)膬?nèi)存管理方法。

performSeletor 系列方法所能處理的選擇子太過(guò)局限了,選擇子的返回類型及發(fā)送給方法的參數(shù)個(gè)數(shù)收到限制。

如果想把任務(wù)放在另一個(gè)線程上執(zhí)行,那么最好不要用performSeletor 系列方法,而是應(yīng)該把任務(wù)封裝到塊里,然后調(diào)用大中樞派發(fā)機(jī)制的相關(guān)方法來(lái)實(shí)現(xiàn)。

第 43 條:掌握GCD 及操作隊(duì)列的使用時(shí)機(jī)

在執(zhí)行后臺(tái)任務(wù)時(shí),GCD 不一定是最佳方式,還有一種技術(shù)叫做NSOperationQueue,開發(fā)者可以把操作以NSOperation 子類的形式放在隊(duì)列中,而這些操作也可以并發(fā)執(zhí)行。

GCD 是純C 的API,操作隊(duì)列的則是Objective-C 的對(duì)象。用NSOperationQueue 類的“addOperationWithBlock” 方法搭配NSBlockOperation 類操作隊(duì)列,其語(yǔ)法與純GCD 方式非常類似。使用NSOperation 及NSOperationQueue 的好處如下:

取消某個(gè)操作。如果使用操作隊(duì)列,那么想取消操作是很容易的。運(yùn)行任務(wù)之前,可以在NSOperation 對(duì)象調(diào)用cancel 方法,該方法會(huì)設(shè)置對(duì)象內(nèi)的標(biāo)識(shí)位,用以表明此任務(wù)不需執(zhí)行,不過(guò),已經(jīng)啟動(dòng)的任務(wù)無(wú)法取消。若不是操作隊(duì)列,而是把塊安排到GCD 隊(duì)列,那就無(wú)法取消了。那套架構(gòu)是 “安排好任務(wù)之后就不管了”。開發(fā)者可以在應(yīng)用層自己來(lái)實(shí)現(xiàn)取消功能,不過(guò)這樣子做需要編寫很多代碼,而那些代碼其實(shí)已經(jīng)由操作隊(duì)列實(shí)現(xiàn)好了。

指定操作間的依賴關(guān)系。一個(gè)操作可以依賴其他多個(gè)操作。開發(fā)者能夠指定操作之間的依賴關(guān)系,使特定的操作必須在另外一個(gè)操作順序執(zhí)行完畢方可執(zhí)行,比方說(shuō),從服務(wù)器下載并處理文件的動(dòng)作,可以用操作來(lái)表示,而在處理其他文件之前,必須先下載 “清單文件”。后續(xù)的下載操作,都要依賴于先下載清單文件這一操作。如果操作隊(duì)列允許并發(fā)的話,那么后續(xù)的多個(gè)下載操作就可以同時(shí)執(zhí)行,但前提是它們所依賴的那個(gè)清單文件下載操作已經(jīng)執(zhí)行完畢。

通過(guò)鍵值觀測(cè)機(jī)制監(jiān)控NSOperation 對(duì)象的屬性。NSOperation 對(duì)象有許多屬性都適合通過(guò)鍵值觀測(cè)機(jī)制(KVO)來(lái)監(jiān)聽,比如可以通過(guò)isCancalled 屬性來(lái)判斷任務(wù)是否取消。如果想在某個(gè)任務(wù)變更期狀態(tài)時(shí)得到通知,或是想用比GCD 更為精細(xì)的方式來(lái)控制所要執(zhí)行的任務(wù),那么鍵值觀測(cè)機(jī)制會(huì)很有用。

制定操作的優(yōu)先級(jí)。操作的優(yōu)先級(jí)表示此操作與隊(duì)列其他操作之間的優(yōu)先關(guān)系。優(yōu)先級(jí)高的操作先執(zhí)行,優(yōu)先級(jí)低的后執(zhí)行。操作隊(duì)列的調(diào)度算法已經(jīng)比較成熟。反之,GCD 則沒(méi)有直接實(shí)現(xiàn)此功能的辦法,GCD 的隊(duì)列有優(yōu)先級(jí),但是是針對(duì)整個(gè)隊(duì)列來(lái)說(shuō)的,而不是針對(duì)每個(gè)塊來(lái)說(shuō)的。對(duì)于優(yōu)先級(jí)這一點(diǎn),操作隊(duì)列所提供的功能比GCD 更為便利。

重用NSOperation 對(duì)象。系統(tǒng)內(nèi)置類一些NSOperation 的子類供開發(fā)者調(diào)用,要是不想用這些固有子類的話,那就得自己來(lái)創(chuàng)建了。這些類就是普通的Objective-C 對(duì)象,能夠存放任何信息。對(duì)象在執(zhí)行時(shí)可以充分利用存于其中的信息,而且還可以隨意調(diào)用定義在類中的方法。這比派發(fā)隊(duì)列中哪些簡(jiǎn)單的塊要強(qiáng)大。這些NSOperation 類可以在代碼中多次使用。

在解決多線程與任務(wù)管理問(wèn)題時(shí),派發(fā)隊(duì)列并非唯一方案。

操作隊(duì)列提供了一套高層的Objective-C API,能實(shí)現(xiàn)純GCD 所具備的絕大部分功能,而且還能完成一些更為復(fù)雜的操作,那些操作若改用GCD 來(lái)實(shí)現(xiàn),則需另外編寫代碼。

第 44 條:通過(guò)Dispatch Group 機(jī)制,根據(jù)系統(tǒng)資源狀況來(lái)執(zhí)行任務(wù)

一系列任務(wù)可歸入一個(gè)dispatch group 之中。開發(fā)者可以在這組任務(wù)執(zhí)行完畢時(shí)獲得通知。

通過(guò)dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)。此時(shí)GCD 會(huì)根據(jù)系統(tǒng)資源狀況來(lái)調(diào)度這些并發(fā)執(zhí)行的任務(wù)。開發(fā)者若自己實(shí)現(xiàn)此功能,則需編寫大量代碼。

第 45 條:使用dispatch_once 來(lái)執(zhí)行只需運(yùn)行一次的線程安全代碼

  1. 對(duì)于單例我們創(chuàng)建唯一實(shí)例,之前都是用@synchronized 加鎖來(lái)解決多線程的問(wèn)題,GCD 提供了一個(gè)更加簡(jiǎn)單的方法來(lái)實(shí)現(xiàn)。

//單例
+(instancetype)shareInstance{
static dispatch_once_t onceToken;
static PXYAdvertisingPagesHelper *shareInstance;
dispatch_once(&onceToken, ^{
shareInstance = [PXYAdvertisingPagesHelper new];
shareInstance.adTimeout = 5.0;
});
return shareInstance;
}

  1. 使用dispatch_once 可以簡(jiǎn)化代碼,并且徹底保證線程安全。

經(jīng)常需要編寫 “只需執(zhí)行一次的線程安全代碼”。通常使用GCD 所提供的dispatch_once 函數(shù),很容易就能實(shí)現(xiàn)此功能。

標(biāo)記應(yīng)該聲明在static 或 global 作用域中,這樣的話,在把只需執(zhí)行一次的快傳給dispatch_once 函數(shù)時(shí),傳進(jìn)去的標(biāo)記也是相同的。

第 46 條:不要使用dispatch_get_current_queue

Mac OS X 與 iOS 的UI 事務(wù)都需要在主線程上執(zhí)行,而這個(gè)線程就相當(dāng)于GCD 中的主隊(duì)列。

dispatch_get_current_queue 這個(gè)函數(shù)返回當(dāng)前正在執(zhí)行代碼的隊(duì)列,但是在iOS 6.0版本起,已經(jīng)棄用這個(gè)函數(shù)了。

該函數(shù)有種典型的錯(cuò)誤用法,就是用它檢測(cè)當(dāng)前隊(duì)列是不是某個(gè)特定的隊(duì)列,試圖以此來(lái)避免執(zhí)行同步派發(fā)時(shí)可能遭遇到的死鎖問(wèn)題。

看下下面這個(gè)代碼:

dispatch_queue_t queueA = dispatch_queue_creat("com.pengxuyuan.queueA",NULL);
dispatch_queue_t queueB = dispatch_queue_creat("com.pengxuyuan.queueB",NULL);

dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dipatch_sync(queueA, ^{
//DeadLock
});
});
});

//這里是個(gè)典型的死鎖現(xiàn)象,queueA 串行隊(duì)列上面的同步任務(wù)相互等待了。

dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_block_t block = ^();
if(dispatch_get_current_queue() == queueA){
block();
}else{
dipatch_sync(queueA, block);
}
});
});
//但是用dispatch_get_current_queue 這個(gè)來(lái)判斷,當(dāng)前返回的是queueB,這里還是會(huì)去執(zhí)行dipatch_sync(queueA, block); 造成死鎖

  1. 因?yàn)殛?duì)列有層級(jí)關(guān)系,所以 “檢查當(dāng)前隊(duì)列是否為執(zhí)行同步派發(fā)所用的隊(duì)列” 這種辦法,并不是總是奏效的。

  2. 要解決這個(gè)問(wèn)題,可以通過(guò)GCD 所提供的功能來(lái)設(shè)定 “隊(duì)列特有數(shù)據(jù)”,此功能可以把任意數(shù)據(jù)以鍵值對(duì)的形式關(guān)聯(lián)到隊(duì)列里。假如根據(jù)指定的鍵獲取不到關(guān)聯(lián)數(shù)據(jù),那么就會(huì)沿著層級(jí)體系向上查找,直到找到數(shù)據(jù)或到根隊(duì)列為止。

dispatch_queue_t queueA = dispatch_queue_creat("com.pengxuyuan.queueA",NULL);
dispatch_queue_t queueB = dispatch_queue_creat("com.pengxuyuan.queueB",NULL);

static int kQueueSpecific;
CFStringRef queueSepcificValue = CFSTR("queueA");

dispatch_queue_set_specific(queueA,
&kQueueSpecific,
(void *)queueSepcificValue,
(dispatch_function_t));
dispatch_sync(queueB, ^{
dispatch_block_t block = ^();
CFStringRef retrievedValue = dispatch_queue_set_specific(&kQueueSpecific);
if(retrievedValue){
block();
}else{
dipatch_sync(queueA, block);
}
});

dispatch_get_current_queue 函數(shù)的行為常常與開發(fā)者所預(yù)期的不同。此函數(shù)已經(jīng)廢棄,只應(yīng)做調(diào)試之用。

由于派發(fā)隊(duì)列是按層級(jí)來(lái)組織的,所以無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述 “當(dāng)前隊(duì)列” 這一概念。

dispatch_get_current_queue 函數(shù)用于解決由不可重入的代碼引發(fā)的死鎖,然而能用此函數(shù)的解決的問(wèn)題,通常也能改用 “隊(duì)列特定數(shù)據(jù)” 來(lái)解決。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

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