Block
語法定義
^(參數(shù)列){ 表達式 }
從 ^ 開始到 {} 結束 就是塊語法;
//聲明沒有返回值,有一個Int類型參數(shù)的塊對象 b1
void (^b1)(int) = ^(int a) { printf("%d", a); };
//聲明沒有返回值為int類型,沒有參數(shù)的塊對象 b2
int (^b2)(void) = ^(void) { return 10; };
// 調用
b1(3); //print 3
b2();
似一般的類型
Block同int等普通類型一樣,也可以看成一種特別的類型,如可以聲明一個int型的變量,同樣也可以聲明一個Block型的變量,只不過 int 代表的是一個整形的數(shù)字,而Block代表的是一個,能捕獲狀態(tài)的,有很多特性的代碼塊(這里后面詳細介紹)
//聲明
int i;
//賦值
i = 10;
//聲明
void (^b1)(void);
//賦值
b1 = ^{ printf("沒有返回值及參數(shù)的Block"); };
類型聲明,為了方便簡寫,可以通過typedef簡化聲明;
//定義MyBlocks類型
typedef int (^MyBlocks) (int);
//聲明 MyBlocks的 Block
MyBlocks block = ^(int a){ return 2 * a; };
- 應用方式
既然上面說到 Block就像 int一樣也可以看做一種類型,那么它也可以像普通類型一樣,作為方法的參數(shù)及方法的返回值;
typedef int (^MyBlocks) (int);
- (MyBlocks)currentBlock {
int (^block)(int) = ^(int a) {
return 2 * a;
};
return block;
}
- (void)setBlock: (MyBlocks) blcok {
blcok(9);
}
捕獲(capture)
Block 一個很重的特性就是,它可以捕獲它聲明處的一些狀態(tài),比如它可以捕獲變量,這也是為什么前文提到,Block是包含一些狀態(tài)的代碼塊;
//全局靜態(tài)變量
static int glob = 1000;
- (void)viewDidLoad {
[super viewDidLoad];
void (^b)(void);
//局部靜態(tài)變量
static int s = 20;
//局部變量
int a = 20;
//1 1000 20 20
b = ^{ NSLog(@"gloab: %d, s: %d, a: %d", glob, s, a); };
[self myPrintFunc:1 block:b];
s = 0;
a = 0;
glob = 5000;
//2 5000 0 20
[self myPrintFunc:2 block:b];
//3 5000 0 0
b = ^{ NSLog(@"gloab: %d, s: %d, a: %d", glob, s, a); };
[self myPrintFunc:3 block:b];
}
- (void)myPrintFunc: (int) m block: (void(^)(void)) b {
//打印
NSLog(@"m: %d", m);
//調用塊
b();
}
打印結果:
m: 1
gloab: 1000, s: 20, a: 20
m: 2
gloab: 5000, s: 0, a: 20
m: 3
gloab: 5000, s: 0, a: 0
1: 這里聲明了一個Block,同時賦值給b,在這個聲明處Block可以捕獲 全局靜態(tài)變量 global、局部靜態(tài)變量s、局部變量a,然后將b作為參數(shù)傳遞給方法 myPrintFunc(:)后,被調用;
2: 改變三種變量的值后,重新調用了myPrintFunc(:)方法,也就是重新執(zhí)行了Block b,這里可以看出,三種變量在Block內的被捕獲狀態(tài)是不同的,局部變量沒有因為原有的值改變而改變,說明Block內部捕獲的是copy的一份;
3.重新聲明一個新的Block給b ,并且重新調用,這個Block 捕獲重新捕獲三個變量;
同時我們改變一下Block b,在下面的代碼中會報錯
b = ^{
glob += 100;
s += 10;
//error
a += 10;
};
總結:
- 在Block塊內可以捕獲:局部變量、形參外、還包括聲明當前Block處可以訪問的外部變量
- 從Block內部,可以直接更改訪問到的,外部變量及靜態(tài)變量(static變量)的值。
- 在Block內可以訪問的局部變量(棧內變量)中,局部變量的值會自動被保存起來(copy一份新的)然后在訪問(變量a的狀況), 所以即使這些自動變量的最初的值發(fā)生了改變,塊對象在使用時也不知道。Block copy的這些局部變量的值可以被讀取,但是不能被改變,相當于用const進行修飾的變量,改變時會報錯(解決辦法后面介紹)。
生命周期
有如下代碼
typedef int (^myBlock)(void);
@interface ViewController ()
/**
* 1
* 聲明一個block屬性
*/
@property myBlock block;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^int{
return 100;
};
[self printFunc:self.block];
[self func1:5];
[self func2:10];
//5
[self printFunc:self.block];
}
- (void)printFunc: (myBlock)b {
NSLog(@"%d", b());
}
- (void)func1: (int) n {
//2
int (^b1)(void) = ^{ return n; };
[self printFunc: b1];
//3 給屬性block賦值
self.block = b1 ;
}
- (void)func2: (int) n {
int a = 20;
int (^b2)(void) = ^{ return n * a; };
//4
[self printFunc: b2];
}
上面的輸出結果:
100
5
200
5
上面的代碼是在ARC 自動內存管理的情況下運行的,如果在手動內存管理的情況下運行代碼,可能會在 5 的時候報錯。
原因:
在2、4處,為在函數(shù)內部聲明的Block b1、b2,這里聲明的Block和在函數(shù)內部的局部變量相同,同樣在棧內存中分配空間,也就是說,函數(shù)運行完成后,為該函數(shù)分配的棧內存會被釋放,同樣的分配給b1、b2的內存也會被釋放,但是在3處將函數(shù)內部的b1 賦值給外部self.block,當執(zhí)行5的時候,由于原來的b1 已經(jīng)被釋放,所以可能會出錯,這里我們需要在賦值的時候進行copy操作,即:
如果在手動內存管理的時候
self.block = [b1 copy];
在ARC情況下,系統(tǒng)自動給我們進行了copy操作,所以不用擔心,但是在聲明Block的屬性時,我們應該做copy的屬性標記,來表示賦值的copy特性,這也是需要屬性聲明的時候需要標記copy的原因。
@property (copy) myBlock block;
官方文檔說明:
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.
_ _block 變量
前面提到Block會捕獲他可以訪問的局部變量,并創(chuàng)建副本,但是只能讀取,不能修改,但是可以讀取并更改全局/局部靜態(tài)變量的值。如果我們想要像對靜態(tài)變量那樣,對捕獲的局部變量進行同樣的操作,我們需要用_ _block 關鍵字來修飾該局部變量。
#include <stdio.h>
#include <Block.h>
void (^g)(void) = NULL;
int c = 0;
void func(int n){
__block int sh = 0;
void (^b1)(void) = ^{
sh += 1;
printf("%d: b1, n = %d, sh = %d\n", ++c, n, sh);};
void (^b2)(void) = ^{
sh += 20;
printf("%d: b2, n = %d, sh = %d\n", ++c, n, sh);};
b1(); //1, 5
b2(); //2, 6
g = b1;
sh += n*1000;
n = 999;
b2(); //3, 7
printf("----\n");
}
int main(void)
{
void (^myBlock)(void);
func(1);
myBlock = g;
myBlock(); //4
func(2);
myBlock(); //8
return 0;
}
- 輸出如下
**1: b1, n = 1, sh = 1**
**2: b2, n = 1, sh = 21**
**3: b2, n = 1, sh = 1041**
**----**
**4: b1, n = 1, sh = 1042**
**5: b1, n = 2, sh = 1**
**6: b2, n = 2, sh = 21**
**7: b2, n = 2, sh = 2041**
**----**
**8: b1, n = 1, sh = 1043**
總結_ _block 如下:
- _ _block修飾的變量的作用如下:
- 函數(shù)內Block語句引用的 _ _ block變量是Block對象可以讀取的變量,同一個作用域內多個Block對象訪問時,他們之間可以共享_ _block變量的值。
- _ _ block變量不是靜態(tài)變量,它在Block句法每次執(zhí)行時獲取變量的內存區(qū)域,也就是說同一個變量作用域內的Block對象及他們之間共享的_ _ block變量是在執(zhí)行時動態(tài)生成的。
- 訪問_ _ block變量的Block對象在被賦值后,新生成的Block對象也能共享_ _block變量的值
- 多個對象訪問同一個_ _block變量時,只要有一個Block對象存在著, _ _block變量就會存在著,如果訪問該變量的對象不存在了,該變量也就會隨之消失。
ARC中使用塊對象的注意點-避免循環(huán)引用
前面我們提到在Block語句內有關外部變量及局部變量的一些行為,下面我們說下在Block內部引用對象時的行為,代碼如下:
- (void)func1: (int) n {
int a = 2 * n;
SomeClass *objc = [SomeClass new];
void (^b1)(void) = ^{ [objc method: a]; };
outBlock = b1;
}
在上面的代碼中,b1同局部變量 a 一樣,在棧內分配空間,也相當于局部變量,在b1中調用了對象objc及a,在b1中就多了a及objc的副本,但是這里多了的objc的副本也僅僅是對objc的引用,而不會使得objc的引用計數(shù)增加,相當于一個 weak 類型的引用,但是當將b1賦值給outBlock,由于會執(zhí)行copy操作,這時候就在堆上生成了一份新的block,在這個新的block內也同樣有著對objc的引用,這時objc的引用計數(shù)會+1,因為函數(shù)func1執(zhí)行結束后,b1就會被釋放,所以被賦值的Block會繼續(xù)持有objc;
如果上面的代碼,objc 為包含當前方法的類對象的實例變量的話,即objc為self的實例變量的話,那么b1被copy后self的引用計數(shù)也會+1,objc為整數(shù)時也是一樣的效果;
根據(jù)上面的特性,在ARC情況下,使用Block語句時,要注意防止產(chǎn)生循環(huán)引用。下面介紹更詳細的例子:
@interface A ()
@property Handel* handel;
@property Logger* logger;
@end
-(void)func {
handel.cleanUp = ^{
[self.logger writeLog];
};
}
在上面的代碼中,類A有 屬性 handel,logger。handel擁有Block cleanUp,如果在A的對象方法中,調用了cleanUp,同時在 Block cleanUp 中調用了A的屬性,根據(jù)Block的特性,就會產(chǎn)生 A -> handel -> block -> A 這樣的循環(huán)。
解決辦法:
- 1.先聲明自身的若引用被Block捕獲,在Block內用強引用保存對象,然后在進行調用。
__weak A * weakSelf = self;
handel.cleanUp = ^{
A * strongSelf = weakSelf;
[strongSelf.logger writeLog];
};
-
2.通過__block解決循環(huán)引用
image.png

執(zhí)行execBlock后

這種辦法的缺點是,為了避免循環(huán)引用,必須要執(zhí)行這個block,如果不執(zhí)行,則會引起循環(huán)引用
未完待續(xù).....
