iOS Block探究

weixin_34208185發表於2018-07-31

問題: 1.Block是什麼,block當初是為了解決什麼樣的問題而設計的? 2.為什麼要用copy修飾Block 3.為什麼Block中可以訪問區域性變數而不能修改 4.為什麼用__block修飾的區域性變數可以在Block內部修改 5.為什麼使用Block會造成迴圈引用,怎麼解決 6.Block,delegate的使用場景

####1.Block是什麼,為什麼會出現Block這種技術


用一句話來概括就是帶有自動變數的匿名函式。 block 本質是一個函式指標加上一個對應捕獲上下文變數的記憶體塊(結構體或者類)

block的定義如下

struct Block_descriptor {
    unsigned long int reserved; 
    unsigned long int size; 
    void (*copy)(void *dst, void *src); 
    void (*dispose)(void *);
};

struct Block_layout { 
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(void *, ...); 
    struct Block_descriptor *descriptor; 
    /* Imported variables. */
};
複製程式碼

一個 block 例項實際上由 6 部分構成:

1. isa 指標,所有物件都有該指標,用於實現物件相關的功能。
2. flags,用於按 bit 位表示一些 block 的附加資訊
3. reserved,保留變數。
4. invoke,函式指標,指向具體的 block 實現的函式呼叫地址。
5. descriptor, 表示該 block 的附加描述資訊,主要是 size 大小,以及 copy 和 dispose 函式的指標。
6. variables,capture 過來的變數,block 能夠訪問它外部的區域性變數,就是因為將這些變數(或變數的地址)複製到了結構體中。
複製程式碼

根據isa指標,block一共有3種型別的block _NSConcreteGlobalBlock 全域性靜態 _NSConcreteStackBlock 儲存在棧中,出函式作用域就銷燬 _NSConcreteMallocBlock 儲存在堆中,retainCount == 0銷燬

######block當初是為了解決什麼樣的問題而設計的?

答案1. block的提出是為了解決在不同的物件間除了傳遞值之外還可以傳遞一個操作而提出的。 答案2. block的設計是為了解決方法中方法可做為引數傳入當前上下文中,將方法具體的實現進行抽象,一種策略設計模式的具體表現。

####2.為什麼要用copy修飾Block


為什麼要用copy,這是因為在MRC時期,作為屬性的block在初始化時是被存放在靜態區的,這樣在使用時如果block內有呼叫外部變數,那麼block無法保留其記憶體,在初始化的作用域內使用並不會有什麼影響,但一但出了block的初始化作用域,就會引起崩潰,使用copy可以將block的記憶體推入堆中,這樣讓其擁有儲存呼叫的外部變數的記憶體的能力。 大部分人都認為block作為屬性在宣告時,只能用copy修飾,而不可以用strong。其實用strong也是可以的。只是使用copy修飾block是MRC時期的遺留物,這在MRC時期是至關重要的事情,但是使用ARC的現在,strong是可以代替的,只是一個習慣問題而已。

####3.為什麼Block中可以訪問區域性變數而不能修改


例子如下:

注: 通過clang命令將OC轉為C++程式碼來檢視一下Block底層實現,clang命令使用方式為終端使用cd定位到main.m檔案所在資料夾,然後利用clang -rewrite-objc main.m將OC轉為C++,成功後在main.m同目錄下會生成一個main.cpp檔案

由此可知,在Block定義時便是將區域性變數的值傳給Block變數所指向的結構體,因此在呼叫Block之前對區域性變數進行修改並不會影響Block內部的值,同時內部的值也是不可修改的

####4.為什麼用__block修飾的區域性變數可以在Block內部修改


新增__block修飾符之後的程式碼如下:

可以看到並不是直接傳遞a的值了,而是把a的地址傳過去了,所以在block內部便可以修改到外面的變數了。

####5.為什麼使用Block會造成迴圈引用,怎麼解決


因為物件obj在Block被copy到堆上的時候自動retain了一次。因為Block不知道obj什麼時候被釋放,為了不在Block使用obj前被釋放,Block retain了obj一次,在Block被釋放的時候,obj被release一次。

迴圈引用問題的根源在於Block和obj可能會互相強引用,互相retain對方,這樣就導致了迴圈引用,最後這個Block和obj就變成了孤島,誰也釋放不了誰。

解決辦法: 在ARC中沒有retain,retainCount的概念。只有強引用和弱引用的概念。當一個變數沒有__strong的指標指向它時,就會被系統釋放。

####6.Block,delegate的使用場景


通常情況應該優先使用 block。而有兩個情況可以考慮 delegate。 **1. **有多個相關方法。假如每個方法都設定一個 block, 這樣會更麻煩。而 delegate 讓多個方法分成一組,只需要設定一次,就可以多次回撥。當多於 3 個方法時就應該優先採用 delegate。 比如一個網路類,假如只有成功和失敗兩種情況,每個方法可以設計成單獨 block。但假如存在多個方法,比如有成功、失敗、快取、https 驗證,網路進度等等,這種情況下,delegate 就要比 block 要好。

**2. **為了避免迴圈引用,也可以使用 delegate。使用 block 時稍微不注意就形成迴圈引用,導致物件釋放不了。這種迴圈引用,一旦出現就比較難檢查出來。而 delegate 的方法是分離開的,並不會引用上下文,因此會更安全些。假如寫一個庫供他人使用,不清楚使用者的水平如何。這時為防止誤用,寧願麻煩一些,笨一些,使用 delegate 來替代 block。

相關文章