原文鏈接:點擊打開鏈接
自從block出現之後,很多API都開始採用這樣的結構,由此可見,block確實有許多優勢存在,這裏將一些簡單用法總結如下:
一、如何聲明一個block變量
我們通過^符號來聲明block類型,形式如下:
void (^myBlock)();
其中第一個void是返回值,可以是任意類型,中間括號中^後面的是這個block變量的名字,我把它命名爲myBlock,最後一個括號中是參數,如果多參數,可以寫成如下樣式:
int (^myBlock)(int,int);
同樣,你也可以給參數起名字:
int (^myBlock)(int a,int b);
很多時候,我們需要將我們聲明的block類型作爲函數的參數,也有兩種方式:
1、-(void)func:(int (^)(int a,int b))block;
第二種方式是通過typedef定義一種新的類型,這也是大多數情況下采用的方式:
2、typedef int (^myBlock)(int a,int b) ;
-(void)func:(myBlock)block ;
二、如何實現一個block
既然block可以被聲明爲變量,那麼就一定可以實現它,就像其他類型變量的賦值。我自己對block的理解爲它是一斷代碼塊,所以給它賦值賦便是一段代碼段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef int (^myBlock)( int , int ) ; @interface ViewController () { myBlock block1; } @end @implementation ViewController - ( void )viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. block1 =^( int a, int b){ return a+b; }; NSLog(@ "%d" ,block1(1,1)); } |
這裏打印的結果是2,從這裏可以發現block和函數的功能很像。
注意:1、在上面的代碼裏 block1是一個對象,如果直接打印將打印對象地址
2、block(),加上後面的括號纔是執行block語句塊
三、block中訪問對象的微妙關係
1、如果你在一個block塊中僅僅訪問對象,而不是對他進行修改操作,是沒有任何問題的:
1
2
3
4
5
6
7
8
9
10
|
- ( void )viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. int tem=2; block1 = ^( int a, int b){ int count= tem+1; return count; }; NSLog(@ "%d" ,block1(1,1)); } |
而如果我在block塊中直接修改,編譯器會報錯:
1
2
3
4
|
block1 = ^( int a, int b){ tem+=1; return tem+1; }; |
爲什麼會出現這樣的情況,根據猜測,可能是block內部將訪問的變量都備份了一份,如果我們在內部修改,外部的變量並不會被修改,我們可以通過打印變量的地址來證明這一點:
1
2
3
4
5
6
7
8
9
10
|
- ( void )viewDidLoad { [super viewDidLoad]; int tem=2; NSLog(@ "%p" ,&tem); block1 = ^( int a, int b){ NSLog(@ "%p" ,&tem); return tem+1; }; NSLog(@ "%d" ,block1(1,1)); } |
打印結果如下:
可以看出,變量的地址已經改變。
2、__block 做了什麼
爲了可以在block塊中訪問並修改外部變量,我們常會把變量聲明成__block類型,通過上面的原理,可以發現,其實這個關鍵字只做了一件事,如果在block中訪問沒有添加這個關鍵字的變量,會訪問到block自己拷貝的那一份變量,它是在block創建的時候創建的,而訪問加了這個關鍵字的變量,則會訪問這個變量的地址所對應的變量。我們可以通過代碼來證明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. int tem=2; block1 = ^( int a, int b){ return tem+a+b; }; tem=4; NSLog(@ "%d" ,block1(1,1)); block1 = ^( int a, int b){ return tem+a+b; }; __block int tem2=2; tem2=4; NSLog(@ "%d" ,block1(1,1)); } |
結果:
3、一點點擴展
由此,我們可以理解,如果block中操作的對象是指針,那麼直接可以進行修改,這包括OC對象,如果不是,則需要用__block關鍵字修飾。
4、關於引用計數
在block中訪問的對象,會默認retain:
1
2
3
4
5
6
7
|
UIImage * number; number = [[UIImage alloc]init] ; NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); block1 = ^( int a, int b){ NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); }; NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); |
結果如下:
而添加__block的對象不會被retain;
注意:如果我們訪問類的成員變量,或者通過類方法來訪問對象,那麼這些對象不會被retain,而類對象會被return,最常見的時self:
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
|
typedef void (^myBlock)( int , int ) ; @interface ViewController2 () { myBlock block1; __block UIImage * number; } @end @implementation ViewController2 -( void )dealloc{ NSLog(@ "dealloc %@" ,self. class ); NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); } - ( void )viewDidLoad { [super viewDidLoad]; self.view.backgroundColor=[UIColor whiteColor]; number = [[UIImage alloc]init] ; NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); block1 = ^( int a, int b){ NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); }; //block1(1,1); NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; btn.frame=CGRectMake(100, 100, 100, 100); btn.backgroundColor=[UIColor redColor]; [self.view addSubview:btn]; [btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside]; } -( void )click{ [self dismissViewControllerAnimated:YES completion:nil]; } |
打印結果:
可以看出,UIImage對象沒有被retain,而self也將循環引用,造成內存泄露。解決方法如下:
1
2
3
4
5
6
7
|
number = [[UIImage alloc]init] ; NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); UIImage * im = number; block1 = ^( int a, int b){ NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)im)); }; NSLog(@ "%ld" ,CFGetRetainCount((__bridge CFTypeRef)number)); |
打印結果:
注意:根據這個機制,如果我們將block用來傳值,在block不用時,務必要置爲nil,而在實現block的方法裏,務必要釋放;我們通過代碼來解釋:
首先,創建三個ViewController,爲ViewController1,ViewController2,ViewController3;
1、在ViewController1中創建一個按鈕,跳轉ViewController2
2、在ViewController2中:
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
34
|
#import "ViewController2.h" #import "ViewController3.h" @interface ViewController2 () { UIButton * im; } @end @implementation ViewController3 -( void )dealloc{ NSLog(@ "dealloc %@" ,self. class ); } - ( void )viewDidLoad { [super viewDidLoad]; UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; btn.frame=CGRectMake(300, 300, 100, 100); btn.backgroundColor=[UIColor redColor]; [btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:btn]; im = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 100, 100)]; im.backgroundColor=[UIColor blackColor]; [im addTarget:self action:@selector(rele) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:im]; } -( void )rele{ [self dismissViewControllerAnimated:YES completion:nil]; } -( void )click{ ViewController3 * con = [[ViewController3 alloc]init]; [con setBlock:^{ im.backgroundColor=[UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1]; }]; [self presentViewController:con animated:YES completion:nil]; } |
3、在ViewController3中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#import "ViewController3.h" void (^myBlock)(); @implementation ViewController3 -( void )setBlock:( void (^)())block{ myBlock = [block copy]; } -( void )dealloc{ NSLog(@ "dealloc %@" ,self. class ); } - ( void )viewDidLoad { [super viewDidLoad]; self.view.backgroundColor=[UIColor whiteColor]; myBlock(); UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; btn.frame=CGRectMake(100, 100, 100, 100); btn.backgroundColor=[UIColor redColor]; [self.view addSubview:btn]; [btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside]; } -( void )click{ [self dismissViewControllerAnimated:YES completion:nil]; } |
通過打印信息,我們會發現,ViewController2不被釋放,原因是其成員變量im被block中retain沒有釋放,我們這樣做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@interface ViewController2 () { UIButton * im; ViewController3 * tem; } -( void )rele{ [tem setBlock:nil]; [self dismissViewControllerAnimated:YES completion:nil]; } -( void )click{ ViewController3 * con = [[ViewController2 alloc]init]; tem=con; [con setBlock:^{ im.backgroundColor=[UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1]; }]; [self presentViewController:con animated:YES completion:nil]; } |
這樣就解決了內存問題。
四、關於block的作用域
應避免將花括號中的block用於外面,如果需要,你可以將這個block聲明爲全局的。