一.block定義
二.block的本質(zhì)
三.block變量捕獲(Capture)
四.block的類型
五.block的copy操作
六.block使用了對(duì)象類型的auto變量的分析
七.
__block
講解八.block的循環(huán)引用問題
九.block的一些使用場景
block
在我們開發(fā)中隨處可見,比如我們常用的AFN
框架和ReactiveCocoa
框架等,多線程中的GCD
,各種方法的回調(diào)等等,可見block
在我們開發(fā)中的重要性,今天我就詳細(xì)的整理下關(guān)于block
的一些東西,如果有什么問題也歡迎大家一起討論.
一. block知識(shí)回顧
1. 定義
什么是Block?=》 Block是將函數(shù)及其執(zhí)行上下文封裝起來的對(duì)象。
這里借用網(wǎng)上的一張截圖,感覺還是比較詳細(xì)的
上圖聲明的block
類型為:int (^)(int)
注意:
block
塊中的代碼并沒有執(zhí)行,只有當(dāng)我們明確調(diào)用的時(shí)候才會(huì)具體執(zhí)行
我們可以使用inlineBlock
的快捷方式生成一段block
代碼
2. 使用typedef重定義
對(duì)于可能需要重復(fù)地聲明多個(gè)相同返回值相同參數(shù)列表的block變量, 如果我們總是重復(fù)地編寫一長串代碼來聲明變量 那么會(huì)非常的繁瑣,所以此時(shí)我們可以使用typedef
來對(duì)block進(jìn)行重定義 具體如下:
typedef int(^sumBlock)(int , int);
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
sumBlock myblock = ^(int a,int b)
{
return a+b;
};
myblock(20,30);
}
二. block的本質(zhì)
block使用起來非常的方便,那么他的底層或者說是本質(zhì)到底是什么呢? 下面我們先通過一個(gè)最簡單的block來分析下它的底層到底是如何實(shí)現(xiàn)的:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"Hello World");
};
block();
}
return 0;
}
通過下面的命令將我們的.m
文件轉(zhuǎn)化成.cpp
文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
生成的C++
文件如下圖所示,為了便于理解我去掉了部分強(qiáng)制轉(zhuǎn)換的東西:
通過以上的代碼分析我們我們可以得出一個(gè)結(jié)論:
block本質(zhì)上是一個(gè)OC對(duì)象,它的內(nèi)部也有一個(gè)isa指針
當(dāng)然我們也可以通過下面的代碼來驗(yàn)證我們的結(jié)論:
void (^block)(void) = ^{
NSLog(@"Hello World");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
執(zhí)行結(jié)果:
block[4176:730867] __NSGlobalBlock__
block[4176:730867] __NSGlobalBlock
block[4176:730867] NSBlock
block[4176:730867] NSObject
重點(diǎn): 可以看到block對(duì)象是繼承自NSObject的一個(gè)OC對(duì)象
四. 變量捕獲(Capture)
1. 局部變量auto
因?yàn)樯厦娑x的block太過簡單,一些本質(zhì)的東西我們無法看到,這里我們定義一個(gè)稍微復(fù)雜一點(diǎn)的block對(duì)象,代碼如下:
int age = 10;//等價(jià)于 auto int age = 10;
void(^block)(void) = ^{
NSLog(@"age is %d",age);
};
age = 20;
block();
執(zhí)行該block后打印的age的值是多少呢?
相信大部分同學(xué)立馬就可以回答出來結(jié)果 age is 10
,但是為什么是這個(gè)結(jié)果呢? 相信大部分同學(xué)應(yīng)該挺迷惑的
不著急我們一步步來為大家分析:
同樣將該段代碼轉(zhuǎn)化為C++代碼 如下圖:
通過以上代碼的簡單分析我們就可以清晰的看出block在內(nèi)部捕獲了age,并且是
值傳遞
, 所以age在block外部修改并不會(huì)影響到其內(nèi)部的值該段C++代碼結(jié)構(gòu)其實(shí)可以簡化成下面這幅圖:
2. 局部變量Static
下面我們來看一個(gè)稍微復(fù)雜點(diǎn)的代碼:
int age = 10;//等價(jià)于 auto int age = 10 ,auto可以省略
static int height = 10;
void(^block)(void) = ^{
NSLog(@"age is %d,height is %d",age,height);
};
age = 20;
height = 20;
block();
執(zhí)行該block后打印的結(jié)果是多少呢? age is 10,height is 20
老規(guī)矩,同樣將該段代碼轉(zhuǎn)化為C++代碼,如下圖所示:
通過以上代碼我們可以看到block可以同時(shí)捕獲到age和height,但是用Static修飾的height和age是不一樣的,age是一個(gè)值傳遞,而height是一個(gè)引用傳遞
,所以在block外部即使height的數(shù)據(jù)變更我們也可以獲取到最新的數(shù)據(jù).
思考
那么不知道大家有沒有想過這樣一個(gè)問題,為什么用static
修飾的變量是引用傳遞,而用auto修飾的變量就是值傳遞呢?
下面我們通過一段簡單的代碼來為大家分下一下:
因?yàn)樽饔糜虻膯栴} 當(dāng)代碼執(zhí)行到23行的時(shí)候也就是我們執(zhí)行完test函數(shù)后,此時(shí)age的內(nèi)存已經(jīng)銷毀,如果block不馬上將age的值捕獲到其內(nèi)部,那么執(zhí)行到24行的時(shí)候,訪問到的age此時(shí)就是一個(gè)垃圾數(shù)據(jù)了,而用static修飾的height就不一樣了,所以傳遞的是地址,將來height的數(shù)據(jù)即使更新了,我們也可以獲取到最新的數(shù)據(jù).
3. 全局變量
上面的兩種情況都是局部變量,下面我們?cè)趤砜纯慈肿兞康那闆r:
#import <Foundation/Foundation.h>
int age = 10;
static int height = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"age is %d,height is %d",age,height);
};
age = 20;
height = 20;
block();
}
return 0;
}
打印結(jié)果:
age is 20,height is 20
轉(zhuǎn)化為C++代碼:
通過上面代碼我們可以看到 __main_block_impl_0內(nèi)部并沒有捕獲age和height 所以打印結(jié)果就是最新的數(shù)據(jù)
通過上面的分析我們應(yīng)該知道因?yàn)閍ge和height都是全局變量,在任何地方都可以訪問到他們,所以block并不用去捕獲他們
4. 總結(jié):
關(guān)于block捕獲變量我們可以總結(jié)成下面一個(gè)表格,清晰明了:
五.block的類型
1. block的三種類型
先說結(jié)論:block對(duì)象有3種類型,分別為:
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
他們都是繼承自NSBlock類型.我們都知道應(yīng)用程序在內(nèi)存中分為 代碼段
數(shù)據(jù)段
堆段
和棧段
,其中堆段
是我們程序員自己動(dòng)態(tài)分配的,并且需要我們手動(dòng)釋放內(nèi)存(free()或者release()操作
),而棧段
是非常危險(xiǎn)的,隨時(shí)都有可能會(huì)被系統(tǒng)銷毀.
因?yàn)橄聢D比較經(jīng)典我就直接拿過來用了
那么這三種類型之間如何區(qū)分以及有什么區(qū)別呢?下面我們先來看一幅圖:
2. 結(jié)論一:如果block沒有訪問auto變量則其類型為:__NSGlobalBlock
對(duì)于這種類型的block
我們不需要考慮作用域的問題,而且對(duì)他進(jìn)行copy
或者retain
操作也是無效的.
驗(yàn)證:
#import <Foundation/Foundation.h>
int height = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//沒有訪問任何變量
void (^block1)(void) = ^{
NSLog(@"Hello World");
};
//訪問全局變量
void (^block2) (void) = ^{
NSLog(@"height is %d",height);
};
//訪問static修飾的局部變量
static int age = 10;
void (^block3) (void) = ^{
NSLog(@"age is %d",age);
};
NSLog(@"%@--%@---%@",[block1 class],[block2 class],[block3 class]);
}
return 0;
}
打印結(jié)果:
block[4486:803768] __NSGlobalBlock__--__NSGlobalBlock__---__NSGlobalBlock__
3. 結(jié)論二:如果block訪問了auto修飾的變量則其類型為:__NSStackBlock
驗(yàn)證:
int age = 10;
void (^block) (void) = ^{
NSLog(@"age is %d",age);
};
NSLog(@"%@",[block class]);
打印結(jié)果:
block[4524:811710] __NSMallocBlock__
恩? 怎么是__NSMallocBlock__
?搞錯(cuò)了吧.....
不著急,因?yàn)槲覀兊捻?xiàng)目是ARC環(huán)境,編譯器幫助我們做了一些事情,我們將代碼改成MRC環(huán)境:
再次執(zhí)行:
block[4577:821982] __NSStackBlock__
這才對(duì)嘛
但是因?yàn)?code>__NSStackBlock__的內(nèi)存是分配在棧段
的,所以在使用的時(shí)候經(jīng)常會(huì)出現(xiàn)一些問題,比如:
#import <Foundation/Foundation.h>
void (^block) (void);
void test()
{
int age = 10;
block = ^{
NSLog(@"age is %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
執(zhí)行結(jié)果:
block[4919:908731] age is -272632360
納尼???!!! 怎么是這么個(gè)亂七八糟的數(shù)據(jù)?
因?yàn)樵揵lock的類型是__NSStackBlock__
,所以當(dāng)test()
函數(shù)執(zhí)行完畢后,block對(duì)象中成員已經(jīng)被釋放掉了,所以當(dāng)我們?cè)趫?zhí)行block()
的時(shí)候拿到的數(shù)據(jù)就是垃圾數(shù)據(jù)了,而不是我們想要的10.
那么該如何解決該問題呢?
我們只需要將block進(jìn)行copy操作即可.
void test()
{
int age = 10;
block = [^{
NSLog(@"age is %d",age);
} copy];
}
執(zhí)行結(jié)果:
block[5004:929720] age is 10
所以在開發(fā)中,我們經(jīng)常會(huì) 對(duì) block進(jìn)行Copy操作 原因就是因?yàn)檫@個(gè)
4. 所以結(jié)論三 NSStackBlock 調(diào)用了copy后會(huì)變成 NSMallocBlock
每一種類型的block調(diào)用Copy操作后的結(jié)果如下圖所示:
block的copy操作
在上文中我們的環(huán)境是MRC環(huán)境,如果是ARC環(huán)境的時(shí)候 block內(nèi)部使用了auto修飾的局部變量時(shí),該block的類型是NSMallocBlock,為什么會(huì)這樣呢?因?yàn)樵贏RC環(huán)境下亿虽,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上谍夭,比如以下這些情況:
1. block作為函數(shù)返回值時(shí)
2. 將block賦值給__strong指針時(shí)
3. block作為Cocoa API中方法名含有`usingBlock`的方法參數(shù)時(shí)
4. block作為`GCD API`的方法參數(shù)時(shí)
5. ....
.
.
.
基于以上結(jié)論:
MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);
ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
但是為了統(tǒng)一,其實(shí)我們更建議不管是ARC環(huán)境還是MRC環(huán)境下block都用copy修飾
block使用了對(duì)象類型的auto變量的分析
因?yàn)樯厦嫖覀兎治龅慕Y(jié)果都是基于基本數(shù)據(jù)類型的,有些問題涉及不到,這里我們分析一種復(fù)雜的情況 , 即 block內(nèi)部使用了對(duì)象類型的變量
首先我們定義一個(gè)Person對(duì)象
@interface Person : NSObject
@property (nonatomic,assign) int age;
@end
在該對(duì)象內(nèi)部調(diào)用dealloc
方法
#import "Person.h"
@implementation Person
-(void)dealloc{
NSLog(@"Person -------dealloc");
}
@end
執(zhí)行程序后 代碼執(zhí)行到20行的時(shí)候 調(diào)用了Person對(duì)象的dealloc方法 , 哥們 你在逗我么 這個(gè)地球人都知道好吧.....
?? 不著急,我們一步步來 上面是預(yù)熱
將我們的編譯環(huán)境切換到MRC環(huán)境,執(zhí)行下面的代碼
斷點(diǎn)停留在了25行,此時(shí)block還沒有銷毀,但是打印結(jié)果顯示Person已經(jīng)掛掉了
結(jié)論一:如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
同樣的代碼將編譯環(huán)境切換到ARC,執(zhí)行結(jié)果如下圖:
ARC環(huán)境下,當(dāng)代碼執(zhí)行到24行的時(shí)候person對(duì)象并沒有銷毀,說明block對(duì)象對(duì)person有一個(gè)強(qiáng)引用
如果我們將Person對(duì)象用__weak
修飾呢?結(jié)果又是什么樣子呢?如下圖所示:
那么其底層是如何操作的呢?執(zhí)行下面的命令將代碼轉(zhuǎn)化為C++代碼:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
總結(jié):當(dāng)block內(nèi)部訪問了對(duì)象類型的auto變量時(shí)
如果block是
__NSStackBlock__
類型,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
如果block被拷貝到堆上,則會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)內(nèi)部會(huì)執(zhí)行
_Block_object_assign
函數(shù),該函數(shù)內(nèi)部會(huì)根據(jù)auto變量的修飾符__strong 熟丸、__weak而做出相應(yīng)的操作,如果是__strong 則形成強(qiáng)引用,而__weak 則會(huì)形成弱引用關(guān)系.
如果block從堆上移除,則會(huì)調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部會(huì)調(diào)用
_Block_object_dispose
函數(shù),該函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類似于release操作.
練習(xí)題:
仔細(xì)看下面的代碼,Person對(duì)象會(huì)在什么時(shí)候銷毀?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 18;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age is %d",person.age);
});
NSLog(@"Hello World");
}
分析過程:因?yàn)镚CD中的block是在堆段
中,所以block會(huì)一直存在直到執(zhí)行完畢之后才會(huì)銷毀,因?yàn)榇颂嶱erson對(duì)象是一個(gè)__Strong
,所以block會(huì)對(duì)Person對(duì)象形成一個(gè)強(qiáng)引用,所以Person對(duì)象會(huì)在3秒后Blcok執(zhí)行完畢后釋放
執(zhí)行結(jié)果:
六. __block講解
在具體講解__block
之前我們看一段代碼:
我們想在block內(nèi)部去修改age的值,但是此時(shí)系統(tǒng)編譯失敗并且提示我們?nèi)鄙?code>__block修飾符,首先我們來思考下為什么在block內(nèi)部不能去修改auto修飾的局部變量呢?
其實(shí)仔細(xì)看過上面的分析之后我們肯定知道答案了,因?yàn)樽饔糜虻脑?自動(dòng)變量隨時(shí)都有可能被銷毀, 所以在block內(nèi)部是不能去更改auto修飾的自動(dòng)變量的.
除了系統(tǒng)給我們的提示, 添加__block
修飾符外,其實(shí)我們還可以通過添加static
修飾符,因?yàn)?code>static修飾的局部變量是一個(gè)地址傳遞,所以完全沒有問題.
或者將age放到函數(shù)外部,也就是將age設(shè)置成一個(gè)全局變量,這樣也是可以更改的
但是不管是static
修飾還是全局變量,age的性質(zhì)就改變了 所以此處最好還是用__block
修飾,那么__block
內(nèi)部做了什么事情呢?
老規(guī)矩..
我們通過C++代碼可以看到 編譯器會(huì)將__block
變量包裝成一個(gè)對(duì)象,然后將age的地址值傳遞給該對(duì)象,通過修改該對(duì)象內(nèi)部的age來達(dá)到目的
注意:
__block可以用于解決block內(nèi)部無法修改auto變量值的問題,但是不能修飾全局變量老翘、靜態(tài)變量(static)
__block的內(nèi)存管理
當(dāng)block在棧上時(shí),僅僅是使用了__block變量锻离,并沒有對(duì)__block變量產(chǎn)生強(qiáng)引用
當(dāng)block被copy到堆時(shí)
會(huì)調(diào)用block內(nèi)部的copy函數(shù)
copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)
_Block_object_assign
函數(shù)會(huì)對(duì)__block
變量產(chǎn)生強(qiáng)引用(僅當(dāng)ARC時(shí)才有效,MRC時(shí)候并不會(huì)對(duì)__block
變量產(chǎn)生強(qiáng)引用)
當(dāng)block從堆中移除時(shí)
會(huì)調(diào)用block內(nèi)部的dispose
函數(shù)
dispose
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)
_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的__block
變量
Block的循環(huán)引用問題
兩個(gè)對(duì)象相互持有铺峭,這樣就會(huì)造成循環(huán)引用,如下圖所示:
圖中汽纠,對(duì)象A持有對(duì)象B卫键,對(duì)象B持有對(duì)象A,相互持有虱朵,最終導(dǎo)致兩個(gè)對(duì)象都不能釋放莉炉。
列舉個(gè)簡單的例子:
Person.h
@interface Person : NSObject
/** 名字 */
@property (nonatomic, copy) NSString *name;
/** block */
@property (nonatomic, copy) void (^dosomethingBlock)();
@end
Person.m
@implementation Person
-(void)dealloc
{
NSLog(@"%s", __func__);
}
@end
在ViewController
中去實(shí)例化Person
對(duì)象:
- (void)viewDidLoad {
[super viewDidLoad];
Person *per = [[Person alloc] init];
per.name = @"Jack";
per.dosomethingBlock = ^{
NSLog(@"-----------%@", per.name);
};
}
@end
運(yùn)行上面的代碼,發(fā)現(xiàn)并沒有調(diào)用 Person
對(duì)象中的 dealloc
方法.說明此時(shí)發(fā)生了循環(huán)引用的問題,
通過上文這么長時(shí)間的分析,我相信大家一定知道block內(nèi)部做了什么事情, 因?yàn)?Person
對(duì)象是用__strong
修飾的,所以此時(shí) __Block_Object_Assign方法
內(nèi)部會(huì)自動(dòng)產(chǎn)生一個(gè)【強(qiáng)引用】指向【對(duì)象Person】
那么此時(shí)我們?cè)撊绾谓鉀Q這個(gè)問題呢? 通常的做法是創(chuàng)建一個(gè) __weak
修飾的弱引用 指向 person對(duì)象,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
Person *per = [[Person alloc] init];
__weak typeof(Person) *weakPerson = per;
per.name = @"Jack";
per.dosomethingBlock = ^{
NSLog(@"-----------%@", weakPerson.name);
};
}
運(yùn)行上面的代碼,發(fā)現(xiàn) Person
對(duì)象調(diào)用了 dealloc
方法.
Block的嵌套
還是以上面的例子,我們?cè)?per.dosomethingBlock
中延遲三秒后打印 weakPerson.name
,仔細(xì)查看下面的代碼,你會(huì)發(fā)現(xiàn)什么問題?
- (void)viewDidLoad {
[super viewDidLoad];
Person *per = [[Person alloc] init];
__weak typeof(Person) *weakPerson = per;
per.name = @"Jack";
per.dosomethingBlock = ^{
NSLog(@"beign-------");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(),
^{
NSLog(@"after-----------%@", weakPerson.name);
});
};
per.dosomethingBlock();
}
控制臺(tái)輸出結(jié)果:
2017-08-14 17:48:17.441 block[23041:1182337] beign-------
2017-08-14 17:48:17.441 block[23041:1182337] -[Person dealloc]
2017-08-14 17:48:20.729 block[23041:1182337] after-----------(null)
通過打印結(jié)果我們可以看到當(dāng) Block
被執(zhí)行的時(shí)候立馬打印了 beign-------
信息,然后緊接著 Person
對(duì)象被銷毀, 3秒以后打印了 after-------
信息,注意因?yàn)榇藭r(shí) Person
對(duì)象已經(jīng)被銷毀了,所以打印出了 Null
所以 在延遲執(zhí)行的Block內(nèi)部 為了保住 Person
對(duì)象不被銷毀 我們需要使用一個(gè)強(qiáng)引用來保住 Person對(duì)象的命,稍微更改下代碼:
- (void)viewDidLoad {
[super viewDidLoad];
Person *per = [[Person alloc] init];
__weak typeof(Person) *weakPerson = per;
per.name = @"Jack";
per.dosomethingBlock = ^{
NSLog(@"beign-------");
Person *strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(),
^{
NSLog(@"after-----------%@", strongPerson.name);
});
};
per.dosomethingBlock();
}
控制臺(tái)輸出結(jié)果:
2017-08-14 17:50:04.947 block[23068:1184458] beign-------
2017-08-14 17:50:08.234 block[23068:1184458] after-----------Jack
2017-08-14 17:50:08.234 block[23068:1184458] -[Person dealloc]
通過上面的結(jié)果我們可以看到 在 dispatch_after
中使用了 一個(gè)強(qiáng)引用 strongPerson
來保住了 Person
對(duì)象的命,所以此時(shí)才是我們想要的結(jié)果.
block的一些使用場景
將block作為對(duì)象屬性,恰當(dāng)時(shí)機(jī)的時(shí)候才去調(diào)用
場景一
比如我們從A控制器Modal到B控制器,在B控制器中點(diǎn)擊了某個(gè)按鈕后需要給A控制器傳遞一些數(shù)據(jù),當(dāng)然這種也可以使用代理來實(shí)現(xiàn),但是我感覺 使用block更為簡潔:
B控制器:
@interface BViewController : UIViewController
@property (nonatomic, strong) void(^buttonClickBlock)(NSString *param);
@end
-(void)buttonClick{
if (_buttonClickBlock) {
_buttonClickBlock(@"需要傳遞的參數(shù)");
}
}
A控制器:
BViewController *modalVc = [[BViewController alloc] init];
modalVc.view.backgroundColor = [UIColor brownColor];
modalVc.buttonClickBlock = ^(NSString *param) {
NSLog(@"傳遞過來的參數(shù):%@",param);
};
// 跳轉(zhuǎn)
[self presentViewController:modalVc animated:YES completion:nil];
場景二:
有一個(gè)UITableView
,此時(shí)點(diǎn)擊每一行都會(huì)做不同的事情,如果不用block 你是不是想著在點(diǎn)擊的時(shí)候去判斷行號(hào),然后根據(jù)行號(hào)在去做對(duì)應(yīng)的事情? 然而通過block我們可以簡化這個(gè)流程:
在對(duì)應(yīng)的實(shí)體模型中,我們只需要定義一個(gè)具體做事的block,例如:
@interface CellItem : NSObject
@property (nonatomic, strong) NSString *title;
// 保存每個(gè)cell做的事情
@property (nonatomic, strong) void(^doSomethingBlock)();
@end
然后在tableView
中:點(diǎn)擊的時(shí)候執(zhí)行對(duì)應(yīng)的代碼塊就可以了,是不是一下輕松了許多
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建模型
CellItem *item1 = [CellItem itemWithTitle:@"打電話"];
// 把要做的事情(代碼)保存到模型
item1.doSomethingBlock = ^{
NSLog(@"打電話");
};
CellItem *item2 = [CellItem itemWithTitle:@"發(fā)短信"];
item2.doSomethingBlock = ^{
NSLog(@"發(fā)短信");
};
CellItem *item3 = [CellItem itemWithTitle:@"發(fā)郵件"];
item3.doSomethingBlock = ^{
NSLog(@"發(fā)郵件");
};
_items = @[item1,item2,item3];
}
// 點(diǎn)擊cell的時(shí)候執(zhí)行對(duì)應(yīng)的block代碼塊就可以了
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CellItem *item = self.items[indexPath.row];
if (item.doSomethingBlock) {
item.doSomethingBlock();
}
}
將block當(dāng)作函數(shù)參數(shù)來傳遞
當(dāng)我們封裝一個(gè)方法的時(shí)候,而這個(gè)方法具體要做什么事情不是方法內(nèi)部能夠決定的,但什么時(shí)候做是由內(nèi)部決定的,(即內(nèi)部決定執(zhí)行時(shí)間,而外部傳入具體做些什么)——這個(gè)時(shí)候就可以使用block來作為函數(shù)參數(shù)
列舉一個(gè)經(jīng)常使用的例子:
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
對(duì)于系統(tǒng)提供的這個(gè)動(dòng)畫方法,要執(zhí)行什么樣的動(dòng)畫,執(zhí)行完動(dòng)畫要做什么事情,該方法內(nèi)部是不知道的,需要交給用戶自己去實(shí)現(xiàn)
場景二:
要求封裝一個(gè)計(jì)算器類CacultorManager
,該類提供一個(gè)計(jì)算方法:怎么計(jì)算是由外界決定的,什么時(shí)候計(jì)算由內(nèi)部決定.
CacultorManager.h
@interface CacultorManager : NSObject
/***保存計(jì)算的結(jié)果**/
@property (nonatomic, assign) NSInteger result;
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
CacultorManager.m
@implementation CacultorManager
- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock
{
if (cacultorBlock) {
_result = cacultorBlock(_result);
}
}
@end
具體使用該類:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建計(jì)算器管理者
CacultorManager *mgr = [[CacultorManager alloc] init];
[mgr cacultor:^(NSInteger result){
result += 5;
result -= 6;
return result;
}];
NSLog(@"%ld",mgr.result);
}
@end
將block當(dāng)作函數(shù)返回值來傳遞
相信大家之前都寫過類似這樣的代碼
[View mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(anotherView);
make.left.equalTo(anotherView);
make.width.mas_equalTo(@60);
make.height.mas_equalTo(@60);
}];
類似于 Masonry
這種可以連續(xù).top.equalTo(anotherView);
的寫法我們稱為鏈?zhǔn)骄幊?/code>,而實(shí)現(xiàn)這種思路的重點(diǎn)在于
方法的返回值必須是block,而block必須返回本身,block的參數(shù)就是我們需要操作的數(shù)據(jù)
下面我們就來寫一個(gè)類似這樣的小功能:還是拿上面的計(jì)算器類CalculatorManager
來舉例子:
CalculatorManager.h
@interface CalculatorManager : NSObject
/***保存計(jì)算的結(jié)果**/
@property (nonatomic, assign) int result;
- (CalculatorManager *(^)(int))add;
- (CalculatorManager *(^)(int))sub;
@end
CalculatorManager.m
@implementation CalculatorManager
- (CalculatorManager *(^)(int))add
{
return ^(int value){
_result += value;
return self;
};
}
- (CalculatorManager *(^)(int))sub
{
return ^(int value){
_result -= value;
return self;
};
}
@end
在實(shí)現(xiàn)類中:
- (void)viewDidLoad {
[super viewDidLoad];
CalculatorManager *mgr = [[CalculatorManager alloc] init];
mgr.add(5).add(5).sub(6).add(5);
NSLog(@"%d",mgr.result);
}
@end