目錄
- Block概述
- Block定義方式
- Block保存代碼
- Block傳值
- Block對外部變量的傳遞
- Block做參數
- Block做返回值(實現鏈式編程)
- Block內存管理
- Block循環引用
- Block其他注意點
1. Block概述
Block是C語言級別和運行時方面的一個特征。Block封裝了一段代碼邏輯,用{}括起,和標準C語言中的函數/函數指針很相似。
2. Block定義方式
- 聲明:(返回類型)(^聲明的Block名稱)(參數列表);
- 實現:^(返回類型)(參數列表){代碼塊}
- 返回類型(^block變量名)(參數列表) = ^(形參列表) {};
void(^MyBlock)(int a,int b) = ^(int a, int b){}
返回類型:void
Block名字:MyBlock
實際參數: int a ,int b
形式參數: int a ,int b
-
Block三種定義方式以及block類型
- 沒有返回值,沒有參數的定義方式
//返回值類型(^block的名字)(參數類型) = ^(參數類型和參數名) {};
void(^block)() = ^(){
NSLog(@"調用了block");
};
//當然,沒有參數的時候可以把括號省去
void(^block)() = ^{
NSLog(@"調用了block");
};
- 有返回值,有參數的定義方式
//返回值類型(^block的名字)(參數類型) = ^(參數類型和參數名) {};
//如果有參數,定義的時候,必須要寫參數,而且必須要有參數變量名
int(^block)(int) = ^(int a){
return 1;
};
- 定義時帶有返回類型的(不常用)
int(^block)() = ^int{//這里的int就是這個block的返回值類型
return 1;
};
- 系統提供了一個定義block的宏
// block快捷方式 輸入:inline
returnType(^blockName)(parameterTypes) = ^(parameters) {
statements
};
- block的調用
//定義block
void(^block)() = ^{
NSLog(@"調用了block");
};
//調用block
block();
- block的類型
//block有自己的類型,就想@"string"是NSString類型一樣
//格式就是 返回值(^)(參數類型)
//比如這個block的類型就是: int(^)(int)
int(^block)(int) = ^(int a){
return 1;
};
//這個block的類型就是void(^)()
void(^block)() = ^{
NSLog(@"調用了block");
};
//在ARC中把block定義成屬性要用strong類型,定義方式如下:
@property (nonatomic, strong) void(^block)();//這樣在類中可以拿到self.block
//當然也可以取別名:
typedef void(^BlockType)();//BlockType不是變量名,而是這種類型的block的別名
//然后就可以這樣
@property (nonatomic, strong) BlockType block;
3. Block保存代碼
這種方式在app的設置界面中使用到,需求大約是這樣:
設置界面每一行cell會做不同的事,有的是跳轉界面,有的是switch開關,有的是需要顯示一下AlertView.
這樣的話,我們可以把需要執行的代碼包裝成block,放在cell的模型里面,當點擊cell的時候,拿出模型中的block來執行。
//這是cell的模型
typedef void(^optionBlock)();
@interface SettingItem : NSObject
@property(nonatomic,copy)NSString *icon;//圖標
@property(nonatomic,copy)NSString *title;//文字
@property(nonatomic,assign) Class destVc;//需要跳轉的界面
@property(nonatomic,copy) optionBlock option;//需要執行的代碼
@end
然后我們可以在didSelectRowAtIndexPath里面這么做:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (item.option != nil) {//block存在就執行
item.option();
}else if ([item isKindOfClass:[SettingArrowItem class]]) {//執行跳轉界面
SettingArrowItem *newItem = (SettingArrowItem *)item;
UIViewController *vc = [[newItem.destVc alloc]init];
vc.title = item.title;
[self.navigationController pushViewController:vc animated:YES];
}
}
4. Block進行傳值
需求如下:
在ViewControllerOne跳到ViewControllerTwo
ViewControllerTwo拿到數據后dismiss
在ViewControllerOne打印數據
//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//點擊控制器View的時候調用
ModalViewController *modalVc = [[ModalViewController alloc] init];
//對Block屬性進行賦值
modalVc.block = ^(NSString *value) {
NSLog(@"%@",value);
};
[self presentViewController:modalVc animated:YES completion:nil];
}
//ViewControllerTwo.h:
@interface ModalViewController : UIViewController
@property (nonatomic, strong) void(^block)(NSString *value);
@end
//ViewControllerTwo.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 調用Block 傳值給ViewControllerOne
if (_block) {
_block(@"123");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
5. Block對外部變量的傳遞
一個很簡單的問題:block使用外部變量,是值傳遞,還是指針傳遞。
- 值傳遞
//block為值傳遞只有一種情況:
int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();//這里調用block打印出的是3,是值傳遞
*注意:因為block中使用的外面變量的值是拷貝過來的即值拷貝,所以在調用之前修改外界變量的值,不會影響到block中拷貝的值.
結論:如果block訪問的變量是局部變量,那么變量是值傳遞.
- 指針傳遞
static int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();//這里調用block打印出的是5,是指針傳遞
//另外全局變量,靜態變量,__block修飾的變量都是指針傳遞
結論:如果是全局變量或者靜態變量,那么變量是指針傳遞。
- 經過其他測試總結:
(1)如果是局部變量,Block是值傳遞
(2)如果是靜態變量,全局變量,__block修飾的變量,block都是指針傳遞
6. Block做參數
第一次見到block當做參數是在AFN框架中,AFN幫你拿到數據以后,執行你傳給他的block:
//這一個個對AFN進行簡單封裝的方法:
+(void)requestWihtMethod:(RequestMethodType)methodType
success:(void (^)(id response))success //是一個block
failure:(void (^)(NSError* err))failure //是一個block
{
AFHTTPSessionManager *manage = [AFHTTPSessionManager manager];
[manage GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);//拿到數據后執行你傳入的block
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
failure(error);//拿到數據后執行你傳入的block
}];
}
}
接下來我們自定義一個計算器,在block里面自定義計算方式,將block傳入計算器來進行計算:
//CalculatorManager.h:
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;//要計算的數據
-(void)calculatorWithMethod:(NSInteger(^)(NSInteger // 計算方法parameterresult))methodBlock;
@end
//CalculatorManager.m:
-(void)cacultor:(NSInteger (^)())cacultorBlock
{
if (cacultorBlock) {//判斷block是否為空
_result = cacultorBlock(_result);//執行block中的計算方法
}
}
//ViewController.m:使用計算器
-(void)viewDidLoad {
[super viewDidLoad];
// 創建計算器管理者
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr calculator:^(NSInteger result){//自定義計算方法block,作為參數傳進去
result += 5;
result += 6;
result *= 2;
return result;
}];
NSLog(@"%ld",mgr.result);
7. Block做方法返回值(鏈式編程)
我們平時寫一些工具類的方法的時候(比如計算器)
//注:result是CalculatorManager的屬性,用來保存計算結果
//如果計算方法這么寫
-(int)add:(int)value{
_result += value;
return result;
}
//就要這么調用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:5]]]]]];//返回值是數字,要繼續用mar調用add來執行操作
//如果計算方法這么寫:
-(CalculatorManager *)add:(int)value
{
_result += value;
return self;
}
//就要這么調用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[[[[[mgr add:5] add:5] add:5] add:6] add:7];//返回值是計算器本身,可以繼續調用add方法
現在我們要用返回值是block的方法來實現鏈式編程:
//計算方法這么寫,返回值是 返回值為CalculatorManager的block.
-(CalculatorManager *(^)(int))add//相當于一個get方法
{
return ^(int value){
_result += value;
return self;
};
}
//就可以這么調用
CalculatorManager *mgr = [[CalculatorManager alloc] init];
mgr.add(5).add(5).add(5).add(5);
下面簡單介紹一下調用原理:
mgr.add相當于get方法的調用: [mgr add];
mgr.add返回的是一個block,所以你可以給他一個參數5,于是寫成這樣:mgr.add(5)
然后block返回的又是CalculatorManager,所以繼續調用add
如此循環下去.......
8. Block內存管理
首先,在oc中block是一個對象,只有對象才涉及到內存管理
block的內存管理在MRC和ARC中有不同的地方,接下來將分別介紹
1.MRC:
Block存放位置:
int a = 3;//這是一個局部變量
void(^block)() = ^{
NSLog(@"調用block%d",a);
};
NSLog(@"%@",block);
//打印結果:<__NSStackBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在棧里面
static int b = 2;
-(void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"調用block%d",b);
};
NSLog(@"%@",block);
}
//打印結果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區
定義屬性時:
@property (nonatomic, copy) void(^block)();
block只能使用copy,不能使用retain,
因為使用retain,block還是在棧里面 代碼塊過了方block就銷毀了,再次訪問self.block會出現壞內存訪問
使用copy是放在堆里面,代碼塊過了不會銷毀
MRC管理Block總結:
只要Block沒有引用外部局部變量,Block放在全局區
只要Block引用外部局部變量,Block放在棧里面.
2.ARC:
Block存放位置:
int a = 3;//這還是一個局部變量
void(^block)() = ^{
NSLog(@"調用block%d",a);
};
NSLog(@"%@",block);
//打印結果:<__NSMallocBlock__: 0x7fc498746000>
//此時引用了外部局部變量,block放在堆里面
static int b = 2;
-(void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"調用block%d",b);
};
NSLog(@"%@",block);
}
//打印結果:<__NSGloBalBlock__: 0x7fc498746000>
//此時引用了全局變量,block放在全局區
定義屬性時:
@property (nonatomic, strong) void(^block)();
block只能使用strong,不要使用copy
因為當使用copy的時候,set方法是調用了copy幫你深拷貝一次,沒有這個必要.
就像NSString一樣,他一般都是@"a"這種常量,沒必要再去深拷貝一次,所以NSString常量也用strong不用copy.
ARC管理Block總結:
只要Block沒有引用外部局部變量,Block放在全局區
只要Block引用外部局部變量,Block放在堆里面.
block循環引用
9. Block的循環引用問題
- 為什么會產生循環引用:
因為block會給內部的強指針對象進行一次強引用,比如常見的傳入block中的self進行強引用
并且在self中,block又是strong的,self對block是強引用
所以,你強引用我,我強引用你,誰也不會被釋放,就造成了循環引用
所以,為了避免循環引用,我們要在block使用self之前,進行這一步操作:
__weak typeof(self) weakSelf = self;
在block中使用weakSelf,就不會產生循環引用問題了.
使用了__weak修飾符的對象,作用等同于定義為weak的property。自然不會導致循環引用問題.
- 接下來是雙層block的循環引用問題:
先來看這樣一個例子:
__weak typeof(self) weakSelf = self;
block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執行下面的代碼
NSLog(@"%@",weakSelf);
});
};
block();
這樣執行下去,如果在延時時間2秒還沒到,控制器就dismiss或者pop(總之是銷毀)了,打印出的weakSelf是null,
為什么呢?
因為只有push self或者present self的控制器對self強引用,當self dismiss了或者pop了,就沒有人對self強引用了(block對self沒有強引用),根據ARC的內存管理原則,當沒有人對一個對象強引用的時候,該對象就會銷毀.
所以,當self dismiss了或者pop了,self就銷毀了,2秒后block再訪問self的時候,self已經不再了.
這時,我們要做如下處理:
__weak typeof(self) weakSelf = self;
block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//這個是局部變量 棧內的強指針 當這個block執行完畢 這個指針就會釋放
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延時2秒執行下面的代碼
NSLog(@"%@",strongSelf);
});
};
block();
加上這一句:strong typeof(weakSelf) strongSelf = weakSelf; strong 就相當于定義為strong的property
那么當self銷毀的之前(pop或者dismiss之前),有兩個人對self強引用(一個是push self或者present self的控制器,一個是這個block中定義的strongSelf).
當控制器銷毀的時候(pop或者dismiss時),push self或者present self的控制器不在強引用self,self失去一個強引用,但是self不會銷毀,因為block中定義的strongSelf還在對self強引用.
但是你會問,那這么不會造成循環引用嗎?不著急,繼續往下看:
當延時2秒到了,block可以訪問到strongSelf
當延時block代碼塊過了,strongSelf就會指向nil了(因為strongSelf是局部變量,存在棧內的強指針 當這個block執行完畢,這個指針就會釋放)
此時就沒有人對self進行強引用了,self也會銷毀,
至此,大家都銷毀了..
10. Block其他注意點
block中可以定義和外界同名的變量,并且如果在block中定義了和外界同名的變量,在block中訪問的是block內部的變量。可以通過打印變量的地址看出兩個變量地址不同。
因為block中使用的外面變量的值是拷貝過來的即值拷貝,所以在調用之前修改外界變量的值,不會影響到block中拷貝的值
默認情況下,在block內部不能改變外面變量的值,如果想在block中修改外界變量的值,必須在外界變量前面加上
__block
。如果在block中修改了外界變量的值,會影響到外界變量的值。如果block中訪問到了外界的變量,block會將外界變量拷貝一份到堆內存中。