雞湯
“你知道一敗涂地有什么好處嗎?”
“那就是,你僅剩一事可做:絕地反擊”
前言
本人作為一個菜鳥干iOS開發(fā)一年左右,除了深感目前該行業(yè)的艱難以外,也在慢慢學(xué)習(xí)當(dāng)中,今天和大家談?wù)勱P(guān)于 我對Block的認(rèn)識,有什么不足的地方,還望大家多多指正
1.Block的認(rèn)識
Block實際上是OC語言對閉包的實現(xiàn),是帶有自動變量值的匿名函數(shù)
Block的作用: 保存一段代碼
閉包:一個函數(shù),或者一個指向函數(shù)的指針,加上這個函數(shù)執(zhí)行的非局部變量。通俗來說就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量,甚至可以訪問不同運行上文中的變量。
2.Block的基本語法 (聲明 -> 實現(xiàn) -> 調(diào)用)
書寫B(tài)lock的快捷鍵 : inline
- 無參數(shù)無返回值
void(^b1)(void); //聲明 ^托字符:表示后面的是 block變量
void(^b1)(void) //block的類型
//b1是 block名字,也叫 block 變量
^(void){ }; //block的實現(xiàn)部分
void(^b1)(void) = ^(void){ //聲明實現(xiàn)放一起了
NSLog(@"無參無返回值");
// block 的實現(xiàn)部分
};
//調(diào)用 block.
b1();
- 無返回值,有參數(shù)
//定義block 參數(shù)只需要有一個類型,但是實現(xiàn)部分需要有參數(shù)名。
void(^b2)(NSString *) = ^(NSString *string){
NSLog(@"%@",[string stringByAppendingString:@"有人說寫字難看的男生長得很帥"]);
};
b2(@"照鏡子發(fā)現(xiàn),都TM是騙人的"); //調(diào)用block
- 有參數(shù),有返回值
// 寫一個block實現(xiàn)兩個數(shù)的 乘積,并返回結(jié)果
NSInteger(^b3)(NSInteger,NSInteger) = ^(NSInteger a,NSInteger b){
return a * b;
};
NSInteger result1 = b3(3,5);
NSLog(@"%ld",result1);
- 有返回值,無參數(shù)
// 寫一個 block實現(xiàn),返回一個[30,50]的隨機(jī)數(shù);
int (^b5)(void) = ^(){
int i = arc4random()%21+30;
return i;
};
int i = b5();
NSLog(@"%d",i);
3.Block在開發(fā)中的應(yīng)用場景
- 在一個方法中定義,在另一個方法中調(diào)用
- 在一個類中定義,在另一個類中調(diào)用 (常用)
聲明
//BlockType:類型的別名,這里不是變量名
typedef void(^BlockType)();
@interface ViewController ()
//block怎么聲明,就如何定義成屬性
@property (nonatomic, strong) void(^block)(); //開發(fā)中推薦用這種
//@property (nonatomic, strong) BlockType block; //和上面一樣
@end
實現(xiàn)
void(^block)() = ^() {
NSLog(@"這里就是Block塊里的內(nèi)容,注意:只有在調(diào)用block的時候這個block塊里的內(nèi)容才會被執(zhí)行");
};
_block = block;
調(diào)用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
// block的調(diào)用:就去尋找保存的代碼,直接調(diào)用
_block();
}
需求:TableView展示3個cell,打電話,發(fā)短信,發(fā)郵件
首先我們創(chuàng)建一個模型Cell item 在.h中代碼如下
#import <Foundation/Foundation.h>
@interface CellItem : NSObject
//設(shè)計模型:控件需要展示什么內(nèi)容,就定義什么屬性
@property (nonatomic, strong) NSString *title;
//保存每個cell做的事情
@property (nonatomic, strong) void(^block)();
+ (instancetype)itemWithTitle:(NSString *)title;
@end
在Cellitem.m中
#import "CellItem.h"
@implementation CellItem
+ (instancetype)itemWithTitle:(NSString *)title{
CellItem *item = [[self alloc] init];
item.title = title;
return item;
}
@end
在ViewController.m中,我們實現(xiàn)需求
#import "TableViewController.h"
#import "CellItem.h"
@interface TableViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSArray *items;
@end
@implementation TableViewController
/*
需求:tableView展示3個cell,打電話,發(fā)短信,發(fā)郵件
*/
- (void)viewDidLoad {
[super viewDidLoad];
_tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:(UITableViewStylePlain)];
[self.view addSubview:_tableView];
_tableView.delegate = self;
_tableView .dataSource = self;
//創(chuàng)建模型
CellItem *item1 = [CellItem itemWithTitle:@"打電話"];
item1.block = ^{
NSLog(@"打電話");
};
CellItem *item2 = [CellItem itemWithTitle:@"發(fā)短信"];
item2.block = ^(){
NSLog(@"發(fā)短信");
};
CellItem *item3 = [CellItem itemWithTitle:@"發(fā)郵件"];
item3.block = ^{
NSLog(@"發(fā)郵件");
};
_items = @[item1,item2,item3];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = @"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:cellIdentifier];
}
CellItem *item = self.items[indexPath.row];
cell.textLabel.text = item.title;
// if (indexPath.row == 0) {
// //打電話
// }else if (indexPath.row == 1){
// //發(fā)短信
// }else if (indexPath.row == 2){
// //發(fā)郵件
// }
return cell;
}
//點擊cell的時候就會調(diào)用
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//把要做的事情(代碼)保存到模型
CellItem *item = self.items[indexPath.row];
if (item.block) {
item.block(); //此處調(diào)用了Block,所以才會走block塊中保存的代碼
}
}
@end
接下來我們看一下Block中的逆向傳值
首先我們創(chuàng)建2個控制器, FirstViewController和SecondViewController, 在后者.h中我們實現(xiàn)block的聲明代碼如下
#import <UIKit/UIKit.h>
typedef void(^passValue)(NSString *str,NSURL *url);
@interface SecondViewController : UIViewController
@property (nonatomic, copy) passValue block; //block作為屬性
@end
在SecondViewController.m中實現(xiàn)布局以及在返回上一頁的方法中進(jìn)行block的調(diào)用
- (void)leftAction:(UIBarButtonItem *)sender{
[self.navigationController popViewControllerAnimated:YES];
if (self.block) {
self.block(self.secondTF.text,self.url); //調(diào)用Block
}
}
在FirstViewController.m的點擊跳轉(zhuǎn)的方法中實現(xiàn)Block塊的內(nèi)容
- (void)rightAction:(UIBarButtonItem *)sender{
SecondViewController *secondVC = [SecondViewController new];
[self.navigationController pushViewController:secondVC animated:YES];
__weak FirstViewController *weakSelf = self;
//這個block只有在second中被調(diào)用時才會執(zhí)行,通過回調(diào),將second中的文本框的內(nèi)容傳遞給first
secondVC.block = ^(NSString *str,NSURL *url){ //這里面的string就是回調(diào)second頁面中傳遞的參數(shù)
weakSelf.textField.text = str;
NSData *data = [NSData dataWithContentsOfURL:url];
weakSelf.imageV.image = [UIImage imageWithData:data];
};
}
接下來是Block作為參數(shù)的使用
什么時候需要把block當(dāng)做參數(shù)去使用呢? 做什么事情由外界決定,但是什么時候做由內(nèi)部決定
需求: 封裝一個計算器,提供一個計算方法,怎么計算由外界決定,什么時候計算由我內(nèi)部決定
首先,我們創(chuàng)建一個CacultorManager類,代碼如下:
#import <Foundation/Foundation.h>
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;
//計算
- (void)cacultor:(NSInteger(^)(NSInteger result))cacultorBlock;
@end
#import "CacultorManager.h"
@implementation CacultorManager
- (void)cacultor:(NSInteger (^)(NSInteger))cacultorBlock{
if (cacultorBlock) {
_result = cacultorBlock(_result);
// 作為方法中的參數(shù),這里開始調(diào)用 參數(shù)cacultorBlock, 也就是由方法內(nèi)部來決定的
}
}
@end
在ViewController.m中,我們實現(xiàn)如下代碼:
- (void)viewDidLoad {
[super viewDidLoad];
/**
* 怎么區(qū)分參數(shù)是blcok,就看有沒有^,只要有^,把block當(dāng)做參數(shù)就行
* 注意:把block當(dāng)做參數(shù),并不是馬上就去調(diào)用Block,什么時候調(diào)用,由方法內(nèi)部決定
*/
//創(chuàng)建計算器管理者
CacultorManager *mgr = [[CacultorManager alloc] init];
//調(diào)用了方法,block是參數(shù),不管block內(nèi)有多少東西,都只作為參數(shù)進(jìn)行了傳遞,并不一定會執(zhí)行。調(diào)用block才會執(zhí)行
[mgr cacultor:^(NSInteger result) {
result += 5;
result += 6;
result *= 2;
return result;
//上面的計算器類調(diào)用了方法就走方法里面的東西,方法里面又調(diào)用block塊,就返回到這里走block塊中的內(nèi)容,也就是說具體執(zhí)行不執(zhí)行block塊中的內(nèi)容要看調(diào)沒調(diào)用block
}];
NSLog(@"%ld",mgr.result);
}
接下來我們再看一下Block作為方法返回值的用法
這里要提到關(guān)于第三方框架Masonry的鏈?zhǔn)骄幊趟枷?即把所有的語句用 點語法 連接起來,這樣優(yōu)點就是可讀性非常高,其核心就是返回值是一個block
- (void(^)())test{ //block 作為返回值
NSLog(@"%s",__func__);
return ^{
NSLog(@"調(diào)用了blcok");
};
}
----------------------- 分割線 ----------------------
- (void)viewDidLoad {
[super viewDidLoad];
self.test(); //這就是為什么masonry為什么能寫出這樣的效果,因為返回值是一個blcok
//我們做個比較
void(^block)() = ^{
NSLog(@"調(diào)用了block");
};
block(); //這個和上面的 self.test()一樣
}
需求: 封裝一個計算器,提供一個加號方法
同樣的,我們創(chuàng)建一個CalculatorManager類, 在這個類中我們先實現(xiàn)普通的方法,然后和作為返回值的block做一個比較
#import <UIKit/UIKit.h>
@interface CalculatorManager : UIViewController
@property (nonatomic, assign) int result;
- (CalculatorManager *)add:(int)value; //普通方法
- (CalculatorManager * (^)(int))add; // block作為返回值方法
@end
#import "CalculatorManager.h"
@interface CalculatorManager ()
@end
@implementation CalculatorManager
- (CalculatorManager *)add:(int)value{
_result += value;
return self;
}
- (CalculatorManager * (^)(int))add{
return ^(int value){
_result += value;
return self;
};
}
@end
我們在ViewController.m中使用這個類,看看有什么不同點
- (void)viewDidLoad {
[super viewDidLoad];
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:5];
[mgr add:5];
[mgr add:5]; //如果這樣的話,不太好,會有很多行
NSLog(@"%d",mgr.result);
mgr.add(5);
mgr.add(5).add(6).add(7).add(8).add(9).add(10);
NSLog(@"%d",mgr.result);
}
以上就是一些block的基礎(chǔ)使用,接下來我們來看一下使用block過程中的一些注意點
Block的內(nèi)存管理
在蘋果的api文檔中,block是一個對象,所以存在著內(nèi)存管理的方式(MRC/ARC)
在MRC中:
- 只要block沒有引用外部的局部變量,block放在全局區(qū)
- 只要block引用外部局部變量,block放在棧里面
- block只能使用copy,不能使用retain,如果使用retain,block還是在棧里面(既然是在棧里面,代碼塊一過,block就會銷毀)
在ARC中:
- 只要block沒有引用外部的局部變量,block放在全局區(qū)
- 只要block引用外部局部變量,block放在堆里面
- blcok使用Strong,最好不要使用copy
Block的循環(huán)引用:
- Block造成循環(huán)引用:Block會對里面所有強(qiáng)指針變量(外部對象變量)全部強(qiáng)引用一次 所以加 WeakSelf。
- 如果是局部變量,block是值傳遞
- 如果是靜態(tài)變量,全局變量,__block修飾的變量,block是指針傳遞
總結(jié):
- 不能再block中直接修改局部變量,如有需要添加一個** __block** 來修飾,block可以訪問全局變量。
- 在block作為函數(shù)參數(shù)的時候,使用block的時候引用計數(shù)會自動+1,所以我們用__weak 去替換原有的 self才行。