ios4.0系統已開始支持block,在編程過程中,block被Obj-C看成是對象,它封裝了一段代碼,這段代碼可以在任何時候執行。block可以作為函數參數或者函數的返回值,而其本身又可以帶輸入參數或返回值。它和傳統的函數指針很類似,但是有區別:block是inline的,並且它對局部變量是只讀的。
block和函數的相似性:(1)可以保存代碼(2)有返回值(3)有形參(4)調用方式一樣。
1、block的定義
// 聲明和實現寫在一起,就像變量的聲明實現 int a = 10;
int(^aBlock)(int,int) = ^(intnum1,intnum2) {
returnnum1 * num2;
};
// 聲明和實現分開,就像變量先聲明後實現 int a;a = 10;
int(^cBlock)(int,int);
cBlock = ^(intnum1,intnum2)
{
returnnum1 * num2;
};
其中,定義了一個名字為aBlock的blocks對象,並攜帶了相關信息:
1、aBlock 有兩個形式參數,分別為int類型;
2、aBlock 的返回值為int 類型;
3、等式右邊就是blocks的具體實現;
4、^ 帶邊blocks聲明和實現的標示(關鍵字);
當然,你可以定義其他形式的block。e.g:無返回值,無形式參數等;
void(^bBlock)() = ^()
{
inta =10;
printf(num = %d,a);
};
2、blocks 訪問權限
blocks可以訪問局部變量,但是不能修改。
inta =10;
int(^dBlock)(int) = ^(intnum)
{
a++;//not work!
returnnum * a;
};
此處不能修改的原因是在編譯期間確定的,編譯器編譯的時候把a的值復制到block作為一個新變量(假設是a‘ = 10),此時a'和a是沒有關系的。
這個地方就是函數中的值傳遞。如果要修改就要加關鍵字:__block或者static
__blockinta =7;
int(^dBlock)(int) = ^(intnum)
{
a++;// work!
returnnum * a;
};
3、block的調用
block調用就像調用函數一樣。e.g:
1intc = aBlock(10,10);
bBlock();
4、block 應用
假設我們熟悉代理遞值的話,對代理我們可能又愛有恨!我們先建立模型A頁面 push
B頁面,如果把A頁面的值傳遞到B頁面,屬性和單例傳值可以搞定!但是如果Pop過程中把B頁面的值傳遞到A頁面,那就可以用單例或者代理了!說到代理,
我們要先聲明協議,創建代理,很是麻煩。常常我們傳遞一個數值需要在兩個頁面間寫很多代碼,這些代碼改變頁面的整體順序,可讀性也打了折扣。所以,此
時,block是一種優化方案!
5、 block的內存管理
block本身是像對象一樣可以retain,和release。但是,block在創建的時候,它的內存是分配在棧(stack)上,而不是在堆(heap)上。他本身的作於域是屬於創建時候的作用域,一旦在創建時候的作用域外面調用block將導致程序崩潰。比如下面的例子。 我在view did load中創建了一個block:
- (void)viewDidLoad
{
[superviewDidLoad];
int number = 1;
_block = ^(){
NSLog(@number %d, number);
};
}
並且在一個按鈕的事件中調用了這個block:
- (IBAction)testDidClick:(id)sender {
_block();
}
此時我按了按鈕之後就會導致程序崩潰,解決這個問題的方法就是在創建完block的時候需要調用copy的方法。copy會把block從棧上移動到堆上,那麼就可以在其他地方使用這個block了~ 修改代碼如下:
_block = ^(){
NSLog(@number %d, number);
};
_block = [_blockcopy];
同理,特別需要注意的地方就是在把block放到集合類當中去的時候,如果直接把生成的block放入到集合類中,是無法在其他地方使用block,必須要對block進行copy。不過代碼看上去相對奇怪一些:
[array addObject:[[^{
NSLog(@hello!);
} copy] autorelease]];
6、循環引用
對於非ARC下, 為了防止循環引用, 我們使用__block來修飾在Block中使用的對象:
對於ARC下, 為了防止循環引用, 我們使用__weak來修飾在Block中使用的對象。原理就是:ARC中,Block中如果引用了__strong修飾符的自動變量,則相當於Block對該變量的引用計數+1。
這一點其實是在第一點的一個小的衍生。當在block內部使用成員變量的時候,比如
@interface ViewController : UIViewController
{
NSString *_string;
}
@end
在block創建中:
_block = ^(){
NSLog(@string %@, _string);
};
這裡的_string相當於是self->_string;那麼block是會對內部的對象進行一次retain。也就是說,self會被retain一次。當self釋放的時候,需要block釋放後才會對self進行釋放,但是block的釋放又需要等self的dealloc中才會釋放。如此一來變形成了循環引用,導致內存洩露。
修改方案是新建一個__block scope的局部變量,並把self賦值給它,而在block內部則使用這個局部變量來進行取值。因為__block標記的變量是不會被自動retain的。
__block ViewController *controller = self;
_block = ^(){
NSLog(@string %@, controller->_string);
};
博主浏覽了很多博客,總結了一下,block實際上是(底層c++): 指向結構體的指針,底層會創建一個結構體,實現構造方法,來接參數,編譯器會將block的內部代碼生成對應的函數。
使用Block的地方很多,其中傳值只是其中的一小部分,下面介紹Block在兩個界面之間的傳值:
先說一下思想:
首先,創建兩個視圖控制器,在第一個視圖控制器中創建一個UILabel和一個UIButton,其中UILabel是為了顯示第二個視圖控制器傳過來的字符串,UIButton是為了push到第二個界面。
第二個界面的只有一個UITextField,是為了輸入文字,當輸入文字,並且返回第一個界面的時候,當第二個視圖將要消失的時候,就將第二個界面上TextFiled中的文字傳給第一個界面,並且顯示在UILabel上。
其實核心代碼就幾行代碼:
下面是主要代碼:(因為我是用storyBoard創建的工程,所以上面的屬性和相應的方法,是使用系統生成的outlet)
一、在第二個視圖控制器的.h文件中定義聲明Block屬性
typedef void(^ReturnTextBlock)(NSString *showText);
@interface TextFieldViewController : UIViewController
@property(nonatomic, copy) ReturnTextBlock returnTextBlock;
- (void)returnText:(ReturnTextBlock)block;
@end
第一行代碼是為要聲明的Block重新定義了一個名字
ReturnTextBlock
這樣,下面在使用的時候就會很方便。
第三行是定義的一個Block屬性
第四行是一個在第一個界面傳進來一個Block語句塊的函數,不用也可以,不過加上會減少代碼的書寫量
二、實現第二個視圖控制器的方法
- (void)returnText:(ReturnTextBlock)block {
self.returnTextBlock = block;
}
- (void)viewWillDisappear:(BOOL)animated {
if(self.returnTextBlock != nil) {
self.returnTextBlock(self.inputTF.text);
}
}
其中inputTF是視圖中的UITextField。
第一個方法就是定義的那個方法,把傳進來的Block語句塊保存到本類的實例變量returnTextBlock(.h中定義的屬性)中,然後尋找一個時機調用,而這個時機就是上面說到的,當視圖將要消失的時候,需要重寫:
- (void)viewWillDisappear:(BOOL)animated;
方法。
三、在第一個視圖中獲得第二個視圖控制器,並且用第二個視圖控制器來調用定義的屬性
如下方法中書寫:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
TextFieldViewController *tfVC = segue.destinationViewController;
[tfVC returnText:^(NSString *showText) {
self.showLabel.text = showText;
}];
}
假設A有一個任務,是去倉庫取一張A4紙放到會議室,然後在紙上寫一份策劃書。取紙又要經過倉庫管理員,於是A通知倉庫管理員來張紙過來。由於管理員是個老
頭動作很慢,另外A還有其他工作,如果一直等待管理員就太浪費時間了,合理的做法是讓倉庫管理員進行取紙這項工作,A在通知管理員後就繼續自己的工作。A
不知道倉庫管理員什麼時候能完成取紙,也就不知道什麼時候可以在紙上寫策劃書。這時block機制就派上用場了,使用這種機制,可以讓A在通知倉庫管理員
取紙的同時,告訴倉庫管理員將紙放到編號XX會議室,並安排好將要在紙上寫的策劃內容,當管理員把紙拿來後,可能過一會就會有個助理將策劃內容寫到紙上。
我們將這個故事對應到代碼上:
#pragmamark-第三方登錄
-(void)btnLoginWeiboClicked:(id)sender{
[_waitCirclestartAnimating];
[[HSLoginClasscreateInstance]loginWithPlatformType:ShareTypeSinaWeibowithBlock:^(BOOLsuccess,idmessage){
if(success){
//跳出登錄頁面
[selfdismissViewControllerAnimated:YEScompletion:^{}];
[_waitCirclestopAnimating];
NSLog(@"新浪微博%@",message);
}else{
}
}];
//teststatistics
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
NSDate*date=[NSDatedate];
NSTimeIntervalnowTime=[datetimeIntervalSince1970];
NSString*netStatus=(NSString*)[[NSUserDefaultsstandardUserDefaults]objectForKey:NETSTATUS];
[[HSStatisticsModulestatisticsModule]makeSession:nowTime:OPER_IN:@"noid"];
[[HSStatisticsModulestatisticsModule]upLoadData:netStatus:[NSStringstringWithFormat:@"%f",nowTime]];
});
}
請看這段代碼,整個方法是A要做的工作,startAnimating/loginWithPlatformType/dispatch_async分別是A要做的三個任務,由於loginWithPlatformType需要一段時間才能完成,並且loginWithPlatformType完成後要根據結果做相應處理,所以我們對loginWithPlatformType進行異步處理。block代碼段是loginWithPlatformType得到結果後要做的操作H,這裡的block寫法就表示,我們先將操作H安排好,然後繼續其他工作,當loginWithPlatformType執行ok後自會有人(可能是那個助理)去執行操作H。
1 什麼是block
對於閉包(block),
有很多定義,其中閉包就是能夠讀取其它函數內部變量的函數,這個定義即接近本質又較好理解。對於剛接觸Block的同學,會覺得有些繞,因為我們習慣寫這
樣的程序main(){ funA();} funA(){funB();} funB(){.....};
就是函數main調用函數A,函數A調用函數B...
函數們依次順序執行,但現實中不全是這樣的,例如項目經理M,手下有3個程序員A、B、C,當他給程序員A安排實現功能F1時,他並不等著A完成之後,再
去安排B去實現F2,而是安排給A功能F1,B功能F2,C功能F3,然後可能去寫技術文檔,而當A遇到問題時,他會來找項目經理M,當B做完時,會通知
M,這就是一個異步執行的例子。在這種情形下,Block便可大顯身手,因為在項目經理M,給A安排工作時,同時會告訴A若果遇到困難,如何能找到他報告
問題(例如打他手機號),這就是項目經理M給A的一個回調接口,要回掉的操作,比如接到電話,百度查詢後,返回網頁內容給A,這就是一個Block,在M
交待工作時,已經定義好,並且取得了F1的任務號(局部變量),卻是在當A遇到問題時,才調用執行,跨函數在項目經理M查詢百度,獲得結果後回調該
block。
2 block 實現原理
Objective-C是對C語言的擴展,block的實現是基於指針和函數指針。
從計算語言的發展,最早的goto,高級語言的指針,到面向對象語言的block,從機器的思維,一步步接近人的思維,以方便開發人員更為高效、直接的描述出現實的邏輯(需求)。
3 block的使用
使用typed聲明block
typedef void(^didFinishBlock) (NSObject *ob);
這就聲明了一個didFinishBlock類型的block,
然後便可用
@property (nonatomic,copy) didFinishBlock finishBlock;
聲明一個block對象,注意對象屬性設置為copy,接到block 參數時,便會自動復制一份。
__block是一種特殊類型,
使用該關鍵字聲明的局部變量,可以被block所改變,並且其在原函數中的值會被改變。
4 常見系列面試題
面試時,面試官會先問一些,是否了解block,是否使用過block,這些問題相當於開場白,往往是下面一系列問題的開始,所以一定要如實根據自己的情況回答。
1 使用block和使用delegate完成委托模式有什麼優點?
首先要了解什麼是委托模式,委托模式在iOS中大量應用,其在設計模式中是適配器模式中的對象適配器,Objective-C中使用id類型指向一切對象,使委托模式在iOS中的實現更為方便。了解委托模式的細節:
使用block實現委托模式,其優點是回調的block代碼塊定義在委托對象函數內部,使代碼更為緊湊;
適配對象不再需要實現具體某個protocol,代碼更為簡潔。
2 多線程與block
GCD與Block
使用 dispatch_async系列方法,可以以指定的方式執行block
dispatch_async的完整定義
void dispatch_async(
dispatch_queue_t queue,
dispatch_block_t block);
功能:在指定的隊列裡提交一個異步執行的block,不阻塞當前線程
通過queue來控制block執行的線程。主線程執行前文定義的 finishBlock對象
dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});