IOS 中block結構的簡單用法

原文鏈接:點擊打開鏈接

自從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聲明爲全局的。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章