iOS开发 - OC - block的详解 - 基础篇
?
深入理解oc中的block
蘋果在Mac OS X10.6 和iOS 4之后引入了block語法。這一舉動對于許多OC使用者的編碼風格改變很大。就我本人而言,感覺block用起來還是很爽的,但一直以來,都是知其然,而不知所以然。這篇文章一共有兩篇,其中基礎篇講解了block的基本的使用和創建,以及一些注意事項。在深入篇中,我將會對block的一些原理陳述出來,探討block的內部。
基礎篇
1.block是什么
?首先,對于Block,我在蘋果的文檔中找到以下描述:
Block objects?are a C-level language construct that you can incorporate into your C and Objective-C code. A block object is essentially an anonymous function and the data that goes with that function, something which in other languages is sometimes called a?closure?or?lambda. Blocks are particularly useful as callbacks or in places where you need a way of easily combining both the code to be executed and the associated data.?
?block對象是一個C語言結構體,可以并入到C和Objective-C代碼中。Block對象本質上是一個匿名函數,以及與該函數一起使用的數據,其他語言中有時稱為閉包或lambda。 Block特別適用于回調,或者是在你為了讓代碼看起來具有更清晰的邏輯進行代碼的組合時使用。
上面的解釋,告訴我們: 首先,block是一個OC中的對象;并且,這個對象時一個C語言的結構體,它可以使用在C語言和OC中;同時,block本質上是一個匿名函數和其包含的數據集中。這應該就是block的定義了。之前經常在一些博客上看到,將block描述成一個結構體,或者是一個匿名函數,抑或是直接說成一個對象。這些描述都有道理,但是并不全面。(在第二部分,我會詳細說明為什么)
2.為什么要使用blobk
蘋果的文檔中還描述:
In iOS, blocks are commonly used in the following scenarios:
-
-
As a replacement for delegates and delegate methods
-
As a replacement for callback functions
-
To implement completion handlers for one-time operations
-
To facilitate performing a task on all the items in a collection
-
Together with dispatch queues, to perform asynchronous tasks
-
在iOS中,在以下情況下通常使用block:
-
- 代替代理和委托方法
- 作為回調函數的替代
- 一次性編寫多次處理的任務
- 方便對集合中的所有項執行任務
- 與GCD隊列一起執行異步任務
這里告訴我們在以上情況下,我們都能夠使用block。我感受最深刻的是使用block進行回調。很多情況下,我們可能只需要對某個事件進行一個單一的回調,也許僅僅就一次,如果我使用代理的話,我需要創建類、編寫協議,僅僅對于一個小地方的回調成本很高,那么block登場就恰到好處了。 除此之外,block的特性可以讓代理集中在某處,我們只需要在一個地方就可以完成回調之前和回調時的代碼,相比,使用回調函數和代理都沒有這個優勢!另外,我們可以想到,OC中封裝好了一些集合的方法,比如,數組的排序,仔細會發現,這里就使用block進行回到操作的。
3.block怎么使用
首先我們看看怎么創建一個block吧。
?
//這樣創建 相當平常的block 有參數,有返回值//申明一個block 名字叫oneFrom ,右邊的float類型是參數類型,左邊的float是返回值的類型。 當這兩者中的任意一個為空的時候都可使用void代替float (^oneFrom)(float);oneFrom = ^(float aFloat) {float result = aFloat - 1.0;NSLog(@"調用時");return result;};如果我們需要創建一個全局block(進階篇將會細說,全局block和其他的block的區別),應該像這樣 :
#import <stdio.h>int GlobalInt = 0; int (^getGlobalInt)(void) = ^{ return GlobalInt; }; //這種方式將申明和實現一起寫當我們調用的時候,我們應當這樣做: ? ??
//這樣創建 相當平常的block 有參數,有返回值//申明一個block 名字叫oneFrom ,右邊的float類型是參數類型,左邊的float是返回值的類型。 當這兩者中的任意一個為空的時候都可使用void代替float (^oneFrom)(float);oneFrom = ^(float aFloat) {float result = aFloat - 1.0;NSLog(@"調用時");return result;};//調用float par = 5.0;NSLog(@"調用之前源對象:%f",par);float result = oneFrom(par);NSLog(@"調用的處理結果:%f",result);NSLog(@"調用之后源對象:%f",par);運行結果:
? ? ? 為了更加清楚的體現block的回調效果,我特意進行了打印。可以發現,盡管block的代碼早就聲明了,但是并沒有立刻調用,而是在block調用的時候才被執行。 相信通過這里,對于block的回調應該有一定的理解了。
? ? ? 也許,這樣用起來,不覺得太簡單了嘛?根本就沒什么卵用啊! 別急,再看看另外兩種block的使用。之前說了,block時OC中的一個對象,既然是對象,我們就可以把它當作一個類的屬性咯,應該是也可以很其他屬性一樣,被當作一個方法的參數吧。沒錯,是這樣的,相信block之所以被大家認可也就在于這里吧。 ?看看這個例子。
?
假設 我有一個這樣的類:包含一個block屬性,testBlock。包含一個調用自己的block 屬性的方法- blockDo.
我在另一個地方實現這樣的代碼
#import "ViewController.h"@interface Computer ()@property (nonatomic,copy) NSString* (^testBlock)(NSString*) ; //將一個block作為 computer 的屬性- (void)blockDo;@end@implementation Computer- (void)blockDo{NSString *testStr = @"testData_Old";if(self.testBlock){NSLog(@"%@",self.testBlock(testStr)); //調用并打印 } }@end
在另一個地方寫下這些代碼:
Computer* computer = [[Computer alloc]init];computer.testBlock = ^(NSString *parStr){NSLog(@"%@",parStr);parStr = @"testData_New";return parStr;}; [computer blockDo]; //執行block運行結果:
?
? ? ?仔細看看,這里的調用就比之前復雜了。 因為我給它添加了一個方法,并且將block自身的調用交給了Computer,我只是實現了block而已,最后啟動調用他的方法。 是的,我在另一個地方對Computer類模擬了一個方法,這個方法沒有在Computer類中實現,我甚至可以在任何地方去實現它,而最后我又可以在其他的地方調用,但是它確實具備了一些功能。 這就是block的神奇的地方。 或許這樣你還是覺得它不過如此。
再來看一個:
#import "ViewController.h"@interface Computer ()- (void)doSomethingFeedBack:(NSString*(^)(NSString*))handle;@end@implementation Computer- (void)doSomethingFeedBack:(NSString*(^)(NSString*))handle{NSString *handleStr = @"Old";sleep(3.0);NSLog(@"%@",handle(handleStr)); } @end其他地方調用:
[computer doSomethingFeedBack:^NSString *(NSString *parStr) {NSLog(@"%@",parStr);NSString *returnStr = [[NSString alloc]initWithFormat:@"add %@",parStr];return returnStr;}];打印結果:
這里將block作為一個參數放在?doSomethingFeedBack 函數面。體現了block 的對象性質,相比之下,代碼量很是簡潔。這種實現回調的方式的邏輯更加清晰,明朗。
上面三個例子,展示block的三種不同的使用方式。它們分別是:
-
- 將block定義成變量
- 將block定義成屬性
- 將block作為參數
4.block的使用注意點 ? ?__block和__week
? ? ?block使用的過程中,并沒有需要特別注意的地方,只需要注意兩個關鍵詞。__block和__weak修飾詞。
? ? ?我在講解創建第一種block的時候,運行程序打印的結果看到一個現象,棧block(后續會解釋什么是棧block,這里主要區分全局bolck)雖然拿到了外部變量,但是對與變量的修改確實沒有效果的———前后的源數據的打印值沒有發生改變。如果我們需要在block中對外部的變量進行修改,應該在這個變量之前加上__block修飾,原因在下一品文章中詳解。
? ? ??__weak關鍵詞用于解決使用block導致強引用循環的問題。block在使用的過程中,當block屬于某個對象,如果在block函數中,又包含了這個對象,或者包含其屬性,都會因為block持有對象,對象又持有block導致對象得不到釋放的情況。 這是只需要在申請一個用 weak修飾的對象替代源對象,將引用循環打斷,保證正常釋放,具體的內部原因在下篇中將會詳細介紹。
5.總結:
? ? ?通過上面演示block的用法發現,block每次的回調是通過它的匿名函數來進行的,也就是每一次最多執行一個回調,在需要進行大批量的回調的時候,就需要寫很多不同的block回調,這樣的方式顯然是不合適的,相比之下,這是時候使用協議和代理的方式就自然多了。除此之外,block還比較適合用在線程之間的切換回調。GCD就是采用了多線程結合block來做的。在多數情況下,應當充分考慮block的可以攜帶環境的優點使代碼的邏輯更加的清晰。
基礎篇簡單的介紹下block的使用。并沒有深入研究其內部細節,在接下來的深入篇中,我將介紹:
- 為什么說block是一個結構體,也是一個對象,同時還是攜帶數據的匿名函數
- 全局block ,棧block以及堆區block的區別和他們之間的聯系,探究block的內存管理
- 為什么使用__block 就可以使得block可以修改外部變量
- 引起強引用循環的原因是什么,我們解決它的方法和原理又是什么
2017-04-01 ??18:54:21
?
?
轉載于:https://www.cnblogs.com/FBiOSBlog/p/6667371.html
總結
以上是生活随笔為你收集整理的iOS开发 - OC - block的详解 - 基础篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: List的子类
- 下一篇: ROS学习之包的概念