IOS 淺談閉包block的使用


前言:對於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 其他用法

 

總結: 認清一件事物需要長期不斷的觀察與思考,長路漫漫修遠兮,時刻保持學習心,與君共勉

 

參考文獻:

一篇文章看懂iOS代碼塊Block

block 中使用__weak 和__strong修飾符的問題

iOS中Block的用法,舉例,解析與底層原理(這可能是最詳細的Block解析)

iOS閉包循環引用精講

深入解構iOS的block閉包實現原理 (想看block實現原理推薦看此文獻)

淺談iOS中的閉包

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM