iOS block 的底層實現

 其實swift 的閉包跟 OC的block 是同樣同樣的,學會了block,你swift裏邊的閉包就會無師自通。html

 

參考:http://www.jianshu.com/p/e23078c11518c++

         http://www.360doc.com/content/15/0901/11/10504424_496203197.shtmlweb

先來簡單介紹一下Block
Block是什麼?蘋果推薦的類型,效率高,在運行中保存代碼。用來封裝和保存代碼,有點像函數,Block能夠在任什麼時候候執行。swift

Block和函數的類似性:(1)能夠保存代碼(2)有返回值(3)有形參(4)調用方式同樣。多線程

Block 底層實現

定義一個簡單的block閉包


咱們再給a賦值爲20,此時打印出來a 的值仍是10函數



但當咱們在第一次給a 賦值時,前面加上__block 的時候,則打印出來20。ui



那麼爲何加上__block 後 就打印出20了呢,這個原理是什麼呢?google

其實能夠用兩個詞來歸納:傳值 和傳址。 可能這樣說你們以爲有點扯,接下來 用C++ 代碼進行編譯。
打開終端作以下操做 在當前文件夾下會獲得一個.cpp 文件。atom



此時打開當前的.cpp 文件(會有差很少10萬行代碼),前面咱們都忽略,只須要滾動到最後,此時你會發現block跟OC中的變化。


接下來咱們一個個來看這個block,先來看等號左邊的。

void(*block)()

這是一個沒有參數沒有返回值的函數指針,既然是一個函數指針,那它就是一個變量,變量裏面只能保存函數地址,而後它又在等號的左邊是否是意味着右邊返回的是一個函數地址(本身推斷)。

再看等號右邊:

((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
  • 參數(自我推斷):

    • ((void (*)()) 強轉(本身理解其實沒有實際含義,不影響本身自己的類型)
    • & 取址 後面都是函數的調用,若是不是也不會獲得一個函數指針的。
    • __main_block_impl_0 這是一個函數名,這個函數有三個參數, com+F 搜索一下,又會發現這是一個結構體,結構體以下:

      struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a;

      可能你會疑惑,剛剛說這是一個函數,而如今是一個結構體。其實在 c++ 裏面結構體至關於OC的類,c++ 裏面結構體擁有本身的屬性以及構造方法和方法。那麼爲何取一個結構體的地址呢? 其實它取得是下面這段代碼的地址:

      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }

      那麼在上面個方法實現裏,又有四個參數。而在剛剛調用的時候只有三個參數,多了一個參數 flags= 0,這個參數其實就至關於Swift中指定了一個默認值,不傳也有值,能夠忽略。那麼後面繼續:

    • a(_a) : 在 c++ 裏面 指定_a(形參) 未來賦值給a 這個實參,也就是這個__main_block_impl_0 結構體中的 int a;在這裏 int a = 10;
    • impl.FuncPtr = fp; 將fp賦值給了 impl 結構體的 FuncPtr 參數, 在這個參數裏面存放的是下面這段代碼的地址:

      static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // 這裏 int a = 10; printf("%d\\\\\\\\n",a); // 打印出a }
    • __main_block_desc_0_DATA com+ F 搜索 定義的就是與大小相關的信息,代碼以下:
      static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    • a 直接放a 其實就至關於把a 當前的值拿過來,若是是&a, 就是a的地址。請看下圖:

接下來,又從新給 a賦值爲 20,可是Block 最終要找到 FuncPtr 裏面存放的是值來執行, 在這裏纔會最終執行打印a 的值的代碼,可是這段代碼裏 a 是 10 了。因此最終打印的仍是10。


最後能夠歸納爲block 底層實現 分兩種:剛剛上面的就是第一種(不加__block), 會建立一個結構體,實現構造方法,來接收三個參數。

接下來看加上__block 的實現。
修改咱們的代碼:



再次在終端裏面進行編譯,你會發現生成的結構體會變化。


等號左邊會封裝一個__Block_byref_a_0 結構體類型的變量a,下面是結構體的聲明:

truct __Block_byref_a_0 {
    void *__isa; //isa 類型的指針 本身的類型 __Block_byref_a_0 *__forwarding; //與本身結構體同名,是一個本身類型的結構體的指針,存放的是本身的地址 int __flags; // 標記 int __size; // 類型的大小 int a; // a 屬性 保存變量的值 };

等號右邊:

{(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
  • 參數:
    • (void*)0 : 一個指針直接存到isa裏面
    • (__Block_byref_a_0 *)&a: 強轉 存放的是本身的地址
    • 0 : 會傳給 flags
    • sizeof(__Block_byref_a_0), 10: 類型的大小
    • 10: a 的值, 僅僅是建立。

這裏僅僅是建立,由於使用了__block 因此建立了一個block 類型的結構體,接下來會纔是調用block,你會發現其他參數和第一種實現都同樣,惟一不一樣的是再去取值的時候,拿到的是結構體的地址,只要把地址傳遞過去,就有了最高的操做權限,到時候再去取值就能夠取到內存中最新的值。


接下來(a.__forwarding->a) = 20; 這句代碼是拿到結構體裏面的地址去修改a的值爲20。

後面再去打印,打印的就是內存地址中最新的值,因此就是20

 

========================================================

 

1、關於block的循環引用:

block屬性,通常用copy修飾;

1.1.若是沒有對block進行copy操做,block就存儲於棧空間

1.2.若是對block進行copy操做,block就存儲於堆空間---強引用

1.3.若是block存儲於棧空間,不會對block內部所用到的對象產生強引用

1.4.若是block存儲於堆空間,就會對block內部所用到的對象產生強引用

注意1:因爲使用了copy修飾,若是block中調用了block屬性的對象,就會形成循環引用

  爲了不循環引用,須要對對象進行若引用修飾:

複製代碼
1 ICKPerson *p = [[ICKPerson alloc] init]; 2 // 一、修飾方法1 3 // __unsafe_unretained typeof(p) weakP = p; 4 // 二、修飾方法2 5 __block typeof(p) weakP = p; 6 p.testBlock = ^{ 7  [weakP run]; 8 };
複製代碼

 

2、關於block中變量的值:

2.1  若是變量沒有經過__block修飾,那麼block中的變量本質是值捕獲,在建立block的同時,是將變量的值傳入到block,不管何時調用,變量的值就是最初傳進去的值

1  int age = 10; 2 void (^block)() = ^{ // 值捕獲 3 NSLog(@"age=%d", age);// 打印是10; 4  }; 5 age = 20; 6 block();

 

2.2  若是變量經過__block修飾,那麼block中的變量實際傳遞的是變量的地址,在建立block的同時,是將變量的地址傳入到block,在調用block的時候,其變量的值是當時變量的值(經過地址(指針)獲取到)。

1 __block int age = 12 void (^block)() = ^{ // 值捕獲 3 NSLog(@"age=%d", age);// 打印是20; 4  }; 5 age = 20; 6 block();

3、關於block的內部實現:

        建立block的時候,內部是建立了對應的函數;

        在調用block的時候,是調用了以前封裝的函數。

 

4、關於block的應用:

 4.1.如何定義block

複製代碼
 1  一、// inline  2 // blockName:block變量名  3 // 返回值類型(^變量名)(返回值類型)  4 <#returnType#>(^blockName)(<#parameterTypes#>) = ^(<#parameters#>) {  5 <#statements#>  6  };  7 void(^block)() = ^(){  8 NSLog(@"block");  9  }; 10 二、// name:Block類型別名 11 typedef void(^MyBlock)() 12 MyBlock myBlock = ^(){ 13 };
複製代碼

   4.2、調用block

1  block(); 2 myBlock();

 

  4.3、實戰練習:

    // 4.通信錄Block使用:

    // 點擊保存,通知聯繫人刷新表格,用代理

    // block:小弟 代理:打電話

    // block:先把刷新表格的代碼保存起來

    // 等用戶點擊了保存按鈕的時候,調用Block

4.3.1、在頭文件中(向其餘文件中傳遞數據的文件)定義一個block:是否帶參數,根據需求肯定

1 @class ICKAddViewController,ICKContact; 2 typedef void(^ICKAddViewControllerBlock)(ICKContact *contact); 3 @interface ICKAddViewController : UIViewController 4 @property (nonatomic, strong) ICKAddViewControllerBlock contactBlock; 5 @end

4.3.2、在獲取數據後,跳轉頁面以前,調用block,將數據傳遞過去

複製代碼
1 - (IBAction)addcontact { 2 ICKContact *contact = [ICKContact contactWithName:self.nameFiled.text andPhone:self.phoneFiled.text]; 3 // 調用block 4 if (self.contactBlock) { 5  self.contactBlock(contact); 6  } 7  [self.navigationController popViewControllerAnimated:YES]; 8 }
複製代碼

4.3.3、在獲取(保存、利用)數據的文件中(拿到獲取數據的對象的時候)調用其block屬性,保存block代碼段(實現特定功能的代碼)

複製代碼
 1 // 跳轉控制器時數據傳遞  2 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{  3 ICKAddViewController *addVc = segue.destinationViewController;  4 // 聲明block  5 addVc.contactBlock = ^(ICKContact *contact){  6  [self.contacts addObject:contact];  7  8 // 存儲數據  9 NSString *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; 10 NSString *path = [cache stringByAppendingString:@"contacts.data"]; 11  [NSKeyedArchiver archiveRootObject:self.contacts toFile:path]; 12  [self.tableView reloadData]; 13  }; 14 }

 =====================================================================

文章末尾說一下block 析構的狀況處理。。

 

四、使用方將self或成員變量加入block以前要先將self變爲__weak

五、在多線程環境下(block中的weakSelf有可能被析構的狀況下),須要先將self轉爲strong指針,避免在運行到某個關鍵步驟時self對象被析構。

第4、第五條合起來有個名詞叫weak–strong dance,來自於2011 WWDC Session #322 (Objective-C Advancements in Depth)

如下代碼來自AFNetworking,堪稱使用weak–strong dance的經典。

複製代碼
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } };
複製代碼

 

 

 

 



文/Liwjing(簡書做者) 原文連接:http://www.jianshu.com/p/e23078c11518 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索