前言:對於ios初學者,block通常用於逆向傳值,遍歷等,會使用,但是可能心虛,會感覺block很神秘,那么下面就一起來揭開它的面紗吧。
ps: 下面重點講敘了閉包的概念,常用的語法,以及訪問變量,循環引用問題,至於底層的運行,堆棧block的區別,還有其他用法這里就不介紹了,目前也處於迷糊中,等到真正理解了再來補充 - -。
一. 概念
1. 什么是閉包?
閉包就是能夠讀取其他函數內部變量的函數,可以理解成“定義在一個函數內部的函數“。
在本質上,閉包是將函數內部和函數外部連接起來的橋梁。
閉包在很多語言中都有應用,C,JAVA,OC等
2. OC中的Block
在OC中,Block是在iOS4開始引入,是對C語言的擴展,被用來實現匿名函數的特性
Block是一種特殊的數據類型,可以正常定義變量、作為參數、作為返回值
特殊地,Block還可以聲明賦值去保存一段代碼,在需要調用的地方去調用
目前Block已經廣泛應用於各類回調傳值、排序遍歷、GCD、動畫等
二. 基本語法
1. block做自由變量 - 聲明、賦值以及調用 (個人感覺理解語法就好)
1 // 聲明一個名字為 TestBlockTest 的無返回值含有參數的變量 2 void (^TestBlockTest)(NSString *); 3 // 只聲明變量,需要賦值 4 TestBlockTest = ^(NSString *parameter){ 5 NSLog(@"測試"); 6 }; 7 // 調用 8 TestBlockTest(@""); 9 10 // 聲明TestTwoBlock變量同時賦值 11 int (^TestTwoBlock)(int) = ^(int num){ 12 return num*8; 13 }; 14 // 已經聲明了blcok並賦值了 ,可以直接調用 15 int num = TestTwoBlock(8); 16 NSLog(@"%d",num);
注意:
^ 這個叫做 脫字符,其中,返回值類型,參數列表可以省略簡寫,這個用多了就知道了,開始推薦寫全,基礎要扎實
第一個輸出值 測試,沒有用到參數parameter,強迫症大神請見諒哈!第二個輸出值 64
Block的聲明與賦值只是保存了一段代碼段,必須 調用 才能執行內部代碼
2. 使用typedef定義Block類型
可做屬性,可做參數、返回值類型等 (這個用法務必掌握)
示例代碼背景:A頁面點擊按鈕 跳轉 B頁面,B頁面返回A頁面時候,傳值@“測試”,用於修改A頁面按鈕名字
2.1 .h中定義
1 #import <UIKit/UIKit.h> 2 3 // typedef 定義無返回值,有一個參數,名字為TestBlock的block類型 4 typedef void(^TestBlock)(NSString *); 5 6 @interface ViewController : UIViewController 7 8 // 做屬性 9 @property (nonatomic, copy) TestBlock testBolck; 10 11 // 做方法參數 12 - (void)returnText:(TestBlock)block; 13 14 @end
2.2 .m中用法,實現(下面整合了做屬性,做參數的代碼)
1 // B頁面 2 3 // 做屬性 4 TestBlock blockVar = ^(NSString *parameterTwo){ 5 NSLog(@"hello world %@",parameterTwo); 6 }; 7 /* 8 * 我們可能需要重復地聲明多個相同返回值相同參數列表的Block變量 9 * 如果總是重復地編寫一長串代碼來聲明變量會非常繁瑣 10 * 所以我們可以使用typedef來定義Block類型 11 * 然后像OC中聲明變量一樣使用Block類型 TestBlock 來聲明變量 12 **/ 13 blockVar(@"UZI"); 14 blockVar(@"");
15 // 實現定義的方法 16 - (void)returnText:(TestBlock)block{ 17 self.testBolck = block; 18 }
19 // 這里選擇返回傳參數,用法很多,看個人喜好 20 - (void)viewWillDisappear:(BOOL)animated{ 21 self.testBolck(@"測試"); 22 }
23 // A頁面 在頁面跳轉部分 24 // 做屬性回調 25 __weak __typeof(self) weakSelf = self; 26 vc.testBolck = ^(NSString *parameter) { 27 [weakSelf setBtnTitle:parameter]; 28 }; 29 30 // 做參數回調 31 [vc returnText:^(NSString *parameter) { 32 [self setBtnTitle:parameter]; 33 }];
34 // 做參數另一種寫法 35 __weak __typeof(self) weakSelf = self; 36 [vc returnText:^(NSString *parameter) { 37 __strong __typeof(weakSelf) strongSelf = weakSelf; 38 // 這里用strong保證self不被釋放,詳見下文 循環引用weak,strong修飾問題 39 [strongSelf setBtnTitle:parameter]; 40 }];
注意:
上面選擇在B頁面返回A頁面時候傳遞參數,傳遞給A,A頁面分別用了block做屬性,做參數是怎么完成逆向傳值的,方式很多,憑自己喜好
三. block訪問變量問題
這里不一一代碼舉例,個人感覺看總結,主要有以下四點,記住就好
1. Block擁有捕獲外部變量的功能,在Block中訪問一個外部的局部變量,Block會持用它的臨時狀態,自動捕獲變量值,外部局部變量的變化不會影響它的的狀態,可以理解為瞬間性捕獲。
2. 在block中,可以訪問局部變量(自由變量),但是不能修改局部變量,因為:block捕獲的是自動變量的const值,名字一樣,不能修改
3. 可以訪問靜態變量,並修改變量的值,靜態變量屬於類的,不是某一個變量,因此block不用調用self指針,所以block可以修改值
4. 使用__block修飾符的局部變量,可以修改局部變量的值。包括可變類型的參數,也可以修改,這個可以用clang命令將OC轉為C++代碼來查看一下Block底層實現
四: 循環引用 __weak __strong 修飾問題
很多初學者對這塊都是模糊的,只知道加上 __weak __typeof(self) weakSelf = self 這句,弱引用,可以防止循環引用。
那什么是循環引用?
簡單理解為 相互持有強引用,造成block內所持有的對象無法釋放,引起內存泄漏
當然,造成循環引用不唯一,好比對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么也會造成循環引用
下面分三點來談論:
1. 如果相互持有強引用,即對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環引用。
解決辦法是使用一個弱引用的指針指向該對象,然后在Block內部使用該弱引用指針來進行操作,這樣引用計數不加1,避免了Block對對象進行強引用。
通常是這樣: __weak __typeof(self) weakSelf = self
注意: 以上是在ARC情況下,如果MRC中,可以在 會引起相互持有的對象 前面,使用 __block 修飾,原理是可以禁止block對對象進行retain操作,引用計數不會加1,從而解決循環引用問題。
這種循環引用示例(部分代碼,提供思路):
1 // 定義一個block 2 typedef void(^HYBFeedbackBlock)(id model); 3 // 聲明一個對象 4 @property (nonatomic, strong) HYBAView *aView; 5 // block做方法參數 6 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 7 // 構造方法 8 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 9 if (self = [super init]) { 10 self.block = block; 11 return self; 12 } 13 // 調用 14 self.aView = [[HYBAView alloc] initWithBlock:^(id model) { 15 // 假設要更新model 16 self.currentModel = model; 17 }];
上面代碼很容易看出所形成的環:
vc->aView->block->vc(self)
vc->aView->block->vc.currentModel
2. 對於上面的那種情況,為消除循環引用,而用弱引用
雖說使用__weak,但是此處會有一個隱患,你不知道 block內的 self 什么時候會被釋放,
為了保證在block內不會被釋放,我們添加__strong,更多的時候需要配合strongSelf使用
1 // 上面講到做方法參數時候的一種寫法 2 __weak __typeof(self) weakSelf = self; 3 [vc returnText:^(NSString *parameter) { 4 __strong __typeof(weakSelf) strongSelf = weakSelf; 5 [strongSelf setBtnTitle:parameter]; 6 }];
可能有人問,用strong,那什么時候才釋放呢?
用修飾符strong時,當外部把變量/對象釋放掉,但block如果沒有執行結束,那么系統就會等待block執行完成后再釋放,
對該變量/對象在block中的使用起到了保護作用,當block執行結束后會自動釋放掉(ARC)。
不過若無強烈需求,不建議在Block里加strong,容易占用內存,造成內存消耗
3. 是不是所有的block都要用弱引用呢?
不是,如果沒有相互直接引用,可以放心大膽的不用__weak
並不是block就一定會造成循環引用,如果不是相互持有,可以不用__weak 去弱引用
最經典的示例: Masonry代碼布局
1 [self.headView mas_makeConstraints:^(MASConstraintMaker *make) { 2 3 make.centerY.equalTo(self.otherView.mas_centerY); 4 }];
* block里用到了self,block會保持一個對self的引用,但是self並沒有直接或者間接持有block,所以不會造成循環引用
形成的持有鏈:
self ->self.headView ··· MASConstraintMaker構造block->self
五. 感興趣的可以繼續了解下
* block 與內存管理,堆棧block等
* block 底層實現
* block 其他用法
總結: 認清一件事物需要長期不斷的觀察與思考,長路漫漫修遠兮,時刻保持學習心,與君共勉
參考文獻:
block 中使用__weak 和__strong修飾符的問題
iOS中Block的用法,舉例,解析與底層原理(這可能是最詳細的Block解析)
深入解構iOS的block閉包實現原理 (想看block實現原理推薦看此文獻)