Block就像delegate的簡化版

 

iOS SDK 4.0開始,Apple引入了block這一特性,而自從block特性誕生之日起,彷佛它就受到了Apple特殊的照顧和青睞。字面上說,block就 是一個代碼塊,可是它的神奇之處在於在內聯(inline)執行的時候(這和C++很像)還能夠傳遞參數。同時block自己也能夠被做爲參數在方法和函 數間傳遞,這就給予了block無限的可能。objective-c

在平常的coding裏絕大時間裏開發者會是各類block的使用者,可是當你須要構建一些比較基礎的,提供給別人用的類的時候,使用block會 給別人的使用帶來不少便利。固然若是你已經厭煩了一直使用delegate模式來編程的話,偶爾轉轉寫一些block,不只能夠鍛鍊思惟,也能讓你寫的代 碼看起來高端洋氣一些,並且由於代碼跳轉變少,因此可讀性也會增長。算法

先來看一個簡單的block吧:編程

// Defining a block variable BOOL (^isInputEven)(int) = ^(int input) { if (input % 2 == 0) { return YES; } else { return NO; } }; 

以上定義了一個block變量,block自己就是一個程序段,所以有返回值有輸入參數,這裏這個block返回的類型爲BOOL。天賦異秉的OC 用了一樣不走尋常路的"{% raw %} ^{% endraw %}"符號來表示block定義的開始(就像用減號和加號來定義方法同樣),block的名稱緊跟在{% raw %} ^{% endraw %}符號以後,這裏是isInputEven(也即之後使用inline方式調用該block時所須要的名稱)。這段block接受一個int型的參數, 而在等號後面的int input是對這個傳入int參數的說明:在該block內,將使用input這個名字來指代傳入的int參數。一開始看block的定義和寫法時可能會 比較痛苦,可是請謹記它只是把咱們常見的方法實現換了一種寫法而已,請以習慣OC中括號發送消息的速度和決心,儘快習慣block的寫法吧!xcode

調用這個block的方法就很是簡單和直觀了,相似調用c函數的方式便可:多線程

// Call similar to a C function call int x = -101; NSLog(@"%d %@ number", x, isInputEven(x) ? @"is an even" : @"is not an even"); 

不出意外的話輸出爲-101 is not an even number函數

以上的用法沒有什麼特別之處,只不過是相似內聯函數罷了。可是block的神奇之處在於block外的變量能夠無縫地直接在block內部使用,好比這樣:post

float price = 1.99; float (^finalPrice)(int) = ^(int quantity) { // Notice local variable price is // accessible in the block return quantity * price; }; int orderQuantity = 10; NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity)); 

輸出爲Ordering 10 units, final price is: $19.90動畫

至關開心啊,block外的price成功地在block內部也能使用了,這意味着內聯函數可使用處於同一scope裏的局部變量。可是須要注意的是,你不能在block內部改變本地變量的值,好比在{% raw %} ^{% endraw %}{}裏寫price = 0.99這樣的語句的話,你親愛的compiler必定是會叫的。而更須要注意的是price這樣的局部變量的變化是不會體如今block裏的!好比接着上面的代碼,繼續寫:ui

price = .99; NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity)); 

輸出仍是Ordering 10 units, final price is: $19.90,這就比較憂傷了,能夠理解爲在block內的price是readonly的,只在定義block時可以被賦值(補充說明,其實是由於price是value type,block內的price是在申明block時複製了一份到block內,block外面的price不管怎麼變化都和block內的price無關了。若是是reference type的話,外部的變化其實是會影響block內的)。atom

可是若是確實須要傳遞給block變量值的話,能夠考慮下面兩種方法:

一、將局部變量聲明爲__block,表示外部變化將會在block內進行一樣操做,好比:

// Use the __block storage modifier to allow changes to 'price' __block float price = 1.99; float (^finalPrice)(int) = ^(int quantity) { return quantity * price; }; int orderQuantity = 10; price = .99; NSLog(@"With block storage modifier - Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity)); 

此時輸出爲With block storage modifier – Ordering 10 units, final price is: $9.90

二、使用實例變量——這個比較沒什麼好說的,實例內的變量橫行於整個實例內..可謂霸道無敵...=_=

block外的對象和基本數據同樣,也能夠做爲block的參數。而讓人開心的是,block將自動retain傳遞進來的參數,而不需擔憂在 block執行以前局部對象變量已經被釋放的問題。這裏就不深究這個問題了,只要嚴格遵循Apple的thread safe來寫,block的內存管理並不存在問題。(更新,ARC的引入再次簡化了這個問題,徹底不用擔憂內存管理的問題了)

因爲block的靈活的機制,致使iOS SDK 4.0開始,Apple大力提倡在各類地方應用block機制。最典型的當屬UIView的動畫了:在4.0前寫一個UIView的Animation大概是這樣的:

[UIView beginAnimations:@"ToggleSiblings"context:nil]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES]; [UIViewsetAnimationDuration:1.0f]; // Make your changes [UIView commitAnimations]; 

在一個不知名的小角落裏的begin/commit兩行代碼間寫下須要進行的動做,而後靜待發生。而4.0後這樣的方法直接被discouraged了(雖然還沒Deprecated),取而代之的正是block:

[UIView animateWithDuration:5.0f animations:^{ view.opacity = 0.5f; }]; 

簡單明瞭,一切就這麼發生了..

可能有人會以爲block的語法很奇怪,不像是OOP的風格,誠然直接使用的block看起來破壞了OOP的結構,也讓實例的內存管理出現了某些「看上去奇怪」的現象。可是經過typedef的方法,能夠將block進行簡單包裝,讓它的行爲更靠近對象一些:

typedef double (^unary_operation_t)(double op); 

定義了一個接受一個double型做爲變量,類型爲unaryoperationt的block,以後在使用前用相似C的語法聲明一個unaryoperationt類型的"實例",而且定義內容後即可以直接使用這個block了~

unary_operation_t square;  
square = ^(double operand) { return operand * operand; } 

囉嗦一句的仍是內存管理的問題,block始終不是對象,而block的內存管理天然也是和普通對象不同。系統會爲block在堆上分 配內存,而當把block當作對象進行處理時(好比將其壓入一個NSMutableArray),咱們須要獲取它的一份copy(好比[square copy]),而且在Array retain了這個block後將其釋放([square autorelease]是不錯的選擇)。而對於block自己和調用該block的實例,則能夠放心:SDK會將調用block的實例自動 retain,直至block執行完畢後再對實例release,所以不會出現block執行到一半,實例就被dealloc這樣的尷尬的局面。 在ARC的時代,這些都是廢話了。打開ARC,而後瞎用就能夠了。ARC解決了block的最讓開發者頭疼的最大的也是惟一的問題,內存管理。關於block的內存管理方面,有一個很好玩的小quiz,能夠作作玩~傳送門

iOS SDK 4.0之後,隨着block的加入不少特性也隨之添加或者發生了升級。Apple所推薦的block使用範圍包括如下幾個方面:

  • 枚舉——經過block獲取枚舉對象或控制枚舉進程
  • View動畫——簡單明瞭的方式規定動畫
  • 排序——在block內寫排序算法
  • 通知——當某事件發生後執行block內的代碼
  • 錯誤處理——當錯誤發生時執行block代碼
  • 完成處理——當方法執行完畢後執行block代碼
  • GCD多線程——多線程控制,關於這個之後有機會再寫…

 

block使用時應注意的事項:

1.  可訪問在同一範圍內的全局變量包括靜態變量。

2.  能夠訪問傳遞給塊的參數(如同函數參數)。

3.  同一範圍的棧(非static)變量視做const變量。它們的值相似塊表達式。嵌套塊時,從最近的做用域取值。

4.  在同一範圍內聲明的變量,若是有__block修飾符修飾,則值是可變的。在該範圍內包括同一範圍內的其餘塊對該變量的改變,都將影響該做用域。具體見「__block 存儲類型」。

5.  在塊的範圍內(塊體)聲明的本地變量,相似於函數中的本地變量。塊的每次調用都會致使從新拷貝這些變量。這些變量可做爲const或參考(by-reference)變量。

block塊回調的例子

Book.h

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
typedef  void  (^PublishBlock) (NSString *name);
@interface Book : NSObject
 
@property (nonatomic, strong) PublishBlock block;
- ( void ) nslogAll;
- ( void ) printAll:(PublishBlock) block;
@end

Book.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "Book.h"
 
@implementation Book
 
-( void )nslogAll{
     NSString *name = [NSString stringWithFormat:@ "helloworld!!!" ];
     if  (self.block) {
         self.block(name);
     }
    
}
- ( void )printAll:(PublishBlock)block{
     NSString *name = [NSString stringWithFormat:@ "HELLOWORLD!!!" ];
     if  (block) {
         block(name); // 調用block傳值
     }
 
}
@end

函數調用時的類名

ViewController.h

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>
 
 
 
@interface ViewController : UIViewController
 
- (IBAction)huiDiao_Action:(id)sender;
 
@end

ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import "ViewController.h"
#import "Book.h"
 
@interface ViewController ()
 
@end
 
@implementation ViewController
 
- ( void )viewDidLoad {
     [super viewDidLoad];
     // Do any additional setup after loading the view, typically from a nib.
}
 
- ( void )didReceiveMemoryWarning {
     [super didReceiveMemoryWarning];
     // Dispose of any resources that can be recreated.
}
 
 
 
- (IBAction)huiDiao_Action:(id)sender {
     Book *book = [[Book alloc]init];
     book.block = ^(NSString *name){
         NSLog(@ "++++xingming:%@" ,name);
     };
     [book nslogAll];
     [book printAll:^(NSString *name) { // block做爲參數傳遞
         NSLog(@ "+++++XINGMING:%@" ,name);
     }];
}
 
@end

 

1. 做爲屬性而存在的Block

testBlock.m文件裏

@property (copy, nonatomic) void (^aBlock)();  // MRC下,block屬性必須是顯式標註copy策略;  ARC下,其實能夠不顯式標明copy, xcode會自動對block屬性採起copy策略

- (void)viewDidLoad {

    [super viewDidLoad];

    self.aBlock = ^{

    NSLog(@"%@",self);

  };

}

 

  當block在棧內存時,Block對內部的對象只是弱引用

  1.1 copy關鍵字

    block默認是在存儲在棧內存, 通過copy,會將block複製到堆內存

 

2. 在方法內部的Block

- (void)viewDidLoad {

    [super viewDidLoad];

    void (^innerFunctionBlock)() = ^{

        NSLog(@"方法內部的block, 有人管這叫內聯Block");

    };

    innerFunctionBlock();

}

 

3. 其餘

- (void)viewDidLoad { ...2.裏的代碼塊... }

void (^block)() = ^{

  NSLog(@"相似全局變量的Block, 其實和方法差很少");

};

 

總結: 1. 若是沒有對block作copy操做, block就存儲於棧內存

        2. 若是對block作copy操做, block就存儲於堆內存

        3. 若是block存儲於棧空間, 對block內部的所用到的外部對象,是弱引用

        4. 若是block存儲於堆空間, 對block內部的所用到的外部對象,是強引用

        5. 解除循環引用的方法: 5.1 ARC:使用 __weak, 有時不能用__weak,那就用 __unsafe_unretained , 這種狀況極少發生

              5.2 MRC:使用__block

                                        5.3 在調用完block以後,將block = nil

        6.  NSInteger a = 1;  

     void (^testBlock)(void) =  ^{

        NSLog(@"a=%d",a);

     };

     ++a;

     testBlock();

            打印出來確定仍是1, 由於block在編譯階段就肯定了, 編譯的時候block內部的 NSLog(@"a=%d",a)  等價於  NSLog(@"a=%d",1) ,此時a在代碼塊中是value type,此時是不能改變值的;

     想要block內部的a是引用,須要用到__block聲明變量a: __block NSInteger a = 1;  這樣編譯編輯階段,block內部的 NSLog(@"a=%d",a)  等價於  NSLog(@"a=%d",*(&a))

    7.  不要迷信xcode能幫你檢查出全部block的循環引用問題,xcode只能檢查出比較淺顯的,這個尤爲須要注意

 

 

仔細研讀4.0的SDK的話,會發現不少經常使用類中都加入了很多帶block做爲參數的方法,改變固有思惟習慣,適應並儘量利用block給程序上帶來的便捷,無疑是提升效率和使代碼優雅的很好的途徑~

相關文章
相關標籤/搜索