原文連接:www.imlifengfeng.comgit
1、概述github
閉包 = 一個函數「或指向函數的指針」+ 該函數執行的外部的上下文變量「也就是自由變量」;Block 是 Objective-C 對於閉包的實現。網絡
其中,Block:session
使用 clang 將 OC 代碼轉換爲 C++ 文件查看 block 的方法:數據結構
2、Block的定義與使用閉包
一、無參數無返回值框架
//1,無參數,無返回值,聲明和定義 void(^MyBlockOne)(void) = ^(void){ NSLog(@"無參數,無返回值"); }; MyBlockOne();//block的調用
二、有參數無返回值async
//2,有參數,無返回值,聲明和定義 void(^MyblockTwo)(int a) = ^(int a){ NSLog(@"@ = %d我就是block,有參數,無返回值",a); }; MyblockTwo(100);
三、有參數有返回值函數
//3,有參數,有返回值 int(^MyBlockThree)(int,int) = ^(int a,int b){ NSLog(@"%d我就是block,有參數,有返回值",a + b);returna + b; }; MyBlockThree(12,56);
四、無參數有返回值(不多用到)學習
//4,無參數,有返回值 int(^MyblockFour)(void) = ^{NSLog(@"無參數,有返回值"); return45; }; MyblockFour();
五、實際開發中經常使用typedef 定義Block
例如,用typedef定義一個block:
typedef int (^MyBlock)(int , int);
這時,MyBlock就成爲了一種Block類型
在定義類的屬性時能夠這樣:
@property (nonatomic,copy) MyBlock myBlockOne;
使用時:
self.myBlockOne = ^int (int ,int){ //TODO }
3、Block與外界變量
一、截獲自動變量(局部變量)值
(1)默認狀況
對於 block 外的變量引用,block 默認是將其複製到其數據結構中來實現訪問的。也就是說block的自動變量截獲只針對block內部使用的自動變量, 不使用則不截獲, 由於截獲的自動變量會存儲於block的結構體內部, 會致使block體積變大。特別要注意的是默認狀況下block只能訪問不能修改局部變量的值。
[圖片上傳中...(image-73164a-1566284363727-7)]
int age = 10; myBlock block = ^{ NSLog(@"age = %d", age); }; age = 18; block();
輸出結果:
age = 10
(2) __block 修飾的外部變量
對於用 __block 修飾的外部變量引用,block 是複製其引用地址來實現訪問的。block能夠修改__block 修飾的外部變量的值。
[圖片上傳中...(image-d7e15-1566284363727-6)]
__block int age = 10; myBlock block = ^{ NSLog(@"age = %d", age); }; age = 18; block();
輸出爲:
age = 18
爲何使用__block 修飾的外部變量的值就能夠被block修改呢?
咱們使用 clang 將 OC 代碼轉換爲 C++ 文件:
clang -rewrite-objc 源代碼文件名
即可揭開其真正面紗:
__block int val = 10; 轉換成 __Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 };
會發現一個局部變量加上__block修飾符後居然跟block同樣變成了一個__Block_byref_val_0結構體類型的自動變量實例!!!!
此時咱們在block內部訪問val變量則須要經過一個叫__forwarding的成員變量來間接訪問val變量(下面會對__forwarding進行詳解)
4、Block的copy操做
一、Block的存儲域及copy操做
在開始研究Block的copy操做以前,先來思考一下:Block是存儲在棧上仍是堆上呢?
咱們先來看看一個由C/C++/OBJC編譯的程序佔用內存分佈的結構:
[圖片上傳中...(image-2a5d0-1566284363727-5)]
其實,block有三種類型:
這三種block各自的存儲域以下圖:
[圖片上傳中...(image-8a9af1-1566284363727-4)]
簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。
遇到一個Block,咱們怎麼這個Block的存儲位置呢?
(1)Block不訪問外界變量(包括棧中和堆中的變量)
Block 既不在棧又不在堆中,在代碼段中,ARC和MRC下都是如此。此時爲全局塊。
(2)Block訪問外界變量
MRC 環境下:訪問外界變量的 Block 默認存儲棧中。
ARC 環境下:訪問外界變量的 Block 默認存儲在堆中(實際是放在棧區,而後ARC狀況下自動又拷貝到堆區),自動釋放。
ARC下,訪問外界變量的 Block爲何要自動從棧區拷貝到堆區呢?
棧上的Block,若是其所屬的變量做用域結束,該Block就被廢棄,如同通常的自動變量。固然,Block中的__block變量也同時被廢棄。以下圖:
[圖片上傳中...(image-ea535c-1566284363727-3)]
爲了解決棧塊在其變量做用域結束以後被廢棄(釋放)的問題,咱們須要把Block複製到堆中,延長其生命週期。開啓ARC時,大多數狀況下編譯器會恰當地進行判斷是否有須要將Block從棧複製到堆,若是有,自動生成將Block從棧上覆制到堆上的代碼。Block的複製操做執行的是copy實例方法。Block只要調用了copy方法,棧塊就會變成堆塊。
以下圖:
[圖片上傳中...(image-a178b3-1566284363727-2)]
例以下面一個返回值爲Block類型的函數:
typedef int (^blk_t)(int); blk_t func(int rate) { return ^(int count) { return rate * count; }; }
分析可知:上面的函數返回的Block是配置在棧上的,因此返回函數調用方時,Block變量做用域就結束了,Block會被廢棄。但在ARC有效,這種狀況編譯器會自動完成複製。
在非ARC狀況下則須要開發者調用copy方法手動複製,因爲開發中幾乎都是ARC模式,因此手動複製內容再也不過多研究。
將Block從棧上覆制到堆上至關消耗CPU,因此當Block設置在棧上也可以使用時,就不要複製了,由於此時的複製只是在浪費CPU資源。
Block的複製操做執行的是copy實例方法。不一樣類型的Block使用copy方法的效果以下表:
[圖片上傳中...(image-cc36bd-1566284363727-1)]
根據表得知,Block在堆中copy會形成引用計數增長,這與其餘Objective-C對象是同樣的。雖然Block在棧中也是以對象的身份存在,可是棧塊沒有引用計數,由於不須要,咱們都知道棧區的內存由編譯器自動分配釋放。關於堆區和棧區詳細內容能夠參考下峯哥以前的文章:《總結:堆、棧、隊列》
無論Block存儲域在何處,用copy方法複製都不會引發任何問題。在不肯定時調用copy方法便可。
在ARC有效時,屢次調用copy方法徹底沒有問題:
blk = [[[[blk copy] copy] copy] copy]; // 通過屢次複製,變量blk仍然持有Block的強引用,該Block不會被廢棄。
二、__block變量與__forwarding
在copy操做以後,既然__block變量也被copy到堆上去了, 那麼訪問該變量是訪問棧上的仍是堆上的呢?__forwarding 終於要閃亮登場了,以下圖:
[圖片上傳中...(image-16f5c5-1566284363726-0)]
經過__forwarding, 不管是在block中仍是 block外訪問__block變量, 也無論該變量在棧上或堆上, 都能順利地訪問同一個__block變量。
5、防止 Block 循環引用
Block 循環引用的狀況:
某個類將 block 做爲本身的屬性變量,而後該類在 block 的方法體裏面又使用了該類自己,以下:
self.someBlock = ^(Type var){ [self dosomething]; };
解決辦法:
(1)ARC 下:使用 __weak
__weak typeof(self) weakSelf = self; self.someBlock = ^(Type var){ [weakSelf dosomething]; };
(2)MRC 下:使用 __block
__block typeof(self) blockSelf = self; self.someBlock = ^(Type var){ [blockSelf dosomething]; };
值得注意的是,在ARC下,使用 __block 也有可能帶來的循環引用,以下:
// 循環引用 self -> _attributBlock -> tmp -> self typedef void (^Block)(); @interface TestObj : NSObject { Block _attributBlock; } @end @implementation TestObj - (id)init { self = [super init]; __block id tmp = self; self.attributBlock = ^{ NSLog(@"Self = %@",tmp); tmp = nil; }; } - (void)execBlock { self.attributBlock(); } @end // 使用類 id obj = [[TestObj alloc] init]; [obj execBlock]; // 若是不調用此方法,tmp 永遠不會置 nil,內存泄露會一直在
6、Block的使用示例
一、Block做爲變量(Xcode快捷鍵:inlineBlock)
int (^sum) (int, int); // 定義一個 Block 變量 sum // 給 Block 變量賦值 // 通常 返回值省略:sum = ^(int a,int b)… sum = ^int (int a,int b){ return a+b; }; // 賦值語句最後有 分號 int a = sum(10,20); // 調用 Block 變量
二、Block做爲屬性(Xcode 快捷鍵:typedefBlock)
// 1\. 給 Calculate 類型 sum變量 賦值「下定義」 typedef int (^Calculate)(int, int); // calculate就是類型名 Calculate sum = ^(int a,int b){ return a+b; }; int a = sum(10,20); // 調用 sum變量 // 2\. 做爲對象的屬性聲明,copy 後 block 會轉移到堆中和對象一塊兒 @property (nonatomic, copy) Calculate sum; // 使用 typedef @property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef // 聲明,類外 self.sum = ^(int a,int b){ return a+b; }; // 調用,類內 int a = self.sum(10,20);
三、做爲 OC 中的方法參數
// ---- 無參數傳遞的 Block --------------------------- // 實現 - (CGFloat)testTimeConsume:(void(^)())middleBlock { // 執行前記錄下當前的時間 CFTimeInterval startTime = CACurrentMediaTime(); middleBlock(); // 執行後記錄下當前的時間 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - startTime; } // 調用 [self testTimeConsume:^{ // 放入 block 中的代碼 }]; // ---- 有參數傳遞的 Block --------------------------- // 實現 - (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock { // 執行前記錄下當前的時間 CFTimeInterval startTime = CACurrentMediaTime(); NSString *name = @"有參數"; middleBlock(name); // 執行後記錄下當前的時間 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - startTime; } // 調用 [self testTimeConsume:^(NSString *name) { // 放入 block 中的代碼,可使用參數 name // 參數 name 是實現代碼中傳入的,在調用時只能使用,不能傳值 }];
四、Block回調
Block回調是關於Block最經常使用的內容,好比網絡下載,咱們能夠用Block實現下載成功與失敗的反饋。開發者在block沒發佈前,實現回調基本都是經過代理的方式進行的,好比負責網絡請求的原生類NSURLConnection類,經過多個協議方法實現請求中的事件處理。而在最新的環境下,使用的NSURLSession已經採用block的方式處理任務請求了。各類第三方網絡請求框架也都在使用block進行回調處理。這種轉變很大一部分緣由在於block使用簡單,邏輯清晰,靈活等緣由。
以下:
//DownloadManager.h #import <Foundation/Foundation.h> @interface DownloadManager : NSObject <NSURLSessionDownloadDelegate> // block 重命名 typedef void (^DownloadHandler)(NSData * receiveData, NSError * error); - (void)downloadWithURL:(NSString *)URL parameters:(NSDictionary *)parameters handler:(DownloadHandler)handler ; @end
//DownloadManager.m #import "DownloadManager.h" @implementation DownloadManager - (void)downloadWithURL:(NSString *)URL parameters:(NSDictionary *)parameters handler:(DownloadHandler)handler { NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:URL]]; NSURLSession * session = [NSURLSession sharedSession]; //執行請求任務 NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(data,error); }); } }]; [task resume]; }
上面經過封裝NSURLSession的請求,傳入一個處理請求結果的block對象,就會自動將請求任務放到工做線程中執行實現,咱們在網絡請求邏輯的代碼中調用以下:
- (IBAction)buttonClicked:(id)sender { #define DOWNLOADURL @"https://codeload.github.com/AFNetworking/AFNetworking/zip/master" //下載類 DownloadManager * downloadManager = [[DownloadManager alloc] init]; [downloadManager downloadWithURL: DOWNLOADURL parameters:nil handler:^(NSData *receiveData, NSError *error) { if (error) { NSLog(@"下載失敗:%@",error); }else { NSLog(@"下載成功,%@",receiveData); } }]; }
爲了加深理解,再來一個簡單的小例子:
A,B兩個界面,A界面中有一個label,一個buttonA。點擊buttonA進入B界面,B界面中有一個UITextfield和一個buttonB,點擊buttonB退出B界面並將B界面中UITextfield的值傳到A界面中的label。
A界面中,也就是ViewController類中:
//關鍵demo: - (IBAction)buttonAction { MyFirstViewController *myVC = [[MyFirstViewController alloc] init]; [self presentViewController:myVC animated:YES completion:^{ }]; __weak typeof(self) weakSelf = self;//防止循環引用 //用屬性定義的注意:這裏屬性是不會自動補全的,方法就會自動補全 [myVC setBlock:^(NSString *string){ weakSelf.labelA.text = string; }]; }
B界面中,也就是MyFirstViewController類中.m文件:
- (IBAction)buttonBAction { [self dismissViewControllerAnimated:YES completion:^{ }]; self.block(_myTextfielf.text); }
.h文件:
#import <UIKit/UIKit.h> //typedef定義一下block,爲了更好用 typedef void(^MyBlock)(NSString *string); @interface MyFirstViewController : UIViewController @property (nonatomic, copy) MyBlock block; @end
看了以上兩個Block回調示例,是否是感受比delegate清爽了很多?