在iOS 4.0之后,block橫空出世,它本身封裝了一段代碼并將這段代碼當做變量,通過block()的方式進行回調。這不免讓我們想到在C函數中,我們可以定義一個指向函數的指針并且調用:
bool?executeSomeTask(void)?{
//do?something?and?return?if?success?or?not
}
bool?(*taskPoint)(void);
taskPoint?=?something;
上面的函數指針可以直接通過(*taskPoint)()的方式調用executeSomeTask這個函數,這樣對比block跟似乎C語言的函數指針是一樣的,但是兩者仍然存在以下區別:
block的代碼是內聯的,效率高于函數調用
block對于外部變量默認是只讀屬性
block被Objective-C看成是對象處理
小結:
1. ?block 是 C 語言的
2. block 是一種匿名函數
3. 是一種數據類型,可以當作參數傳遞
4. 是一組預先準備好的代碼,在需要的時候執行
Block 應用場景:
1. 自定義視圖的反向傳值
2. Modal / POP 控制器的反向傳值
3. 異步方法執行完畢后的反向傳值
注:
1. Block 的反向傳值一般被稱為回調
2. 反向傳遞的樹枝通過 Block 的參數傳遞
Block 的定義和匿名函數
定義 block 的函數體
1. 以^起始,表示是一個block
2. 以{}包裝block中的代碼塊
^ {
NSLog(@"hello block");? ?
};
提示:Expression result unused=> 表達式沒有使用
使用一個變量記錄 block
// block 是一個沒有參數,沒有返回值的匿名函數
// myBlock 是記錄這個匿名函數的變量名
void(^myBlock)() = ^ {
NSLog(@"hello block");? ?
};
提示:Unused variable 'myBlock'=>'myBlock' 沒有使用
使用變量
// block 是一個沒有參數,沒有返回值的匿名函數
// myBlock 是記錄這個匿名函數的變量名
void(^myBlock)() = ^ {
NSLog(@"hello block");? ??
};
// 執行 myBlock 變量中記錄的代碼myBlock();
Block 的定義可以借助inlineBlock速記,但是:
1. 不要過份依賴inlineBlock
2. block的手寫定義一定要過關!
當做參數傳遞 Block
定義一個接收并且執行 block 的方法
// 接收并且執行 block
- (void)callBlock:(void(^)())completion {
NSLog(@"干點什么");? ??
completion();
}
定義 block 并且當做參數傳遞
#pragma mark - Block 當做參數傳遞
- (void)blockDemo2 {
void(^myBlock)() = ^ {
NSLog(@"hello block");? ??
};? ??
[selfcallBlock:myBlock];
}
通過 Block 的參數回調
準備一個方法,模擬回調網絡請求結果
// ?通過 block 的參數回調模擬網絡請求的結果
- (void)callBlock2:(void(^)(NSString*result))completion {
NSLog(@"網絡加載了一點數據");
NSString*jsonString =@"我是網絡加載的 json 字符串";? ??
completion(jsonString);
}
定義 block 并且傳遞參數
#pragma mark - Block 通過參數回調
- (void)blockDemo3 {
// 1. 定義 blockvoid(^myBlock)(NSString*) = ^ (NSString*json) {
NSLog(@"%@", json);
? ? };
// 2. 調用方法
[selfcallBlock2:myBlock];
}
block的反向傳值
來一個小demo:
點擊PUSH按鈕在最右側控制器輸入用戶名
點擊保存按鈕 POP 控制器,并且將用戶名顯示在nameLabel中
項目準備
右側的 DemoViewController
在DemoViewController中定連線屬性
@interface DemoViewController()
@property(weak,nonatomic)IBOutlet UITextField *nameText;
@end
連線方法
/// 保存并返回
- (IBAction)save:(id)sender {
// 將輸入內容傳回
// 導航控制器彈棧
[self.navigationController popViewControllerAnimated:YES];
}
在 .h 中定義屬性
/// 輸入完成 block 回調
@property(nonatomic,copy)void(^inputCompletion)(NSString*userName);
修改 save 方法
// 保存并返回
- (IBAction)save:(id)sender {
// 判斷 block 屬性是否有內容
if(self.inputCompletion != nil) {
// 執行完成回調將輸入內容傳回
self.inputCompletion(_nameText.text);
? ? }
// 導航控制器彈棧
[self.navigationController popViewControllerAnimated:YES];
}
左側的 viewController
導入頭文件
#import"DemoViewController.h"
屬性連線
@property(weak,nonatomic)IBOutlet UILabel*nameLabel;
實現prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
// 1. 獲取目標控制器
DemoViewController *vc = segue.destinationViewController;
// 2. 設置完成回調
[vc setInputCompletion:^(NSString*name) {
self.nameLabel.text= name;
? ? }];
}
與代理的對比
實現步驟的對比
被調用方(子控制器)
代理
定義協議
定義代理屬性
需要的時候通知代理執行協議方法
block
定義 block 屬性
需要的時候執行 block 屬性
調用方
代理
設置代理
遵守協議
實現協議方法
block
設置 block 屬性,準備需要執行的代碼
代碼實現的對比
Block 的優勢
使用 block 所有的代碼寫在一起,更加便于維護和閱讀
使用 代理 代碼想對松散
Block 的弱勢
一個回調對應一個屬性,屬性定義在類的頭文件中,容易和其他屬性混在一起
代理設計模式通過協議預先定義好代理的行為,從設計模式而言,更加嚴謹
尤其在協議方法很多的時候,使用代理更加容易上手!
使用 block 需要注意循環引用!
block 保存的內存區域
不引用任何外部變量的 block
- (void)blockDemo1 {
void(^myBlock)() = ^ {
NSLog(@"hello world");
? ? };
NSLog(@"%@", myBlock);
}
不引用任何外部變量的 block 保存在全局區__NSGlobalBlock__
從而可以保證無論 block 如何被傳遞,內部包裝的代碼都不會發生變化,而且執行效率高
但是實際開發中,不引用外部變量的 block 幾乎是不存在的
引用外部變量的 block
- (void)blockDemo2 {
int i =10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
? ? };
NSLog(@"%@", myBlock);
}
引用外部變量的 block 保存在:
ARC:堆區__NSMallocBlock__
MRC:棧區__NSStackBlock__
因此:在定義block 屬性時應該使用copy關鍵字,將 block 從棧區復制到堆區
定義 block 屬性
/// 定義 block 屬性
@property(nonatomic,copy)void(^demoBlock)();
記錄 block 屬性
- (void)blockDemo2 {
int i =10;
void(^myBlock)() = ^ {
NSLog(@"hello world %zd", i);
? ? };
NSLog(@"%@", myBlock);
// 錯誤的寫法,不會調用 setter 方法
// _demoBlock = myBlock;
// 正確的寫法,調用 setter 方法,并且對 block 進行 copy
self.demoBlock= myBlock;
NSLog(@"%@",self.demoBlock);
}
提示:為了避免程序員的麻煩,在 ARC 中,定義了引用外部變量的 block,默認都是在堆區的!
block的循環引用
定義一個引用self.的 block
@implementation ViewController
- (void)viewDidLoad {
? ? [superviewDidLoad];
void(^block)() = ^ {
NSLog(@"%@",self.view);
? ? };
? ? block();
}
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
@end
運行測試
沒有循環引用:因為 block 執行完畢后會釋放,從而釋放對self的引用
定義屬性
@property(nonatomic,copy)void(^demoBlock)();
在viewDidLoad中記錄 block
- (void)viewDidLoad {
? ? [superviewDidLoad];
void(^block)() = ^ {
NSLog(@"%@",self.view);
? ? };
// 記錄 block,等待需要的時候執行
self.demoBlock= block;
}
運行測試
循環引用發生了:
因為ViewController引用了block屬性
block內部引用了self(ViewController)
相互引用產生了循環引用
解決循環引用
使用__weak修飾符,定義一個弱引用的對象
然后讓block引用此弱引用對象,從而可以打破循環引用
__weak typeof(self) weakSelf =self;
void(^block)() = ^ {
NSLog(@"%@", weakSelf.view);
? ? };
提示:
在使用block的時候,如果出現self,同時使用屬性記錄 block 的時候,要格外注意是否會出現循環引用
注意:不是所有的self.都會出現循環引用 ——block 執行完畢就銷毀,例如 UIView 的動畫代碼
大坑:在block內部不要使用_成員變量!
因為,成員變量默認是由控制器強引用的,以下代碼同樣會出現循環引用,同時非常不容易發現!
@implementation ViewController{
NSMutableArray*_arrayM;
}
- (void)viewDidLoad {
? ? [superviewDidLoad];
? ? __weak typeof(self) weakSelf =self;
void(^block)() = ^ {
NSLog(@"%@ %@", weakSelf.view, _arrayM);
? ? };
// 記錄 block,等待需要的時候執行
self.demoBlock= block;
}