Block的定義:
Block的格式: ^ + 返回值類型(可以省略) + 參數(shù)(如果沒有參數(shù),可以省略) + {表達(dá)式}
Block變量格式:返回值類型(不可省略,沒有返回值使用void) + (^變量名稱) + (參數(shù))(不可省略,無參使用())。格式和函數(shù)指針很相似,只是把*改成了^
int (^blockName)(int, NSString *) = ^(int para1, NSString *para2) {
return 1;
}
// int (^blockName)(int, NSString*) 的意思是要定義一個(gè)名字為"blockName"的block,他具有兩個(gè)參數(shù)和一個(gè)int類型返回值。
void (^blockName)() = ^{
}; // 如果沒有參數(shù),可省略如上面寫法,如果沒有返回值,必須要寫void
使用 typedef:(Block聲明比較復(fù)雜,建議使用這種方式聲明Block)
typedef int(^blockName1)( int, NSString*);
blockName1 bn = ^(int para1, NSString *para2) {
return 1;
};
無參數(shù)情況:
typedef int(^blockName1)();
blockName1 bn = ^{
return 1;
};
作為函數(shù)參數(shù)寫法:
C函數(shù)
#參數(shù)
typedef int(^ABlock)();
void cFunc(void(^blockName)(), ABlock block) {
//兩種寫法都可以
}
#返回值寫法
int (^func())(int) {
return ^(int count) { return count; };
}
ABlock func() {
return ^(int count) { return count; };
}
OC函數(shù)
#參數(shù)
- (void)OCFunc:(void(^)())blockName andOtherBlock:(ABlock)block { // 注意第一種寫法的特別之處,OC函數(shù)要求變量類型和形參名分開,所以寫法和C不同
}
@property 寫法
@property (nonatomic, copy) int (^block)(int a); //使用C的方式,不能使用OC函數(shù)形參的寫法.
@property (nonatomic, copy) Block blockName;
#以Block作為方法返回值的寫法:
- (int (^)(int))blockBack { //和C的寫法是不同的,需要注意
return ^(int count){ retrun count; };
}
block 不同其他變量的原因在于它不是一個(gè)單一的變量,而是一個(gè)方法,我們要傳遞的是一個(gè)代碼塊,并且這個(gè)代碼塊可以存在參數(shù),這個(gè)參數(shù)并不是在定義block的時(shí)候就賦予值,而是我們在實(shí)際運(yùn)行block的時(shí)候才賦予值。
??因此對于有參數(shù)的block,當(dāng)我們傳遞過去的時(shí)候,它需要接收方提供相應(yīng)的參數(shù)才能運(yùn)行,這么做我們就可以在A類為B類將來會(huì)發(fā)生的事件提前做好處理的方法,即使我們還沒有這些事件的具體參數(shù),某種意義上省去了兩者之間的委托關(guān)系。
委托關(guān)系就是B類發(fā)生一個(gè)事件后,通知A類,讓A類再針對這個(gè)事件進(jìn)行一些處理。
??而使用Block,則是A已經(jīng)提前將這個(gè)事件的處理方法告訴了B類,等事件發(fā)生的時(shí)候,B類無需通知A類,直接運(yùn)行實(shí)現(xiàn)設(shè)置好的處理方法(block)即可。
??如果你在運(yùn)行一個(gè)方法的時(shí)候又想告訴這個(gè)方法在某一特定情況你還要怎么做的話,就可以使用block。
GCD:
GCD主要使用Block來代替委托模式,使程序變得簡潔,同時(shí)運(yùn)行效率也得到了提高。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static int i = 0;
while (i < 20) {
dispatch_async(dispatch_get_main_queue(), ^{
_label.text = [NSString stringWithFormat:@"%d",i++]; //UI的更新必須要在主線程完成
});
[NSThread sleepForTimeInterval:1];
}
});
NSLog(@"s");
這個(gè)函數(shù)的意思是更新label的顯示,每秒更重跟新一次,如果我不使用異步更新直接使用一個(gè)方法如下:
- (void)renLabel {
static int i = 0;
while ( i < 20 ) {
_label.text = [NSString stringWithFormat:@"%d", i++];
[NSThread sleepForTimeInterval:1];
}
}
這會(huì)導(dǎo)致整個(gè)程序無法響應(yīng)其它事件。
如果使用NSThread 完成則需要委托,非常繁瑣。
Block 基礎(chǔ)知識
block是一個(gè)特殊的OC對象, 它建立在棧上, 而不是堆上, 這么做一個(gè)是為性能考慮,還有就是方便訪問局部變量.
默認(rèn)情況下block使用到的局部變量都會(huì)被復(fù)制,而不是保留.
所以它無法改變局部變量的值.
如果在變量面前加上__block, 那么編譯器回去不會(huì)復(fù)制變量, 而是去找變量的地址, 通過地址來訪問變量, 實(shí)際上就是直接操作變量.
另外塊是在棧上分配的, 所以一旦離開作用域, 就會(huì)釋放, 因此如果你要把快用在別的地方, 必須要復(fù)制一份.
所以在屬性定義一個(gè)快的時(shí)候需要使用copy: @property (nonatomic, copy) void (^onTextEntered)(NSString *enteredText);
塊是不能保留的, retain對塊沒有意義.
用塊遍歷字典:
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(@"%@,%@",key,obj);
}
ARC下Block何時(shí)會(huì)從棧自動(dòng)被復(fù)制到推, 以及__block和__weak的使用問題
由于Block是默認(rèn)建立在棧上, 所以如果離開方法作用域, Block就會(huì)被丟棄, 在非ARC情況下, 我們要返回一個(gè)Block ,需要 [Block copy];
在ARC下, 以下幾種情況, Block會(huì)自動(dòng)被從棧復(fù)制到堆:
1.被執(zhí)行copy方法
2.作為方法返回值
3.將Block賦值給附有__strong修飾符的id類型的類或者Blcok類型成員變量時(shí)
4.在方法名中含有usingBlock的Cocoa框架方法或者GDC的API中傳遞的時(shí)候.
對于非ARC下, 為了防止循環(huán)引用, 我們使用__block來修飾在Block中使用的對象:
__block id blockSelf=self;
self.blk=^{
NSLog(@"%@",blockSelf); //在非ARC下對于棧上的_block對象, Block不會(huì)對其復(fù)制, 僅僅使用, 不會(huì)增加引用計(jì)數(shù).
};
對于ARC下, 為了防止循環(huán)引用, 我們使用__weak來修飾在Block中使用的對象:
__weak id weakSelf=self;
self.blk=^{
NSLog(@"%@",weakSelf);
};
如果要在ARC下, 為了防止循環(huán)引用, 使用__block來修飾在Block中使用的對象,仍然會(huì)被retain, 所以需要多做一些設(shè)置
__block id blockSelf=self;
self.blk=^{
NSLog(@"%@",blockSelf);
self.blk=nil; //blk被釋放, blk只有的blockSelf也就被釋放了
};
blk(); //并且一定要運(yùn)行一次, 否則不能被釋放
這樣就使blk斷開了與blockSelf的持有關(guān)系.
這么多好處是可以自己控制對self的持有時(shí)間.
不過在最新的ios版本中, 這些會(huì)始終被已嘆號形式提示存在循環(huán)引用問題.
這種書寫方式不被推薦. 除非你要在block中修改__block的指針指向.
其實(shí)我們用使用__weak修飾符, 只是不能修改對象本身, 但是可以修改對象的屬性.
demo總結(jié)
#pragma mark --無參數(shù)無返回值的Block
/**
* 無參數(shù)無返回值的Block
*/
-(void)func1{
/**
* void :就是無返回值
* emptyBlock:就是該block的名字
* ():這里相當(dāng)于放參數(shù)。由于這里是無參數(shù),所以就什么都不寫
*/
void (^emptyBlock)() = ^(){
NSLog(@"無參數(shù),無返回值的Block");
};
emptyBlock();
}
#pragma mark --有參數(shù)無返回值的Block
- (void)func2 {
/**
* 調(diào)用這個(gè)block進(jìn)行兩個(gè)參數(shù)相加
*
* @param int 參數(shù)A
* @param int 參數(shù)B
*
* @return 無返回值
*/
void (^sumBlock)(int ,int ) = ^(int a,int b){
NSLog(@"%d + %d = %d",a,b,a+b);
};
/**
* 調(diào)用這個(gè)sumBlock的Block,得到的結(jié)果是20
*/
sumBlock(10,10);
}
#pragma mark --有參數(shù)有返回值
- (void)func3 {
/**
* 有參數(shù)有返回值
*
* @param NSString 字符串1
* @param NSString 字符串2
*
* @return 返回拼接好的字符串3
*/
NSString* (^logBlock)(NSString *,NSString *) = ^(NSString * str1,NSString *str2){
return [NSString stringWithFormat:@"%@%@",str1,str2];
};
//調(diào)用logBlock,輸出的是 我是Block
NSLog(@"%@", logBlock(@"我是",@"Block"));
}
簡單的回調(diào)
在控制器3中觸發(fā)了touchBegan方法,改變控制器2的背景色
ViewController2.m
#import "ViewController2.h"
#import "ViewController3.h"
@interface ViewController2 ()
@end
@implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
ViewController3 *vc =[[ViewController3 alloc]init];
// 回調(diào)修改顏色
vc.backgroundColor = ^(UIColor *color){
self.view.backgroundColor = color;
};
[self.navigationController pushViewController:vc animated:YES];
}
ViewController3.h
#import <UIKit/UIKit.h>
@interface ViewController3 : UIViewController
/**
* 定義了一個(gè)changeColor的Block。這個(gè)changeColor必須帶一個(gè)參數(shù),這個(gè)參數(shù)的類型必須為id類型的
* 無返回值
* @param id
*/
typedef void(^changeColor)(id);
/**
* 用上面定義的changeColor聲明一個(gè)Block,聲明的這個(gè)Block必須遵守聲明的要求。
*/
@property (nonatomic, copy) changeColor backgroundColor;
@end
ViewController3.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (self.backgroundColor) {
//聲明一個(gè)顏色
UIColor *color = [UIColor redColor];
//用剛剛聲明的那個(gè)Block去回調(diào)修改上一界面的背景色
self.backgroundColor(color);
}
}
子視圖點(diǎn)擊觸發(fā)回調(diào)
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
BtnView *bt = [[BtnView alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
[self.view addSubview:bt];
bt.flag = @"btnView did tap";
/**
* bt視圖點(diǎn)擊事件觸發(fā)回調(diào)
*
* @param view 當(dāng)前事件響應(yīng)視圖
*
*/
bt.viewTap = ^(UIView *view) {
BtnView *btV = (BtnView*)view;
NSLog(@"%@",btV.flag);
};
// 兩種方法都可以
// [bt didSelected:^{
// NSLog(@"btnView did tap");
// }];
}
BtnView.h
@interface BtnView : UIView
/**
* 視圖標(biāo)記,用來區(qū)分不同的視圖
*/
@property (nonatomic, strong) NSString *flag;
/**
* 定義block
*
* @param id 參數(shù)類型
*/
typedef void(^viewDidSelected)(id);
/**
* 聲明block對象
*/
@property (nonatomic, copy) viewDidSelected viewTap;
/**
* block回調(diào)響應(yīng)方法
*/
//- (void)didSelected:(viewDidSelected)select;
@end
BtnView.m
#import "BtnView.h"
@implementation BtnView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.backgroundColor = [UIColor redColor];
/**
添加手勢
- parameter tapEvent: 手勢響應(yīng)方法
*/
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapEvent:)];
[self addGestureRecognizer:tapGesture];
return self;
}
/**
* 實(shí)現(xiàn)Block
*
*/
- (void)tapEvent:(id)sender {
if (self.viewTap) {
// self.viewTap();
self.viewTap(self);
}
}
/**
* 用對象來保存block防止被銷毀
*/
- (void)didSelected:(viewDidSelected)select {
self.viewTap = select;
}