寫在前面:demo 或者代碼 評論發郵箱地址,我會及時給需要的小伙伴發過去.
一 : 科普一分鐘
- 什么是block: 個人簡單的理解為就是一個存放代碼片段的容器,作用就是保存代碼.
- block 蘋果官方定義為
對象
可以用數組
和字典
進行操作,寫到這同學們可能會明白了,block 說的簡單點就是 一個可以存放代碼片段的對象,可以進行內存管理,可以作為屬性,等普通對象操作.
既然分析明白了就可以做事情了,接下來進入block 的神器世界
二 : block的基本使用
block的聲明
void(^block)();
聲明一個名字為 block 的 block 對象;當然 名字可以換啊 我們可以用doit 代替,更形象
void(^doit)();
聲明了名字 為doit 的block 代碼塊對象block 的定義
block 的定義方式通常有三種方式
- 第一種 :沒有參數 沒有返回值 = 右邊相當對名字為 doit1這個block對象的代碼塊的賦值 里面存放一段代碼
void(^doit1)() = ^(){
NSLog(@"關注我吧,會有更多精彩");
};
- 第二種 :如果沒有參數,參數可以隱藏
void(^block2)() = ^{
NSLog(@"關注我吧,會有更多精彩");
};
如果有參數,定義的時候 必須要寫參數 而且必須要有參數變量名
void(^block22)(int) = ^(int a){
NSLog(@"關注我吧,會有更多精彩");
};
- 第三種 block 返回值可以省略,不管有沒有返回值 都可以省略
int(^block3)() = ^int{
return 3;
};
也可以寫成
int(^block3)() = ^{
return 3;
};
- block 的類型
int(^)(NSString *)
我們定義了一個返回值 為int
參數為NSString
block 代碼塊對象類型 它現在還沒有名字 我們現在給它取名為 :doit4
int(^doit4)(NSString *) = ^(NSString *name){
return 2;
};
- block 調用
不要以為定義完成 就大功告成了 ,因為我們還沒有去調用它,我們寫block 代碼塊的 目的 就是去使用它 .
doit1();
之前我們已經聲明了 doit1 這個block 代碼塊對象 所以運行后的結果是
NSLog(@"關注我吧,會有更多精彩");
- block 的快捷方式
我們手動去寫block 會很麻煩 我們可以敲inline
然后選擇第一個 接下來 就可以方便我們使用了,會自動聯想代碼
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
- block作為屬性
block 對象也可以當成屬性使用 屬性的書寫原則是 如何聲明 就如何屬性 block
例如
@property(nonatomic,strong) void(^doit)() ;
一個無返回值,無參數的block類型 名字為doit的屬性對象
當然這樣書寫 我們會不習慣 我們還可以重新定義類型 來寫成我們熟悉的形式.
例如重新聲明類型
//BlockType;類型的別名
typedef void(^BlockType)();
注意 這個BlockType 是類型名
并不是對象名
所以我們要再定義一個 類型 為BlockType 名字為doit 的對象
@property(nonatomic,strong) BlockType doit;
- blcok 內部變量傳遞分析
- 看代碼分析結果:
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
a = 5;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
block();
}
結果為 : 5
- 看代碼分析結果
- (void)viewDidLoad {
[super viewDidLoad];
int a = 3;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
a = 5;
block();
}
結果為 : 3
原因分析
如果是局部變量 block 是值傳遞,當定義block 是 a = 3 此時,block 內部代碼塊的a = 3 ,改變a = 5,但是不影響block 代碼塊內容.看代碼分析結果
- (void)viewDidLoad {
[super viewDidLoad];
static int a = 3;
void(^block)() = ^{
NSLog(@"-- %d",a);
};
a = 5;
block();
}
- 結果 : 5
- 原因分析 : 如果是靜態變量,全局變量,block 是指針傳遞
三 : block 在開發中的基本應用
1. block 保存一段代碼
實例場景:在我們寫tableView列表中 根據不同的cell 判斷不同點擊事件 我們多數人的做法是 通過很多很麻煩的判斷如下:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == 0) {
}else if (indexPath.row == 1){
}else if (indexPath.row == 2){
}
}
其實我們可以用Block 存儲要做的事情 來代替這些復雜的判斷,因為這樣增加了代碼的可讀性,而且需求發生變化的時候也非常容易的完成了需求的變化
做法:
- 我們在
cellItem
這個模型里添加 block 屬性
//保存每個cell 做的事情
@property(nonatomic,strong) void(^block)();
- 保存每個模型對應cell 要做的事情
cellItem *item1 = [cellItem itemWithTitle:@"打電話"];
item1.block = ^{
NSLog(@"睡覺");
};
cellItem *item2 = [cellItem itemWithTitle:@"發短信"];
item2.block = ^{
NSLog(@"吃飯");
};
cellItem *item3 = [cellItem itemWithTitle:@"發郵件"];
item3.block = ^{
NSLog(@"裝逼");
};
_items = @[item1,item2,item3];
- 替換復雜又low 的判斷
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
cellItem *item = self.items[indexPath.row];
if (item.block) {
item.block();
}
}
2. 逆向傳值
我們的經典逆向傳值的例子就是代理了吧,大多數的開發者也是習慣于代理的方式,但是block 的傳值方式要比代理好太多了,代理的6步驟比較復雜. 接下來我們要用block 替換代理
實例場景:
首先我們定義了兩個控制器ViewController 和 TZpopViewController 點擊ViewController 的view模態到TZpopViewController 然后 點擊TZpopViewController的view 返回 并且傳至到ViewController
ViewController方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
TZpopViewController *vc = [[TZpopViewController alloc]init];
vc.block = ^(NSString *index) {
NSLog(@"index = %@",index);
};
vc.view.backgroundColor = [UIColor brownColor];
[self presentViewController:vc animated:YES completion:nil];
}
TZpopViewController 屬性
@property(nonatomic,strong)void(^block)(NSString*index) ;
TZpopViewController 方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (_block) {
_block(@"tz123");
}
}
- 分析
首先當我們在ViewController
點擊觸發touchesBegan
方法時候 創建了 TZpopViewController 并且給它的block
屬性賦值了一段帶代碼
= ^(NSString *index) {
NSLog(@"index = %@",index);
};
此時這個代碼片段是不走的 ,因為沒有操作執行block
當我們觸發TZpopViewController
中的 touchesBegan
方法時候 做了執行block 的方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (_block) {
_block(@"tz123");
}
}
此時則會在ViewController
中走打印方法 打印結果為tz123
四 : block 內存管理
- block 在MRC下的注意事項
由于MRC沒有strong ,weak局部對象相當于基本類型,存放于棧區,代碼走過就銷毀
1.在MRC 中block只能使用copy 修飾,不能使用retain,因為使用retain 了block 存放于棧區,被銷毀了.
2.在MRC只要block引用了外部局部變量,block 放在棧里面,只要block 沒有引用外部局部變量,block 放在全局區里面.
- block在ARC下的注意事項
1.只要block 引用外部局部變量,block放在堆里面
注意:在ARC
block
使用 strong
修飾,最好不用使用 copy
節省性能.
有的小伙伴會反駁說用copy ,其實是看需求而定 ,因為我們大部分需求是不可變賦值給不可變 ,所以這里建議用strong .如果保守的話也可以用copy
- 分析
我們使用 copy 通常是淺拷貝 因為 是不可變賦值給不可變 ,不用考慮 重新分配內存的問題, 我們copy 修飾 內部方法會走[block copy]
這個方法系統會分析是否從新分配內存 ,浪費資源.所以我們通常情況下使用strong
五 : block 的循環引用問題
- 我們在使用的時候稍微疏忽會造成循環引用,雙方都不會銷毀,導致內存泄漏.
- 模擬循環引用
在ViewController 定義屬性
@property(nonatomic,strong)void(^block1)();
- (void)viewDidLoad {
[super viewDidLoad];
_block1 = ^{
NSLog(@"關注我有更多精彩%@",self);
};
_block1();
}
這種寫法就會造成循環引用.
-
分析
block造成循環引用:block 會對里面所有的強指針變量都強引用一次
循環引用圖解.png 解決辦法
__weak typeof (self) weakself = self;
_block1 = ^{
NSLog(@"關注我有更多精彩%@",weakself);
};
_block1();
}
思考
假如我們想在block 代碼塊種寫一段延遲做的事情怎么辦
通常block 代碼塊走過,則會銷毀拿不到.解決辦法
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof (self) weakself = self;
_block1 = ^{
__strong typeof (weakself) strongself = weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"關注我有更多精彩%@",strongself);
});
};
_block1();
}
再次強引用一次,知道 延遲代碼走完,則指針釋放 .
六 : block 在開發中的高級應用
- block 當參數在開發中的應用
1.什么情況下block 被當成參數了呢,
看參數有沒有^ 如果有^ 怎參數為block.
2.什么時候需要把block 當成參數,
做的事情由外部決定,什么時候做由內部決定.
代碼舉例:
- 做一個計算器:要求怎么計算由外部決定,時候時候計算由內部決定
TZcaculoterManager
@property(nonatomic,assign)NSInteger result;
//計算
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock;
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock{
_result = cacultorblock(_result);
}
調用
- (void)viewDidLoad {
[super viewDidLoad];
caculoterManager *magr = [[caculoterManager alloc]init];
[magr cacutor:^(NSInteger result){
result += 5;
result += 7;
return result;
}];
}
- 分析 表面看有點難理解 其實我們不妨把它當成另一個函數
-(void)magrCautor:(NSString*)tz{
}
感覺是一個意思,后者傳遞的是字符串,前者傳遞的是block 代碼塊,區別在于前者控制 傳遞的代碼塊何時調用.AFN 封裝 等各種封裝 很多采用這種
- block 當返回值在開發中的應用
主要應用場景:鏈式編程,Masonry就是最典型的鏈式編程,其內部原理用就是用block實現
鏈式編程特點:把所有的語句 用.號連接起來,好處:可讀性非常號.
代碼實現 我們先簡單寫一個返回值為block 的函數
-(void(^)())test{
return ^{
};
}
這個是一個返回值 為無返回值,無參數的block
現在我們來調用這個函數
self.test();
相當于 self.test 這個函數給我們返回block
然后我們去執行 block()
- 操作:我們現在還做 一個需求 封裝一個計算器,提供一個累加方法
cacutotermanager
.h
@property(nonatomic,assign)int result;
-(cacutotermanager* (^)(int))add;
.m
-(cacutotermanager* (^)(int))add{
return ^(int value){
_result += value;
return self;
};
}
調用
cacutotermanager *magr1 = [[cacutotermanager alloc]init];
magr1.add(1).add(2).add(4);
模仿了 Masonry
的鏈式編程方法.
- 解析
magr1.add
調用方法后 返回block 執行block(1)
;
此時block 的返回類型是cacutotermanager
magr
再調用magr(2)....
七 : 總結
通過上述大家對block 的應用應該了解的非常透徹了,希望大家活學活用. 根據需求選擇正確的方法.效率更高. ^ _ ^ 下期再見. 想聽什么可以在評論區留言. H5,Java 陸續會開始. 加油!!