版權聲明:原創做品,謝絕轉載!不然將追究法律責任。html
一個Objective-c類定義了一個對象結合數據相關的行爲。有時候,這使得他有意義的表達單個任務或者單元的行爲。而不是集合的方法。ios
blocks是語言的特性,咱們能夠在C C++ 和Objective-c看到,這容許你建立不一樣的代碼片斷,這代碼片斷能夠經過在方法或者函數裏調用若是他們有值。blocks是Objective-c的對象,意味着他們能夠被添加到集合像數組和字典裏。他們也有能力撲捉封閉範圍值。web
這章咱們闡述怎麼聲明和引用blocks的語法,和展現怎麼用塊語法來簡化咱們一般的任務例如集合的枚舉。更多信息參考: Blocks Programming Topics.數組
blocks的語法:安全
用(^)定義blocks字面語法像這樣:服務器
^{併發
NSLog(@"This is a block");app
}異步
就像函數和方法定義那樣,{}表示函數的開頭和結尾。這個例子中block沒有返回值也沒有參數。async
一樣的你也能夠用函數指針來引用一個C函數,你能夠聲明一個變量來跟蹤block像這樣:
void (^simpleBlock)(void);
若是你不習慣處理C函數指針,語法可能彷佛有點不尋常,這個例子叫作simpleBlock聲明瞭一個變量來引用一個塊不帶任何參數不返回值。這意味着塊能夠指定給變量像這樣:
simpleBlock = ^{
NSLog(@"This is a block");
};
這就像其餘任何變量的賦值,所以語句必須以分號結束。你能夠把變量聲明和賦值放在一塊兒:
void (^simpleBlock)(void) = ^{
NSLog(@"This is a block");
};
一旦你已經聲明和賦值一個block變量,你能夠調用block:
simpleBlock();
注意:注意你聲明的塊變量沒有賦值爲nil你的應用會崩潰。
塊帶着參數和返回值:
塊也能夠帶參數和返回值就像方法和函數同樣。
舉個例子把兩個值相乘的結果返回給一個塊變量:
double (^multiplyTwoValues)(double, double);
相應的字面塊語法像這樣:
^ (double firstValue, double secondValue) {
return firstValue * secondValue;
}
當塊調用的時候這個firstValue 和secondValue引用的值。就像任何函數定義同樣。這個例子中這個返回值類型是塊語句返回值類型推斷的。
你也能夠顯示的聲明返回的類型在^和()以前像這樣:
^ double (double firstValue, double secondValue) {
return firstValue * secondValue;
}
一旦你聲明和定義了block,你就能夠調用他就像調用函數同樣:
double (^multiplyTwoValues)(double, double) =
^(double firstValue, double secondValue) {
return firstValue * secondValue;
};
double result = multiplyTwoValues(2,4);
NSLog(@"The result is %f", result);
塊能夠從封閉範圍內撲捉值:
一個block也能夠有能力撲捉值從封閉的範圍內,就像包含執行代碼同樣。若是你定義了一個字面block在方法裏面例如,在方法範圍內他能夠撲捉任何值。就像這樣:
- (void)testMethod {
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
testBlock();
}
在這個例子中,anInteger是定義在block的外面,可是當block定義的時候值被撲捉。
一旦值被撲捉,除非你特別說明。這意味着在你定義block和調用他之間若是你改變外部變量值。就像這樣:
int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
這個值被block撲捉沒有收到影響。這意味着打印的值是
Integer is: 42
這意味着block不能改變原始值即便被撲捉的(他被撲捉的是不可修改的變量)
用_block共享存儲
若是你須要改變block撲捉的值,你能夠用_block存儲類型的修飾符在變量聲明以前。這意味着變量存活在存儲共享的詞法做用域之間的原始變量和任何塊中聲明的範圍。你能夠重寫以前的代碼像這樣:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
};
anInteger = 84;
testBlock();
由於anInteger被定義爲一個__block變量,他存儲被共享在block聲明。這意味着輸出值會這樣:
Integer is: 84
這也意味着block能夠修改原始值像這樣:
__block int anInteger = 42;
void (^testBlock)(void) = ^{
NSLog(@"Integer is: %i", anInteger);
anInteger = 100;
};
testBlock();
NSLog(@"Value of original variable is now: %i", anInteger);
這時輸出窗口會這樣:
Integer is: 42
Value of original variable is now: 100
你能夠把blocks做爲方法或者函數的參數傳遞:
以前在這章的每一個例子定義過block後立刻被調用。事實上,這是經常使用的在其餘地方調用塊函數或者方法。你能夠在後臺用GCD調用block,例如,或者定義一個block表明一個任務被調用屢次,例如當枚舉集合。併發性或者枚舉性在後面章節介紹。
blocks能夠被用來回調,定義代碼被執行在任務完成時候。例如你的應用可能響應用戶建立對象的動做來執行一個完成的任務。例如請求信息從服務器。由於這個任務可能用很長時間,當任務發生的時候你可讓一個指示器來表示正在請求數據,一旦任務完成的時候你隱藏指示器。
你也可能完成這個任務用代理:你須要建立一個合適的協議,實現必須的方法,設置你的對象做爲任務的委託,而後你等着代理方法被調用一旦你的對象完成了方法。
blocks可使這變的更簡單,由於你能夠定義回調行爲當你發起任務時候像這樣:
- (IBAction)fetchRemoteInformation:(id)sender {
[self showProgressIndicator];
XYZWebTask *task = ...
[task beginTaskWithCallbackBlock:^{
[self hideProgressIndicator];
}];
}
這個例子調用一個方法來顯示指示器,而後建立任務而且告訴他執行。這個回調block指定代碼被執行一旦這個任務完成的時候;這個例子,他只調用一個方法來隱藏指示器。注意這個回調block爲了可以調用隱藏指示器方法當調用時候給self捕獲值。重要的是要照顧好self當他被捕獲的時候,由於可能形成強引用就像在以後描述的這樣:「Avoid Strong Reference Cycles when Capturing self.」
根據代碼的可讀性,這個block能夠很容易的看到這個任務完成以前和以後將要發生什麼,避免須要追蹤代理方法來發現將要發生什麼、
在這個例子中定義這個beginTaskWithCallbackBlock:方法像這樣:
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock;
這個(void (^)(void))指示這個方法參數block沒有返回值和參數。這個方法實現能夠調用block像以前那樣。
- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}
有一些方法參數但願一個block有一個或者多個參數被指定:
- (void)doSomethingWithBlock:(void (^)(double, double))block {
...
block(21.0, 2.0);
}
一個block應該一直做爲方法的最後一個參數
最好的練習是用一個block參數在一個方法裏面。若是方法也須要不是block的參數,那麼這個block參數應該放在方法的最後一個參數:
- (void)beginTaskWithName:(NSString *)name completion:(void(^)(void))callback;
當指定塊爲內聯時候更容易被閱讀像這樣:
[self beginTaskWithName:@"MyTask" completion:^{
NSLog(@"The task is complete");
}];
用typedef指令來簡化block語法定義:
若是你須要定義不止一塊具備相同簽名的block,你可能喜歡定義你本身的類型簽名,例如你給一個沒有參數和返回值的block定義一個類型像這樣:
typedef void (^XYZSimpleBlock)(void);
你能夠用你的自定義類型做爲方法參數或者建立一個block變量:
XYZSimpleBlock anotherBlock = ^{
...
};
- (void)beginFetchWithCallbackBlock:(XYZSimpleBlock)callbackBlock {
...
callbackBlock();
}
自定義類型很是有用當你處理blocks然而這些blocks有blocks的參數或者返回值。考慮下面例子:
void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
...
return ^{
...
};
};
這個complexBlock 變量引用一個blocks做爲參數還返回一個block做爲返回值.
用自定義類型來重寫代碼讓你的代碼變的可讀性強:
XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
...
return ^{
...
};
};
咱們能夠給blocks聲明成屬性來跟蹤他:
定義屬性的語法來跟蹤block就像定義block變量:
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
注意:咱們在定義blocks屬性時候他的關鍵字應該是copy。由於一個block須要被拷貝來跟蹤他捕獲原始範圍之外的狀態。當用ARC的時候咱們須要擔憂,由於這是自動發生的。可是最好仍是給屬性指定關鍵字來代表他的行爲。更多信息參考Blocks Programming Topics.
一個block屬性被設置或者引用像其餘任何block變量:
self.blockProperty = ^{
...
};
self.blockProperty();
也能夠給block屬性重定義像這樣:
typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
當捕獲self的時候當心強引用循環:
若是你須要捕獲self在block中,例如定義一個block回調,你更應該考慮的是他的內存泄漏。
blocks維持着強引用向任何捕獲的對象,包括self,這意味着很容易引發強引用循環,例如一個對象維持着一個copy屬性給一個捕獲self的block
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a strong reference cycle
};
}
...
@end
若是你這樣定義例子編譯器會警告你,可是一個更復雜的例子可能包括多個對象之間的強引用來建立循環,使它更難診斷。
爲了不這個問題,咱們聲明一個弱引用類型給self,像這樣:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
給self聲明弱引用類型。這個block沒有維持一個強引用給XYZBlockKeeper對象。若是這個對象在這個block以前被銷燬,這個weakSelf指針將要被指爲nil。
blocks能夠簡化枚舉
除了通常的完成處理程序,許多的cocoa或者cocoaTouchAPI用blocks來簡化常見的任務,就像集合枚舉。這個NSArray類,例如提供了3個基本的block方法包括這個:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
這個方法有一個簡單的block參數來遍歷數組的每一個元素:
NSArray *array = ...
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"Object at index %lu is %@", idx, obj);
}];
這個block本身有三個參數。前兩個是當前對象和他在數組的索引。第三個參數是一個指向布爾類型變量的指針能夠中止這個枚舉像這樣:
[array enumerateObjectsUsingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
if (...) {
*stop = YES;
}
}];
咱們也能夠自定義枚舉用enumerateObjectsWithOptions:usingBlock:方法指定這個NSEnumerationReverse參數選項,例如將集合反向遍歷。
這個在block裏的代碼是一個處理器密集的安全併發的,你能夠用NSEnumerationConcurrent 選項。
[array enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
...
}];
這個標識符代表這個枚舉塊可能在多個線程裏面調用,若是這個block代碼是特別的密集處理器將提升潛在的性能。注意這個枚舉順序是未定義的當用這個選項的時候:
這個NSDictionay來提供block的基本方法包括:
NSDictionary *dictionary = ...
[dictionary enumerateKeysAndObjectsUsingBlock:^ (id key, id obj, BOOL *stop) {
NSLog(@"key: %@, value: %@", key, obj);
}];
和傳統的循環變量相比他能夠很方面的變量字典的鍵值。
塊能夠簡化併發任務:
一個block是一個工做單元。組合可執行代碼從周圍範圍捕獲可選狀態。這使異步調用更完美在OSX和IOS用一個有效的併發選項。而不是必定指出怎麼使用低級的機制像線程。你能夠簡單的定義你的任務使用塊而後讓系統執行這些任務做爲處理器資源變的可用。
OSX和IOS提供了不少的併發技術包括兩個任務調度機制: Operation queues and Grand Central Dispatch。這些機制解決的是一個隊列的任務怎麼等待被調用。你能夠添加blocks到你的隊列順序當你須要他們被調用。而且系統爲調用出列當處理器時間和資源可用。
一系列隊列只容許一個任務被執行在同一時間下一個在隊列裏面的任務直到前面一個完成纔會調用。當前的隊列調用他儘量調用的任務不用等待前面的任務完成。
使用塊操做和運行隊列:
一個操做隊列在cocoa或者cocoaTouch相似與任務調度。你能夠建立一個NSOperation實例來封裝一個工做單元以及必要的數據,而後放到一個NSOperationQueue 裏面來執行。
儘管你能夠建立一個自定義的NSOperation子類來實現複雜的任務,你也可能用NSBlockOperation 來建立一個操做用block像這樣:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
你可能執行一個手動的線程可是這些線程常常被添加一個已經存在的隊列或者你本身建立的隊列,等待被執行:線程
// schedule task on main queue:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// schedule task on background queue:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
若是你用一個線程隊列,你須要配置優先級或者線程之間的依賴,就像指定一個線程不能被執行直到其餘的線程完成。你能夠用一個觀察者來觀察線程的狀態。他使你很容易來更新一個進度指示器例如當任務完成了。
更多信息關於線程和線程隊列的參考「Operation Queues」.
在GCD裏面用block來調度隊列:
若是你須要安排任意的代碼快來執行,你能夠直接用GCD控制的dispatch queues。
隊列調度使他更容易執行同步或者異步並執行他們的任務在一個先進先出的順序。
你也能夠建立你本身的隊列調度或者用GCD提供的隊列。你須要安排你的任務來併發執行。例如你能夠獲得一個已經存在的隊列用dispatch_get_global_queue()方法而且指定這個隊列的優先級像這樣:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
分派block到隊列,你能夠用dispatch_async() 或者dispatch_sync()方法。這個dispatch_async()方法馬上返回,無需等待被調用。
dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});
這個dispatch_sync()方法直到block完成了才能調用。你可使用他在一種狀況下,併發塊須要等待另外一個任務在主線程繼續以前完成。
更多信息參考隊列調度和GCD「Dispatch Queues」.