概述
上圖就是一個(gè)block簡(jiǎn)單使用,它包括了block的聲明、賦值實(shí)現(xiàn)、調(diào)用 三個(gè)部分,其中,實(shí)現(xiàn)部分可以看作是一種匿名函數(shù);跟函數(shù)一樣,block也是需要調(diào)用才能執(zhí)行內(nèi)部代碼的;賦值的行為又讓block看起來跟數(shù)據(jù)類型類似
代碼塊Block是在iOS4開始引入的,是對(duì)C語言的擴(kuò)展,用來實(shí)現(xiàn)匿名函數(shù)的特性
Block是一種特殊的數(shù)據(jù)類型,可以像基本數(shù)據(jù)類型一樣定義成變量、作為參數(shù)、返回值來使用
Block還可以保存一段代碼,在需要的時(shí)候調(diào)用
在iOS開發(fā)中,Block被系統(tǒng)應(yīng)在很多地方,例如:GCD、UIView動(dòng)畫、排序等,我們開發(fā)者也可以應(yīng)用在各類回調(diào)、傳值、傳消息等
Block的聲明、賦值實(shí)現(xiàn)、調(diào)用
Block的聲明樣式。
返回類型 (^Block名稱)(參數(shù)列表)
void(^myBlock)(NSString *, NSString *)
Block的返回類型分為有返回類型和無返回類型(void),參數(shù)列表也可有也可以沒有,具體看需求
// 無返回類型無參數(shù)列表
void(^block)();
// 無返回類型有參數(shù)列表
void(^block)(int);
// 有返回類型無參數(shù)列表
int(^block)();
// 有返回列表有參數(shù)列表
int(^block)(int);
Block的簡(jiǎn)單使用:聲明、賦值、調(diào)用
Block變量的賦值格式為:
Block變量 = ^(參數(shù)列表){函數(shù)體}; // 這里的參數(shù)列表一定要和聲明時(shí)的參數(shù)列表一致
// 聲明
void(^block)();
// 賦值
block = ^(){
NSLog(@"Hello World");
};
// 調(diào)用
block();
也可以在聲明時(shí)完成賦值
// 聲明、賦值
void(^block)() = ^(){
NSLog(@"Hello World");
};
// 調(diào)用
block();
定義Block類型
前面提到過,Block是一種特殊的數(shù)據(jù)類型,我們可以使用 typedef 來定義 Block 類型,這樣我們就可以使用該類型來聲明很多相同的Block變量了。
typedef 返回類型(^Block名稱)(參數(shù)列表);
示例:
// 聲明一個(gè)Block類型
typedef void(^Block)();
// 使用定義兩個(gè)block變量
Block myBlock,myNewBlock;
// 賦值實(shí)現(xiàn)
myBlock = ^(){
NSLog(@"Hello World");
};
myNewBlock = ^(){
NSLog(@"Hello World, I am lolita0164.");
};
// 調(diào)用
myBlock();
myNewBlock();
ARC模式下簡(jiǎn)單應(yīng)用
- 作為對(duì)象屬性實(shí)現(xiàn)消息傳遞
前面說到,Block保存一段代碼,在需要的時(shí)候調(diào)用。我們可以將使用Block的三個(gè)步驟拆開,實(shí)現(xiàn)消息傳遞、傳值功能。
// 定義Block類型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 聲明Block變量
@property (nonatomic, copy) Block myBlock;
-(void)sayHello;
@end
@implementation Person
-(void)sayHello{
// Block調(diào)用
self.myBlock(@"Hello, I am lolita0164");
}
@end
在定義聲明、調(diào)用之后,還缺少實(shí)現(xiàn)的部分,這一步通常由外部實(shí)現(xiàn)。
Person *p = [Person new];
// Block賦值實(shí)現(xiàn)
p.myBlock = ^(NSString *string) {
NSLog(@"%@",string);
};
[p sayHello];
這樣,我們就可以在Block的賦值實(shí)現(xiàn)部分里拿到 p類里的數(shù)據(jù)了。
- 作為函數(shù)參數(shù)實(shí)現(xiàn)數(shù)據(jù)回調(diào)
我們將之前的例子稍加改動(dòng)
// 定義Block類型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 將Block作為參數(shù)
-(void)sayHelloUseBlock:(Block)myBlock;
@end
@implementation Person
-(void)sayHelloUseBlock:(Block)myBlock{
// Block調(diào)用
myBlock(@"Hello, I am lolita0164");
}
@end
在外部進(jìn)行實(shí)現(xiàn)。
Person *p = [Person new];
// Block實(shí)現(xiàn)
[p sayHelloUseBlock:^(NSString *string) {
NSLog(@"%@",string);
}];
作為參數(shù)和作為屬性傳遞消息,在應(yīng)用場(chǎng)景稍稍有些不同。
作為參數(shù)時(shí),通常和當(dāng)前的方法有著緊密的聯(lián)系,函數(shù)體內(nèi)部需要與調(diào)用的外部進(jìn)行交互。例如在請(qǐng)求方法中,經(jīng)常會(huì)使用到block進(jìn)行回調(diào),而這個(gè)block和當(dāng)前的方法關(guān)系緊密,通常是該方法的結(jié)果回調(diào)。又或者是方法執(zhí)行期間需要外部提供一定的信息,從而通過block獲取外部提供的數(shù)據(jù)。
作為屬性時(shí),通常是和當(dāng)前類相關(guān),作為類與類之間的交互代表。
- 作為返回值實(shí)現(xiàn)鏈?zhǔn)秸Z法
將block作為返回值的經(jīng)典例子就是約束庫(kù) masonry
,這個(gè)庫(kù)在做完每次約束設(shè)置之后通過 block 將實(shí)例再次回調(diào),就形成了鏈?zhǔn)秸Z法。
[view makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(view1.mas_right).offset(10);
make.top.equalTo(view1).offset(0);
make.right.equalTo(-10);
make.size.equalTo(viewWidth);
}];
下面通過創(chuàng)建顏色類來演示 block 作為參數(shù)的使用。
// block 作為返回值
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
// block 的聲明和實(shí)現(xiàn)
UIColor* (^rgbBlock)(CGFloat, CGFloat, CGFloat) = ^id(CGFloat r, CGFloat g, CGFloat b) {
return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
};
return rgbBlock;
}
解析
返回值是一個(gè)有返回值參數(shù)有三個(gè)的block:UIColor * (^)(CGFloat, CGFloat, CGFloat)
。我們?cè)谠摲椒ǖ膬?nèi)部進(jìn)行了block的聲明和具體實(shí)現(xiàn),并且將其作為返回值返回了出去,那么外部在調(diào)用該方法之后接收到的是一個(gè)block,可以使用該值。
那么外部使用情況如下。
// 接收 block 類型
UIColor* (^colorBlock)(CGFloat, CGFloat, CGFloat) = [UIColor rgb];
// 使用 block 獲取到顏色
UIColor* color = colorBlock(10,33,65);
self.view.backgroundColor = color;
在丟棄不需要的部分后,代碼如下。
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
return ^id(CGFloat r, CGFloat g, CGFloat b) {
return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
};
}
// 使用block作為參數(shù)的方法
self.view.backgroundColor = UIColor.rgb(10, 33, 65);
Block 和 變量
Block訪問局部變量問題
block 內(nèi)部可以訪問局部變量。
int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
Block(); // 輸出 "global = 100"
但是 block 會(huì)把變量 復(fù)制 為自己私有的const變量,也就是說block會(huì)捕獲棧上的變量(或指針),將其復(fù)制為自己私有的const變量,當(dāng)變量被修改時(shí),不會(huì)影響到block自己私有的const變量。
int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
global = 101;
Block(); // 輸出 "global = 100"
在Block中不可以直接修改局部變量。
int global = 100;
void (^Block)() = ^(){
global ++; // 這句報(bào)錯(cuò)
NSLog(@"global = %i", global);
};
Block();
但是可以通過 __block
修飾符修改局部變量。
__block int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
global = 101;
Block(); //輸出 "global = 101"
__block int global = 100;
void (^Block)() = ^(){
global ++; // 這句正確
NSLog(@"global = %i", global);
};
Block(); //輸出 "global = 101"
原因:在局部變量前使用 __block修飾 ,在Block定義時(shí)便是將局部變量的指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)局部變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。
Block訪問全局變量、靜態(tài)變量問題
可以訪問和修改。
全局變量所占用的內(nèi)存只有一份,供所有函數(shù)共同調(diào)用,在Block定義時(shí)并未將全局變量的值或者指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)全局變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。
在Block定義時(shí)便是將靜態(tài)變量的指針傳給Block變量所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)靜態(tài)變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的。
ARC下的內(nèi)存管理
在ARC默認(rèn)情況下,Block的內(nèi)存存儲(chǔ)在堆中,ARC會(huì)自動(dòng)進(jìn)行內(nèi)存管理,我們只需要避免循環(huán)引用即可。
// 當(dāng)Block變量出了作用域,Block的內(nèi)存會(huì)被自動(dòng)釋放
void(^myBlock)() = ^{
NSLog(@"------");
};
myBlock();
如果在Block中引用了外面的對(duì)象,會(huì)對(duì)所引用的對(duì)象進(jìn)行強(qiáng)引用,但是在Block被釋放時(shí)會(huì)自動(dòng)去掉對(duì)該對(duì)象的強(qiáng)引用,因此比并不會(huì)造成內(nèi)存泄漏問題。
Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock();
// Person對(duì)象在這里可以正常被釋放
// 注:這里的Block只是單方面的強(qiáng)引用,所以不會(huì)產(chǎn)生循環(huán)引用,也不會(huì)內(nèi)存泄漏
如果對(duì)象內(nèi)部引用一個(gè)Block屬性,而在Block內(nèi)部又訪問了該對(duì)象,那么會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存泄漏。
self.block = ^{
NSLog(@"------%@", self);
};
解決辦法:使用一個(gè)弱引用的指針指向該對(duì)象,然后在Block內(nèi)部使用該弱引用指針來進(jìn)行操作,這樣就避免了Block對(duì)對(duì)象進(jìn)行強(qiáng)引用。
__weak typeof(self) weakSelf = self;
weakSelf.block = ^{
NSLog(@"------%@", weakSelf);
};
提示:如果只是Block單方面地對(duì)外部變量進(jìn)行強(qiáng)引用,并不會(huì)造成內(nèi)存泄漏。
補(bǔ)充
1、聲明block屬性的時(shí)候?yàn)槭裁从胏opy呢?
在說明為什么要用copy前,先思考下block是存儲(chǔ)在棧區(qū)還是堆區(qū)呢?其實(shí)block有3種類型:
- 全局塊(_NSConcreteGlobalBlock)
- 棧塊(_NSConcreteStackBlock)
- 堆塊(_NSConcreteMallocBlock)
全局塊存儲(chǔ)在靜態(tài)區(qū)(也叫全局區(qū)),相當(dāng)于OC中的單例;棧塊存儲(chǔ)在棧區(qū),超出作用域則馬上被銷毀。堆塊存儲(chǔ)在堆區(qū)中,是一個(gè)帶引用計(jì)數(shù)的對(duì)象,需要自行管理其內(nèi)存。
關(guān)于內(nèi)存分配,請(qǐng)看這篇:C語言內(nèi)存分配。
怎么判斷一個(gè)block所在的存儲(chǔ)位置呢?
- block不訪問外界變量(包括棧中和堆中的變量)
block既不在棧中也不在堆中,此時(shí)就為全局塊,ARC和MRC下都是如此。
- block訪問外面變量
MRC環(huán)境下:默認(rèn)存儲(chǔ)在棧區(qū)
ARC環(huán)境下:默認(rèn)存儲(chǔ)在堆中,實(shí)際上是先放在棧區(qū),在ARC情況下自動(dòng)又拷貝到堆區(qū),自動(dòng)釋放
因此,使用 copy 修飾符的作用就是將block從棧區(qū)拷貝到堆區(qū)
為什么要這么做呢?官方給出的答案是:
復(fù)制到堆區(qū)的主要目的就是 保存 block 的狀態(tài),延長(zhǎng)其聲明周期。因?yàn)閎lock如果在棧上的話,其所屬的變量作用域結(jié)束,該block就被釋放掉了,block中的 __block 變量也同時(shí)被釋放掉了,為了解決超出作用域就被釋放的問題,我們就需要把block復(fù)制到堆中。
總結(jié)
OC 中的 block 是對(duì) C 語言的匿名函數(shù)的一種特性是實(shí)現(xiàn)。block 具有函數(shù)特性,同時(shí)也可以作為變量使用。block 可以作為屬性、參數(shù)、返回值使用。想要在 Block 內(nèi)部修改外部變量時(shí),需要使用 __Block
將變量指針傳遞給 block。在使用 Block 時(shí)需要特別注意內(nèi)存泄漏的問題。