聲明:這個(gè)筆記的系列是我每天早上打開電腦第一件做的事情,當(dāng)然使用的時(shí)間也不是很多因?yàn)檫€有其他的事情去做,雖然吧自己買了紙質(zhì)的書但是做筆記和看的時(shí)候基本都是看的電子版本,一共52個(gè)Tip,每一個(gè)Tip的要點(diǎn)我是完全謄寫下來的,害怕自己說的不明白所以就謄寫也算是加強(qiáng)記憶,我會(huì)持續(xù)修改把自己未來遇到的所有相關(guān)的點(diǎn)都加進(jìn)去,最后希望讀者尊重原著,購買正版書籍。PS:不要打賞要喜歡~
GitHub代碼網(wǎng)址,大大們給個(gè)鼓勵(lì)Star啊。
整個(gè)系列筆記目錄
《Effective Objective-C 2.0》第一份讀書筆記
《Effective Objective-C 2.0》第二份讀書筆記
《Effective Objective-C 2.0》第三份讀書筆記
第六章 block和GCD (block和Grand Central Dispatch)
“塊”是一種可在C,C++以及Objective-C代碼中使用的“語法閉包(lexical closure)”,借用此機(jī)制,開發(fā)者可講代碼像對象一樣傳遞,令其在不同的情況下運(yùn)行。
GCD是一種和塊有關(guān)的技術(shù),它提供了對線程的抽象,而這種抽象則基于“派發(fā)隊(duì)列(dispatch queue)”。開發(fā)者可將塊排入隊(duì)列中,由GCD負(fù)責(zé)處理所有調(diào)用事宜。GCD會(huì)根據(jù)系統(tǒng)資源情況,適時(shí)的創(chuàng)建,復(fù)用,摧毀后臺線程。
37.理解“塊”這一概念
塊的基本知識
塊與函數(shù)類似,只不過是直接定義在另一個(gè)函數(shù)里的,和定義它的那個(gè)函數(shù)共享同一個(gè)范圍內(nèi)的東西。塊使用 “^” 符號來表示,后面跟著花括號。
int additional = 5;
__ block int iSu = 8;
int (^ addBlock) (int a, int b) = ^(int a , int b){
return a + b + additional;
iSu++;
}
int add = addBlock(2,5) // add = 12
聲明變量的時(shí)候可以加上_ _block修飾符,變量的地址就可以從stack轉(zhuǎn)移到heap上,從而持有這個(gè)變量,而如果只是局部變量,block會(huì)copy變量數(shù)值,但是不會(huì)再受到變量改變的影響。
如果塊所捕獲的變量是對象類型,那么就會(huì)自動(dòng)保留它。系統(tǒng)在釋放這個(gè)塊的時(shí)候,那會(huì)將其一并釋放。就是引出一個(gè)和塊有關(guān)的重要問題。塊本身可視為對象。有引用計(jì)數(shù)。當(dāng)最后一個(gè)指向塊的引用移走之后,塊就回收了。
如果將塊定義在OC的實(shí)例方法中,那么除了可以訪問類的所有實(shí)例變量之外,還可以使用self變量。塊總能修改實(shí)例變量,所以在聲明時(shí)無須加__block。不過,如果實(shí)例變量與self所指代的實(shí)例關(guān)聯(lián)在一起的。例如,下面這個(gè)塊聲明在EOCClass類的方法中。
@interface EOCClass
- (void)anInstancemethod{
void (^someBlock)()= ^{
_anInstanceVariable = “Something”;
NSLog(@“_anInstanceVariable = %@”,_anInstanceVariable);
}
}
如果某個(gè)EOCClass實(shí)例正在執(zhí)行anInstanceMethod方法,那么self變量就指向此實(shí)例。由于塊里沒有明確使用self變量,所以很容易就會(huì)忘記self變量其實(shí)也為塊所捕獲了。直接訪問實(shí)例變量和通過self來訪問是等效的。
self -> _anInstanceVariable = @“Something” ;
那么這種情況下就會(huì)導(dǎo)致“保留環(huán)”。
塊的內(nèi)存布局:

首個(gè)變量是指向Class對象的指針,該指針叫做isa。其余內(nèi)存里含有塊對象正常運(yùn)轉(zhuǎn)所需的各種消息。
- invoke變量,這是個(gè)函數(shù)指針,指向塊的實(shí)現(xiàn)代碼。函數(shù)原型至少要接受一個(gè)void * 型的參數(shù),此參數(shù)代表塊。
- descriptor 變量是指向結(jié)構(gòu)體的指針,每個(gè)塊里都包含此結(jié)構(gòu)體,其中聲明塊對象的總體大小,還聲明了copy與dispose這兩個(gè)輔助函數(shù)所對應(yīng)的函數(shù)指針。輔助函數(shù)在拷貝及丟棄塊對象時(shí)運(yùn)行。
塊還會(huì)把捕捉到的所有變量都拷貝一份。放在descriptor后面。捕捉多少個(gè)變量,就會(huì)占據(jù)多少內(nèi)存空間。請注意,拷貝的并不是對象本身,而是指向這些對象的指針變量。用于在執(zhí)行塊的時(shí)候,從內(nèi)存中吧這些捕捉到的變量讀出來。
全局block,棧block以及堆block
定義塊的時(shí)候,其所占的內(nèi)存區(qū)域是分配在棧中的,這就是說,塊只在定義它的那個(gè)范圍內(nèi)有效。
void(^block)();
if ( some condition){
block = ^ {
NSLog(@“Block A”);
}
} else {
block = ^ {
NSLog(@“Block B”);
}
}
block();
解決此問題的辦法就是發(fā)送copy消息以拷貝。
if ( some condition ){
block = [^{
NSLog(@“Block A”);
} copy];
}else {
block = [^{
NSLog(@“Block B”);
} copy];
}
那么其他文章里面有相關(guān)的block類型分類:
全局block(_NSConcreteGlobalBlock)的block要么是空block,要么是不訪問任何外部變量的block。它既不在棧中,也不再堆中。
棧block(_NSConcreteStackBlock)的block有閉包行為,也就是有訪問外部變量,并且block只且只有有一次執(zhí)行,因棧中的空間是可重復(fù)使用的,所以當(dāng)棧中的block執(zhí)行一次之后就會(huì)被清空出棧,所以無法多次使用。
堆block(_NSConcreteMallocBlock)的block有閉包行為,并且該block需要被多次執(zhí)行。當(dāng)需要多次執(zhí)行時(shí),就會(huì)把該block從棧中復(fù)制到堆中。
要點(diǎn):
- 塊是C,C++,Objective-C中的詞語閉包。
- 塊可接受參數(shù),也可返回值。
- 塊可以分配在棧或堆上,也可以是全局的。分配在站上的block可拷貝到堆里,這樣的話,就和標(biāo)準(zhǔn)的Objective-C對象一樣,具備引用計(jì)數(shù)了。
38.為常用的塊類型創(chuàng)建typedef
要點(diǎn):
- 以typedef重新定義塊類型,可令塊變量用起來更加簡單。
- 定義新類型時(shí)應(yīng)準(zhǔn)從現(xiàn)有的命名習(xí)慣,勿使其名稱與別的類型相沖突。
- 不妨為同一塊簽名定義多個(gè)類型別名。如果要重構(gòu)的代碼使用了塊類型的某個(gè)別名。那么只需要修改相應(yīng)typedef中塊簽名即可,無須改動(dòng)其他的tyoedef。
39.用handler塊降低代碼分散程度
iOS 上有一個(gè)叫”系統(tǒng)監(jiān)控器 ”(system watchdog)在發(fā)現(xiàn)某個(gè)應(yīng)用程序的主線程已經(jīng)阻塞了一段時(shí)間之后,就會(huì)令其終止。
異步方法在執(zhí)行任務(wù)之后,需要以某種手段通知相關(guān)代碼。實(shí)現(xiàn)此功能有很多辦法。常用的技巧是設(shè)計(jì)一個(gè)委托協(xié)議,令關(guān)注此事件的對象遵從該協(xié)議。
這里面呢我需要再重新規(guī)劃一下委托模式的規(guī)范:
第一如果我們要一個(gè)類監(jiān)視另一類的某個(gè)屬性,那么。
在監(jiān)視的那個(gè)類里面
@protocol EOCNetworkFecherDelegate<NSObject>
(void)newworkFetcher:(EOCNetworkFetcher *)networkFether didFinishWithData:(NSData *)data;
@end
然后給一個(gè)需要賦值給監(jiān)視類的delegate屬性
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate>delegate;
因?yàn)槭且O(jiān)視一個(gè)data屬性的 ,所以在被監(jiān)視的類的.m文件中我們需要:
[_delegate newworkFether:networkFether didFinishWithData:data];
然后再監(jiān)視的類里面我們首先是
EOCNetworkFecher對象 newEOC newEOC.delegate = self;
然后實(shí)現(xiàn)newworkFecher didFinishWithData:
(void)newworkFetcher:(EOCNetworkFetcher *)networkFether didFinishWithData:(NSData *)data{
做對于data數(shù)據(jù)收取情況的相關(guān)對策。
}
該Tip的主題:
那么現(xiàn)在如果我們想要通過block來傳遞監(jiān)聽的話:
被監(jiān)聽類:
typedef void (^ iSuNetWorkFecherCompletionHandler)(NSData data,NSError * error);
@interface iSuNetWorkFetcher :NSObject
(void)iSuStartWithCompletionHandler:(iSuNetworkFecherCompletionHandler)completion;
然后再.m里面?zhèn)鬟fblock里面的數(shù)值
(void)iSuStartWithCompletionHandler:(iSuNetworkFecherCompletionHandler) completion{
int a = 5;
NSError * error;
completion(a,error);
}
然后監(jiān)聽的類里面 。
首先是對象iSuTest。
[iSuTest iSuStartWithCompletionHandler:^(int data, NSError * error){
NSLog(@“監(jiān)聽過來的數(shù)值為:%@”,data);
}];
要點(diǎn):
- 在創(chuàng)建對象時(shí),可以使用內(nèi)聯(lián)的handler塊將相關(guān)業(yè)務(wù)邏輯一并聲明。
- 在有多個(gè)實(shí)例需要監(jiān)控的時(shí)候,如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對象來切換,而若改用handler塊來實(shí)現(xiàn),則可直接將塊和相關(guān)對象放在一起。
- 設(shè)計(jì)API時(shí)如果用到handler塊,那么可以增加一個(gè)參數(shù),使調(diào)用者可以通過此參數(shù)來決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行。
40.用塊引用其所屬對象時(shí)不要出現(xiàn)保留環(huán)
//EOCNetworkFetcher.h
#import <Foundation/Foundation.h>
typedef void(^EOCNetworkFetcherCompletionHandler)(NSData * data)
@interface EOCNetworkFether:NSObject
@property (nonatomic, strong, readonly) NSURL * url;
(id) initWithURL:(NSURL *)url;
(void)startWithCompletionHandle:(EOCNetworkFetcherCompletionHandler)completion
@end
//EOCNetworkFetcher.m
#import “EOCNetworkFetcher.h”
@interface EOCNetworkFetcher ()
@property(nonatomic, strong, readwrite) NSURL *url;
@property(nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property(nonatomic, strong) NSData * downloadedData;
@implementation EOCNetworkFetcher
(id)initWithURL:(NSURL *)url{
if (slef = [super init]){
_url = url;
}
return self;
}
(void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion{
self.completionHandler = completion;
//Start the request;
//Request sets downloadedData property
// When request is finished , p_requestCompleted is called;
}
(void)p_requestCompleted{
if(_completionHandler){
_completionHadnler(_downloadedData);
}
}
而另一個(gè)類可能會(huì)創(chuàng)建這種網(wǎng)絡(luò)數(shù)據(jù)獲取器對象
@implementation EOCClass {
EOCNetworkFetcher * _networkFethcer;
NSData * _fetchedData;
}
- (void)downLoadData{
NSURL * url =[nsurl alloc]initWithString : @“www.baidu.com”;
_networkFethcer =[EOCNetworkFetcher alloc]initwithURL:url];
[_networkFethcer startWithCompletionHandler:^(NSData * data){
NSLog:(@“url = %@’,_networkFethcer.url);
_fetchedData = data;
}];
}
這段代碼看上去是沒有問題的 。 但是存在循環(huán)引用。
但是因?yàn)閔andler 里面要設(shè)置_fechedData = data ,所以handler是要持有self(EOCClass)的。而實(shí)例EOCClass又通過屬性持有著網(wǎng)絡(luò)獲取器。
解決辦法就是:要么令_networkFetcher實(shí)例變量不再引用獲取器,要么令獲取器的completionHandler屬性不再持有handler塊。在這個(gè)例子中,應(yīng)該等待comletion handler塊執(zhí)行完畢之后,再去打破保留環(huán),比如:
[_networkFecher startWithCompletionHandler:^(NSData * data){
NSLog(@“Request for URL:%@ finished ” ,_networkFetcher.url );
_fetchedData = data;
_networkFetcher = nil;
}];
但是這種情況很自由在執(zhí)行handler的時(shí)候才會(huì)調(diào)用,如果completion handler一直不運(yùn)行,那么保留環(huán)就無法被打破,內(nèi)存就會(huì)泄露。
我們可以嘗試 不持有屬性_fetchedData
(void)setcondDownload{
NSURL * url =[[NSURL alloc]initWithString:@"www.baidu.com"];
SecondYearNetworkFetcher * networkFetcher =[[SecondYearNetworkFtcher alloc] initWithURL:url];
[networkFetcher startWithCompletionHandler:^(NSData *data) {
NSLog(@"%@",networkFetcher.url);
_fetchedData = data;
}];
}
//也就是一個(gè)局部變量networkFetcher;
但是這樣也是含有保留環(huán)的;
因?yàn)橐驗(yàn)閔andler獲取網(wǎng)址是通過networkFecher的 那么也就持有了這個(gè)類。
但是這個(gè)networkFecher又通過block 來持有handler;
那么解決辦法就是在block運(yùn)行觸發(fā)的時(shí)候,將completionHandler屬性置為nil
(void)p_requestCompleted{
if (_completionHandler){
_completionHandler(_downloadedData);
}
self.completionHandler = nil;
}
這樣一來,只要下載請求執(zhí)行完成,保留環(huán)就被解除了。
要點(diǎn):
- 如果塊所捕獲的對象直接或間接地保留了塊本身,那么就要當(dāng)心保留環(huán)問題。
- 一定要找個(gè)適當(dāng)?shù)臅r(shí)機(jī)解除保留環(huán),而不能把責(zé)任推給API的調(diào)用者。
41.多用派發(fā)隊(duì)列,少用同步鎖
有兩種添加鎖頭的方法:
(void)synchronizedMehod{
@synchronized(self){
}
}
或者是:
_lock =[NSLock alloc] init];
(void)synchronizedMethod{
[_lock lock];
//safe
[_lock unlock];
}
這里面也確定了一下設(shè)置屬性的時(shí)候?yàn)槭裁词欠窃有缘?,而不是線程安全的原子性的。
因?yàn)闉E用也就是每一個(gè)屬性都用原子性的話會(huì)所有的同步快都會(huì)彼此搶奪同一個(gè)鎖。要是有很多個(gè)屬性都這么寫的話,那么每個(gè)屬性的同步塊都要等其他所有同步塊執(zhí)行完畢才能執(zhí)行。而且屬性的這種知識提供了某種程度的“線程安全(thread safety)”,但是無法保證訪問該對象時(shí)絕對是線程安全的。
有個(gè)簡單并且高效的辦法可以替代同步塊或者鎖對象,那就是“串行同步隊(duì)列”。
_syncQueue = dispatch_queue_create(“com.effectiveobjective.syncQueue”,NULL)
這個(gè)最后的一個(gè)參數(shù)"0"的時(shí)候是并行,NULL是串行應(yīng)該都知道吧。
- (NSString*)someString{
__ block NSString * localSomeString;
dispatch_sync (_syncQueue,^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_sync(_syncQueue,^{
_someString = someString;
});
}
這樣就變成了同步,優(yōu)化一下設(shè)置數(shù)值的時(shí)候是不用同步的,
- (void)setSomeString:(NSString *)someString{
dispatch_async(_syncQueue,^{
_someString = someString;
});
}
從調(diào)用者的角度來看,這個(gè)小改動(dòng)可以提升設(shè)置方法的執(zhí)行速度,但是如果你測一下程序的性能,那么可能會(huì)發(fā)現(xiàn)這種寫法比原來慢,因?yàn)閳?zhí)行異步派發(fā)的時(shí)候,需要拷貝塊。如果塊里面的運(yùn)行操作比較簡單,那么運(yùn)行的時(shí)間就會(huì)變慢,但是如果繁瑣的話就會(huì)快一點(diǎn)。
那么第二個(gè)問題,我們想要讀寫一個(gè)屬性。讀的時(shí)候可以寫,但是寫的時(shí)候停止讀的操作。
這個(gè)時(shí)候我們可以同步并發(fā)隊(duì)列的柵欄塊來試下你這個(gè)效果。
_syncQueue = dispatch_get_global_queue(DISPATHC_QUEUE_PRIOPRTY_DEFAULT,0);
- (NSString *)someString{
_ _ block NSString * localSomeString;
dispathch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
})
}
這樣的并發(fā)柵欄隊(duì)列要比串行要快。這個(gè)意思是必須等set完事才能進(jìn)行g(shù)et方法。
要點(diǎn):
- 派發(fā)隊(duì)列可用來表述同步語義(synchronization semantic),這種做法要比使用@synchronized塊或NSLock對象更簡單。
- 將同步與異步派發(fā)結(jié)合起來,可以實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為,而這么做卻不會(huì)阻塞執(zhí)行異步派發(fā)的線程。
- 使用同步隊(duì)列以及柵欄塊,可以令同步行為更加高效。
42.多用GCD,少用performSelector系列方法
關(guān)于perform方法:
object調(diào)用perfome方法返回的類型是id,雖然可以是void,如果想返回整數(shù)型或者是浮點(diǎn)型那就需要復(fù)雜的轉(zhuǎn)換操作了。而這種轉(zhuǎn)換很容易出錯(cuò)。performSelector還有幾個(gè)版本,可以在發(fā)消息時(shí)順便傳遞參數(shù)。
所以對比GCD和perform方法我們經(jīng)常選擇GCD。
[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]
})
對比任務(wù)放主線程的兩種形式:
[self performSelectorOnMainThread:withObject:waitUntilDone:];
//Using dispathc_async
dispathc_async(dispathc_get_main_queue(),^{
[self doSomething];
});
要點(diǎn):
- performSelector系列方法在內(nèi)存管理方法容易有疏失。它無法確定將要執(zhí)行的選擇子具體是什么,因而ARC編譯器也就無法插入適當(dāng)?shù)膬?nèi)存管理方法。
- performSelector系列方法所能處理的選擇子太過局限了,選擇子的返回值類型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受到限制。
- 如果想要把任務(wù)放在另一個(gè)線程上執(zhí)行,那么最好不要用performSelector系列方法,而是應(yīng)該把任務(wù)封裝到塊里,然后調(diào)用GCD的相關(guān)方法來實(shí)現(xiàn)。
43.掌握GCD及操作隊(duì)列的使用時(shí)機(jī)
NSOperationi以及NSOperationQueue的好處如下:
- 取消某個(gè)操作。如果使用派發(fā)隊(duì)列 ,那么取消某個(gè)操作是麻煩的,他只存在fire and forget。而操作隊(duì)列只要調(diào)用cancel方法就可以了。
- 指定操作間的依賴關(guān)系。GCD也能完成響應(yīng)的操作,只不過多任務(wù)的要好好算算邏輯。而操作隊(duì)列調(diào)用addDependency就可以了。
- 通過鍵值觀測機(jī)制監(jiān)控NSOperation對象的屬性。NSOperation有好多屬性能夠被監(jiān)聽。比如isCancelled等屬性。
- 指定操作的優(yōu)先級。
- 重用NSOperation對象
比如NSBlockOperation對象,使用方法是:
第一種:
NSBlockOperation * block1 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@“調(diào)用子類方法的block”);
}];
[block1 setCompletionBlock:^{
NSLog(@“結(jié)束調(diào)用”);
}];
[block1 start];
第二種
詳細(xì)講解
NSBlockOperation * op1 =[NSBlockOperation alloc] init];
[op1 addExecutionBlock:^{
sleep(10)
NSLog(@“第一個(gè)線程第一個(gè)任務(wù)”);
}];
[op1 addExecutionBlock:^{
sleep(4)
NSLog(@“第二個(gè)線程第一個(gè)任務(wù)”);
}];
[op1 addExecutionBlock:^{
sleep(6)
NSLog(@“第一個(gè)線程第二個(gè)任務(wù)”);
}];
結(jié)論:這樣方法加載上去的線程 只會(huì)是兩個(gè) 為啥不是三個(gè)或者更多呢?
要點(diǎn):
- 在解決多線程與任務(wù)管理問題時(shí),派發(fā)隊(duì)列并非唯一方案。
- 操作隊(duì)列提供了一套高層的Objective-C API,能實(shí)現(xiàn)純GCD所就被的絕大部分功能,而且還能完成一些更為復(fù)雜的操作,那些操作若改用GCD來實(shí)現(xiàn),則需另外編寫代碼。
44.通過Disptch Group機(jī)制,根據(jù)系統(tǒng)資源狀態(tài)來執(zhí)行任務(wù)
這里面dispatch group主要的還是并行的隊(duì)列,因?yàn)榇械木蜎]有意義了。
創(chuàng)建通過 dispatch_group_dispatch_group_create();
把任務(wù)加入到組里面:
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
dispatch_group_wait (dispatch_group_t group, dispatch_time_t time);
這個(gè)方法同在group結(jié)束的時(shí)候。算是一種堵塞吧
dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue ,dispatch_block_t block);
這個(gè)方法算是一種結(jié)束調(diào)度組之后調(diào)用的block。
要點(diǎn):
- 一系列任務(wù)可歸入一個(gè)dispatch group之中。開發(fā)者可以在這組任務(wù)完畢時(shí)獲得通知。
- 通過dispatch group,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)。此時(shí)GCD會(huì)根據(jù)系統(tǒng)資源狀況來調(diào)度這些并發(fā)執(zhí)行的任務(wù)。開發(fā)者若自己來實(shí)現(xiàn)此功能,則需編寫大量大媽。
45.使用dispatch_once來執(zhí)行只需運(yùn)行一次的線程安全代碼
單例模式我們之前使用的方法是:
- (id)sharedInstance{
static EOCClass * shared = nil;
@synchrnized(self){
if(!sharedInstance){
sharedInstance = [[self alloc] init];
}
}
return shared;
}
使用@synchrnized同步鎖的原因是因?yàn)榫€程安全問題。
關(guān)于線程安全問題;
基本就是同一個(gè)資源被不同線程同時(shí)設(shè)置的時(shí)候產(chǎn)生的沖突。
而后來我們經(jīng)常使用的是dispatch_once方法進(jìn)行單例方法編寫:
- (id)iSuSharedInstance{
static EOCClass * shared = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken ,^{
shared = [[self alloc] init];
})
return shared;
}
事實(shí)證明,GCD方法的運(yùn)行速度要比同步鎖塊的運(yùn)行速度高了兩倍。
要點(diǎn):
- 經(jīng)常需要編寫“只需執(zhí)行一次的線程安全代碼(thread-safe single-code execution)”。通過GCD所提供的dispathc_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
析構(gòu)函數(shù):在對象銷毀的時(shí)候調(diào)用的函數(shù)就是析構(gòu)函數(shù)。
我們看下下面的這個(gè)兩個(gè)函數(shù)
- (NSString *)someString{
__block NSString * localSomeString;
dispatch_sync(_syncQueue,^{
localSomeString = _ someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
獲取方法的時(shí)候可能會(huì)死鎖(EXC_BAD_INSTRUCTION)
死鎖是啥子意思呢:
我看哈 就是說單個(gè)一個(gè)人 要做一個(gè)任務(wù) ,然后這個(gè)任務(wù)的任務(wù)是巴拉拉巴拉的任務(wù),所以這個(gè)人想要繼續(xù)做的話就要先把這個(gè)巴拉巴拉的任務(wù)做完,然而這個(gè)巴拉巴拉德任務(wù)又是一個(gè)加到了同步隊(duì)列里面的任務(wù)。因?yàn)檫@個(gè)任務(wù)列表不是一個(gè)棧類型的(LIFO)而是一個(gè)先進(jìn)去先出來的(FIFO)的隊(duì)列所以,那么這個(gè)人想要完成所有任務(wù)就必須要完成外表的隊(duì)列任務(wù),然后再完成巴拉巴拉的內(nèi)部隊(duì)列任務(wù)。所以任務(wù)停在第一個(gè)外表任務(wù),而內(nèi)部巴拉巴拉任務(wù)也需要執(zhí)行,因?yàn)檫@個(gè)人已經(jīng)被第一個(gè)任務(wù)給拖住了,所以不可能跑去干第二個(gè)事情,第二個(gè)任務(wù)完不成導(dǎo)致第一個(gè)任務(wù)也返回完成不了。嗨呀,好氣啊,我寫了些啥。
死鎖存在時(shí)機(jī)就是get和set方法一同被調(diào)用。
這里面就帶出了為什么不用dispatch_get_current_queue。
解決方法中含有一個(gè)判斷是個(gè)否是同步隊(duì)列,如果是就直接block() 實(shí)現(xiàn),如果不是就正常的加入隊(duì)列運(yùn)行。
- (NSString *)someString{
_ _block NSString * localSomeString ;
dispatch_block_t accessorBlock = ^{
localSomeString = _someString;
};
if (dispathc_get_current_queue() == _syncQueue){
accessorBlock();
}else {
dispatch_sync(_syncQueue,accessorBlock);
}
}
這個(gè)例子這么做當(dāng)然沒有問題,但是如果是嵌套的隊(duì)列
dispatch_queue_t queueA =dispatch_queue_create("com.effective.queueA", NULL);
dispatch_queue_t queueB =dispatch_queue_create("com.effective.queueB", NULL);
dispatch_sync(queueA, ^{
NSLog(@“第一個(gè)A");
dispatch_sync(queueB,^{
NSLog(@“第二個(gè)B");
dispatch_sync(queueA, ^{
NSLog(@"第三個(gè)C");
});
});
});
這個(gè)時(shí)候就算用getCurrent方法獲取queue的隊(duì)列獲取的也是queueB,同樣會(huì)產(chǎn)生死鎖。
要解決這個(gè)問題,最好的辦法就是通過GCD提供的功能來設(shè)定“隊(duì)列特有數(shù)據(jù)”(queue-specific data)
dispatch_queue_t queueC = dispatch_queue_create("com.effectiveobjectivec.queueC", NULL);
dispatch_queue_t queueD =dispatch_queue_create("com.effectiveobjectivec.queueD", NULL);
//將D的運(yùn)行優(yōu)先級賦予給C
dispatch_set_target_queue(queueD, queueC);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR("queueC");
dispatch_queue_set_specific(queueC, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueD, ^{
dispatch_block_t block = ^{
NSLog(@"No deadlock!");
CFStringRef retrievalue = dispatch_get_specific(&kQueueSpecific);
if (retrievalue) {
block();
}else{
dispatch_sync(queueC, block);
}
};
});
那么這邊確認(rèn)一下。dispatch_set_target_queue(A,B)的意思是講A的優(yōu)先級變成和B同級別,那么就會(huì)變成直線向下運(yùn)行。
要點(diǎn):
- dispatch_get_current_queue函數(shù)的行為常常與開發(fā)者所預(yù)期的不同。此函數(shù)已經(jīng)廢棄,只應(yīng)該在調(diào)試的時(shí)候運(yùn)用。
- 由于派發(fā)隊(duì)列是按層級來組織的,所以無法單用某個(gè)隊(duì)列對象來描述“當(dāng)前隊(duì)列”這一概念。
- dispatch_get_current_queue函數(shù)用于解決由不可重入的代碼所引起的死鎖。然而能用次函數(shù)解決的問題,通常也能改變“隊(duì)列特定數(shù)據(jù)”來解決。
第七章 系統(tǒng)框架
47.熟悉系統(tǒng)框架
框架的意義:將一系列代碼封裝為動(dòng)態(tài)庫(dynamic library),并在其中放入描述其接口的頭文件,這樣做出來的東西就叫框架。iOS憑條構(gòu)建的三方框架使用的是靜態(tài)庫(static library),這是因?yàn)閕OS應(yīng)用程序不允許在其中包含動(dòng)態(tài)庫。這些東西嚴(yán)格講不是真正的框架,不過,所有iOS平臺的系統(tǒng)框架仍然使用動(dòng)態(tài)庫。
在iOS上開發(fā)“帶圖形界面的應(yīng)用程序”時(shí),會(huì)用到名為Cocoa的框架,也就是Cocoa Touch,其實(shí)Cocoa本身并不是框架,但是里面集成了一批創(chuàng)建應(yīng)用程序時(shí)經(jīng)常會(huì)用到的框架。
開發(fā)者會(huì)碰到的主要框架就是Foundation 像NSObject,NSArray,NSDictionary等類都在其中。
還有一個(gè)Foundation相伴的框架,叫做CoreFoundation。從技術(shù)上講,CoreFoundation框架不算是Objective-C框架,但是他編寫了OC應(yīng)用程序所應(yīng)熟悉的重要框架,F(xiàn)oundation框架中的許多功能,都可以在CoreFoundation上找到對應(yīng)的C語言API。CoreFoundation和Foundation框架不僅名字相似,而且還有更加緊密的聯(lián)系,叫做“無縫橋接”(toll-free bridging)。
無縫橋接技術(shù)是用某些相當(dāng)復(fù)雜的代碼實(shí)現(xiàn)出來的。這些代碼可以使運(yùn)行期系統(tǒng)把CoreFoundation框架中的對象視為普通的Objective-C對象。
- CFNetwork:此框架提供了C語言級別的網(wǎng)絡(luò)通信能力。
- CoreAudio:C語言API來才做設(shè)備上的音頻硬件。
- CoreData:可將對象放入數(shù)據(jù)庫,以便持久保存。
- AVFoundation:回放并錄制視頻音頻。
- CoreText:執(zhí)行文字排版以及渲染操作。
用C語言來實(shí)現(xiàn)API的好處是,可以繞過OC的運(yùn)行期系統(tǒng),從而提高執(zhí)行速度。當(dāng)然由于ARC只負(fù)責(zé)Objective-C的對象,所以使用這些API的時(shí)候需要注意內(nèi)存管理問題。如果是UI框架的話,主要就是UIKit。
CoreAnimation是OC寫成的,CoreAnimation本身并不是框架,它是QuartzCore框架的一部分。然而在框架的國度里,CoreAnimation仍算是“一等公民”。
CoreGraphics框架是以C語言寫成的。提供了2D渲染所必備的數(shù)據(jù)結(jié)構(gòu)和函數(shù)。例如CGPoint,CGSize,CGRect等。
要點(diǎn):
- 許多系統(tǒng)框架都可以直接使用,其中最重要的是Foundation和CoreFoundation,這兩個(gè)框架構(gòu)建應(yīng)用程序所需的許多核心功能。
- 很多常見任務(wù)都能用框架來做,例如音頻處理,網(wǎng)絡(luò)通訊,數(shù)據(jù)管理等。
- 請記住:用純C寫成的框架與同OC寫成的一樣重要,若要成為優(yōu)秀的OC開發(fā)者,應(yīng)該掌握C語言的核心概念。
48.多用塊枚舉,少用for循環(huán)
相對于For循環(huán)對應(yīng)的 NSArray,NSDictionary,NSSet,NSEnumerator更加便捷。
Dic : [aDictionary allKeys] 賦值給NSArray;
Set : [aSet allObjects] 賦值給NSArray;
NSEnumerator是個(gè)抽象類,其中提供了兩個(gè)方法- nextObject 和 - allObjects
NSArray:
NSArray * anArray = @[@"iSu",@"Abner",@"iSuAbner"];
NSEnumerator * enumerator = [anArray objectEnumerator];
id iSuobject;
while ((iSuobject = [enumerator nextObject]) != nil) {
NSLog(@"NSEnumerator保存的數(shù)據(jù)位%@",iSuobject);
}
NSDictionary:
NSDictionary * aDictionary = @{@"iSu":@"1",@"Abner":@"2",@"KoreaHappend":@"3"};
NSEnumerator * DicEn = [aDictionary keyEnumerator];
id key;
while ((key = [DicEn nextObject])!= nil) {
id value = aDictionary[key];
NSLog(@"NSDictionary的%@對應(yīng)的是%@",key,value);
}
NSSet:
NSSet * aSet = [NSSet setWithObjects:@"iSu",@"Abner",@"iSuAbner", nil];
NSEnumerator * SetEn = [aSet objectEnumerator];
id Setobject;
while ((Setobject = [SetEn nextObject]) != nil) {
NSLog(@"NSSet里面出來的東西:%@",Setobject);
}
快速遍歷:
也就是forin遍歷:
NSDictionary:
id key;
for (id key in aDictionary) {
id value = aDictionary[key];
//NSLog(@"ForinNSDictionary的%@對應(yīng)的是%@",key,value);
}
NSSet:
id key;
for (id key in aDictionary) {
//NSLog(@“ForinNSSet:%@",key);
}
基于塊的遍歷方式:
(void)enumerateObjectsUsingBlock:(void(^)(id object, NSUInteger idx ,BOOL * stop)) block
block遍歷的優(yōu)勢在于有一個(gè)stop的指針可以選擇性的結(jié)束遍歷。
//塊遍歷
[anArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([anArray[idx] isEqualToString:@"Abner"]) {
*stop = YES;
}else{
NSLog(@"block遍歷:%@",anArray[idx]);
}
}];
要點(diǎn):
- 遍歷collection有四種方式,最基本的辦法是for循環(huán),其次是NSEnumerator遍歷法以及快速遍歷法,最新,最先進(jìn)的方式則是“塊枚舉法”。
- “塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作,無需另行編寫代碼。而采用其他遍歷方法則無法輕易實(shí)現(xiàn)這一點(diǎn)。
- 若提前知道待遍歷的collection含有何種對象,則應(yīng)修改塊簽名,指出對象的具體類型。
49.對自定義內(nèi)存管理語義的collection使用無縫連接
什么時(shí)候能夠用到無縫連接?
那我看了原文基本就是一個(gè)改變某些基類的內(nèi)存管理語義。
比如說我們需要一個(gè)NSDictionary的鍵是一個(gè)不能被copy的對象,我們知道正常情況下key的數(shù)值是被拷貝而不是保存的,而如果這個(gè)key不是一個(gè)遵守NSCopy對象,那么我們就需要他保留下來,而不是copy。
首先說一下OC的NSArray —> CFArrayRef
NSArray * anNSArray = @[@1,@2,@3,@4,@5];
// __bridge本意是:ARC仍然具備這個(gè)OC對象的所有權(quán)。而__bridge_retained則與之相反,意味著ARC交出對象的所有權(quán)。
// NSArray --> CFArrayRef 用__bridge
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"size of array = %li",CFArrayGetCount(aCFArray));
而反向的CFArrayRef --> NSArray 用 __bridge_transfer
這里面提醒一下。如果是從C的不可變數(shù)組或者是字典轉(zhuǎn)化成OC的,那么需要的參數(shù)會(huì)比可變的要多:
// CFArrayCreateMutable(<#CFAllocatorRef allocator#>, <#CFIndex capacity#>, <#const CFArrayCallBacks *callBacks#>)
CFArrayCreate(<#CFAllocatorRef allocator#>, <#const void **values#>, <#CFIndex numValues#>, <#const CFArrayCallBacks *callBacks#>)
我們用到改變內(nèi)存管理語義的話基本都是可變的,因?yàn)槟愣疾豢勺兞四阕兓芾碚Z義有什么語義....
這里面我們用CFMutableDictionaryRef為例子需要四個(gè)參數(shù):
CFDictionaryCreateMutable(<#CFAllocatorRef allocator#>, <#CFIndex capacity#>, <#const CFDictionaryKeyCallBacks *keyCallBacks#>, <#const CFDictionaryValueCallBacks *valueCallBacks#>)
第一個(gè)是內(nèi)存,第二個(gè)是個(gè)數(shù),第三個(gè)是key屬性的對象內(nèi)存管理,第四個(gè)參數(shù)是value屬性的對象內(nèi)存管理。仔細(xì)的demo請看FortyNine。
最后代碼:
CFMutableDictionaryRef aCFDictionary = CFDictionaryCreateMutable(NULL, 0, &keyCallbacks, &valueCallbacks);
NSMutableDictionary * anNSDictionary = (__bridge_transfer NSMutableDictionary *)aCFDictionary;
這樣生成的字典就能滿足健可以是不能copy的對象。
要點(diǎn):
- 通過無縫橋接計(jì)數(shù),可以在Foundation框架中的Objective-C對象與CoreFoundation框架中的C語言結(jié)構(gòu)之間來回轉(zhuǎn)換。
- 在CoreFoundation層面創(chuàng)建collection時(shí),可以指定許多回調(diào)函數(shù),這些函數(shù)表示此collection應(yīng)如何處理其元素。然后,可運(yùn)用無縫橋接技術(shù),將其轉(zhuǎn)換成具備特殊內(nèi)存語義的Objective-C collection。
50.構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
對比NSCache相對于NSDictionary的優(yōu)勢:
- 當(dāng)系統(tǒng)資源將要耗盡的時(shí)候,它可以自動(dòng)刪除緩存,字典的話會(huì)比較麻煩,而且NSCache會(huì)先行刪除“最久未使用的對象”。
NSCache并不會(huì)“拷貝”鍵,而是“保留”他,這樣就可以使得健值可以使不能被copy的對象。 - 開發(fā)者可以操控緩存刪減其內(nèi)容的時(shí)機(jī)。可以調(diào)整的有兩個(gè)數(shù)值,一是緩存中的”對象總數(shù)“,其二是對象的“總開銷”,開發(fā)者在將對象加入緩存的時(shí)候,可以為其指定“開銷值”。當(dāng)對象總數(shù)或開銷超過上限時(shí),緩存就可能會(huì)刪減其中的對象了。想緩存中添加對象時(shí)候,只能在很快計(jì)算出“開銷值”的情況下,才應(yīng)該考慮采用這個(gè)尺度。如果計(jì)算的過程長的就不適合這樣了。具體的例子可以看Fifty.demo。
- 還有一個(gè)類NSPurgeableData,此類是NSMutableData的子類,這個(gè)對象在內(nèi)存能夠根據(jù)需求隨時(shí)丟棄。我們可以通過他遵守的NSDiscardableContent協(xié)議里面的isContentDiscarded方法來查詢相關(guān)內(nèi)存是否已經(jīng)釋放。
- 如果想要訪問某個(gè)NSPurgeableData對象,可以調(diào)用其beginContentAccess方法,告訴它現(xiàn)在不應(yīng)該被拋棄,使用完之后調(diào)用endContentAccess方法來告訴必要的時(shí)候可以丟棄。這就和引用計(jì)數(shù)相似。
要點(diǎn):
- 實(shí)現(xiàn)緩存時(shí)應(yīng)選用NSCache而非NSDictionary對象。因?yàn)镹SCache可以提供優(yōu)雅的自動(dòng)刪除功能,而且是“線程安全的”,此外,它與字典不同,并不會(huì)拷貝健。
- 可以給NSCache對象設(shè)置上限,用以限制緩存中的對象總個(gè)數(shù)及“總成本”,而這些尺度則定義了緩存刪除其中對象對象的時(shí)機(jī),但是絕對不要把這些尺寸當(dāng)成可靠的“硬限制(hard limit)”,它們僅對NSCache其指導(dǎo)作用。
- 將NSPurgeableData與NSCache搭配使用,可實(shí)現(xiàn)自動(dòng)清除數(shù)據(jù)的功能,也就是說當(dāng)NSPurgeableData對象所占內(nèi)存為系統(tǒng)所丟棄時(shí),該對象自身也會(huì)從緩存中移除。
- 如果緩存使用得當(dāng),那么應(yīng)用程序的響應(yīng)速度就能提高。只有那種“重新計(jì)算起來很費(fèi)事”數(shù)據(jù),才值得放入緩存,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤讀取的數(shù)據(jù)。
51.精簡initialize和load的實(shí)現(xiàn)代碼
load方法和initialize方法的區(qū)別:
- load方法只會(huì)調(diào)用一次。雖然initalize也是只調(diào)用一次。。
- load方法必須要所有的load運(yùn)行才能繼續(xù)下去,而init是惰性的。不會(huì)直接啟動(dòng)。
- 如果子類沒有實(shí)現(xiàn)load方法,那么就不會(huì)調(diào)用父類的load方法。
而initialize 就是正常的,如果子類沒有實(shí)現(xiàn)就實(shí)現(xiàn)超類的。如果有,也要實(shí)現(xiàn)超類的。 - 在一個(gè)類的load方法里面放入了其他的類的方法是不安全的,應(yīng)為不清楚那個(gè)類是否已經(jīng)load完畢了。
要點(diǎn):
- 在加載階段,如果類實(shí)現(xiàn)了load方法,那么系統(tǒng)就會(huì)調(diào)用它。分類里也可以定義此方法,類的load方法要比分類中的先調(diào)用。和其他類方法不同,load方法不參與覆寫機(jī)制。
- 首次使用某個(gè)類之前,系統(tǒng)會(huì)給其發(fā)送initialize消息。由于此方法遵從普通的覆寫規(guī)則,所以通常應(yīng)該在判斷當(dāng)前要初始化的是哪個(gè)類。
- load和init方法都應(yīng)該實(shí)現(xiàn)的精簡一點(diǎn),這有助于保持應(yīng)用程序的響應(yīng)能力,也- - 能減少引入“依賴環(huán)”的幾率
- 無法在編譯器設(shè)定的全局常量,可以放在initialize方法里初始化。
隨便看到的一個(gè)句子:
朋友問我暗戀是什么感覺,下意識的回答:好像在商店看到喜歡的玩具,想買,錢不夠,努力存錢,回頭去看的時(shí)候發(fā)現(xiàn)漲價(jià)了,更加拼命的存錢,等我覺得差不多的時(shí)候,再回去發(fā)現(xiàn)已經(jīng)被買走了。希望不會(huì)在垃圾堆看到這玩具,不然我依然會(huì)把它撿起來。
52.別忘了NSTimer會(huì)保留其目標(biāo)對象
計(jì)時(shí)器是一個(gè)很方便很有用的對象,F(xiàn)oundation框架中有個(gè)類叫做NSTimer,開發(fā)者可以指定絕對的日期和時(shí)間,以便到時(shí)執(zhí)行任務(wù)。
由于計(jì)時(shí)器會(huì)保留其目標(biāo)對象,所以反復(fù)執(zhí)行任務(wù)通常會(huì)導(dǎo)致應(yīng)用程序出問題。例如:
- (void)startPolling{
//因?yàn)閠arget的對象是self 所以定時(shí)器是持有self的,而timer有是self的成員變量,所以就相互保留
_pollTimer =[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES];
}
想要打破保留環(huán):
只能改變實(shí)例變量或者令計(jì)時(shí)器無效。
也就說要不就調(diào)用[_pollTimer invalidate]; _pollTime = nil; 要不就改變實(shí)例變量。
除非所有的代碼在你的手上,否則我們不確定這個(gè)定時(shí)器失效方法一定會(huì)被調(diào)用,而且即使?jié)M足沒這種條件,這種通過調(diào)用某個(gè)方法來避免內(nèi)存泄露的方法,不是一個(gè)好主意。那我們就選擇通過改變實(shí)例變量的方法。
創(chuàng)造一個(gè)NSTimer的分類,然后新加入一個(gè)類方法
- (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
這段代碼將計(jì)時(shí)器所執(zhí)行的任務(wù)封裝成了“塊”,在調(diào)用計(jì)時(shí)器函數(shù)的時(shí)候,把它作為userInfo參數(shù)傳進(jìn)去。這個(gè)參數(shù)可以用來存放“不透明值(也就是全能數(shù)值id)”,只要計(jì)時(shí)器還有效,就會(huì)一直保留它。傳入?yún)?shù)時(shí)要通過copy方法將block拷貝到“堆”上,否則等到稍后要執(zhí)行它的時(shí)候,該塊可能已經(jīng)無效了。計(jì)時(shí)器現(xiàn)在的target是NSTimer類對象,是個(gè)單例,因此計(jì)時(shí)器是否保留他都無所謂。
然后block還是有個(gè)強(qiáng)引用的循環(huán),這個(gè)簡單就_ _weak就行了。
要點(diǎn):
- NSTimer對象會(huì)保留其目標(biāo),直到計(jì)時(shí)器本身失效為止,調(diào)用invalidate方法可令計(jì)數(shù)器失效,另外,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)之后也會(huì)失效。
- 反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器(repeating timer),很容易引入保留環(huán),如果這種計(jì)時(shí)器的目標(biāo)對象又保留了計(jì)時(shí)器本身,那肯定會(huì)導(dǎo)致保留環(huán)。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的,也可能是通過對象圖里的其他對象間接發(fā)生的。
- 可以擴(kuò)充NSTimer的功能,用”塊“來打破保留環(huán)。不過,除非NSTimer將來在公共接口里提供此功能。否則必須創(chuàng)建分類,將相關(guān)代碼加到其中。
結(jié)尾
自己寫的筆記首先是用Pages寫的,寫完之后放到簡書里面以為也就剩下個(gè)排版了,結(jié)果發(fā)現(xiàn)基本上每一個(gè)點(diǎn)的總結(jié)都不讓自己滿意,但是又想早點(diǎn)放上去,總感覺自己被什么追趕著,哈哈,本來寫完筆記的時(shí)候是2W字的,結(jié)果到第二次發(fā)表的時(shí)候發(fā)現(xiàn)就成了2.5W了,需要改進(jìn)的東西還是太多,希望朋友們有什么改進(jìn)的提議都可以告訴我,我會(huì)一直補(bǔ)充這個(gè)筆記,然后抓緊改GitHub上的代碼~