關於block的那些事(參考大神部落格 自己想法)

2021-07-15 21:30:54 字數 4159 閱讀 5371

塊可以實現閉包。這項語言特性是作為「擴充套件」而加入gcc編譯器中的,從技術上講,這是個c語言層面的特性,因此,只要有支援此特性的編譯器,以及能執行塊的執行期元件,就可以在c、c++,oc,oc++**中使用

塊的基本知識

塊與函式類似,只不過是直接定義在另乙個函式裡的,和定義它的那個函式共享乙個範圍內的東西。塊用「^」符號來表示,後面跟著一對花括號,括號裡面是塊的實現**,例如,下面就是乙個簡單的塊:

^塊其實就是乙個值,而且有其相關型別。與int、float或者oc物件一樣,也可以吧塊賦值給變數,然後像使用其他變數那樣使用它。塊型別的語法與函式指標近似。下面列出的這個塊很簡單,沒有引數,也不返回值:

void (^someblock)() = ^

下面就是有兩個引數並且有返回值的:

int (^addblock)(int a,int b) = ^(int a,int b);

我們可以像使用c語言的函式一樣使用:

int add = addblock(4,5);

塊的強大之處是:在宣告它的範圍內,所有的變數都可以為其所捕獲。也就是說,那個範圍的全部變數,在塊裡依然可用,比如,下面這段**所定義的快,就使用了塊外的變數:

int addition = 6;

int (^addblock)(int a,int b) = ^(int a,int b);

int add = addblock(4,5);

我們可以列印下記憶體位址來進行驗證:

__block int a = 0;

nslog(@"定義前:%p", &a);        //棧區

void (^foo)(void) = ^;

nslog(@"定義後:%p", &a);        //堆區

foo();

2016-05-17 02:03:33.559 leancloudchatkit-ios[1505:713679] 定義前:0x16fda86f8

2016-05-17 02:03:33.559 leancloudchatkit-ios[1505:713679] 定義後:0x155b22fc8

2016-05-17 02:03:33.559 leancloudchatkit-ios[1505:713679] block內部: 0x155b22fc8

「定義後」和「block內部」兩者的記憶體位址是一樣的,我們都知道 block 內部的變數會被 copy 到堆區,「block內部」列印的是堆位址,因而也就可以知道,「定義後」列印的也是堆的位址。

那麼如何證明「block內部」列印的是堆位址?

把三個16進製制的記憶體位址轉成10進製就是:

定義後前:6171559672

block內部:5732708296

定義後後:5732708296

中間相差438851376個位元組,也就是 418.5m 的空間,因為堆位址要小於棧位址,又因為ios中乙個程序的棧區記憶體只有1m,mac也只有8m,顯然a已經是在堆區了。

這也證實了:a 在定義前是棧區,但只要進入了 block 區域,就變成了堆區。這才是 __block 關鍵字的真正作用。

理解到這是因為堆疊位址的變更,而非所謂的「寫操作生效」,這一點至關重要,要不然你如何解釋下面這個現象:

以下**編譯可以通過,並且在block中成功將a的從tom修改為jerry。

nsmutablestring *a = [nsmutablestring stringwithstring:@"tom"];

nslog(@"\n 定以前:------------------------------------\n\

void (^foo)(void) = ^;

foo();

nslog(@"\n 定以後:------------------------------------\n\

定以前:------------------------------------

block內部:------------------------------------

定以後:------------------------------------

我們還能經常看到「內聯塊」 的用法:

nsarray *array = [nsarray array];

[array enumerateobjectsusingblock:^(id  _nonnull obj, nsuinteger idx, bool * _nonnull stop) ];

這種常見的編碼習慣也是可以看出來塊為何如此有用。在oc中引入塊這個特性之前,想要編出同樣功能的**,就必須傳入函式指標或者是選擇子的名稱,這樣就會再寫幾行**了,而且還會令方法變得有些鬆散,與之相反,若宣告內聯形式的塊,就能把業務邏輯都放在一起了。

如果塊所捕獲的變數就是物件型別,那麼就睡自動儲存它,系統在釋放這個塊的時候,也會將其一並釋放。這就引出了乙個與塊有關的問題。塊本身可視為物件。實際上,在其他oc物件所能響應的選擇子中,有很多是塊也可以響應的。而最重要之處則在於,塊本身也和其他物件一樣有引用計數。當最後乙個指向塊的引用移走之後,塊就**了。**時也會釋放塊所捕獲的變數,以便平衡捕獲是所執行的保留操作。

如果將塊定義在oc類的例項方法中,那麼除了可以訪問類的所有例項變數之外,還可以使用self變數。塊總能修改例項變數,所以在宣告時無需加__block。不過,如果通過讀取或者寫入操作捕獲了例項變數,那麼也會自動把self變數一併捕獲了,因為例項變數與self所指代的例項關聯在一起。

@property (nonatomic,copy) nsstring *value;

@end

@implementation viewcontroller

- (void)viewdidload;}

@end

在這種情況下,self變數就指向此block。由於在塊內沒有明確的使用self變數,所以很容易忘記self變數其實以為塊所捕獲了。直接訪問例項變數和通過self來訪問是等效的。然而一定要記住:self也是乙個物件,因而塊在捕獲它時也會將其保留。如果self所指代的那個物件同時保留了塊,那麼就會出現保留環,在這樣的情況下,我們一般會採用weak-strong dance方法(在arc的情況下)來解決這個問題。

每個oc物件都佔據著某個記憶體區域。因為例項變數的個數及物件所包含的關聯資料互不相同,所以每個物件所佔的記憶體區域也有大小之分。塊本身也就是物件,在存放塊物件記憶體區域中,首個變數是指向class物件的指標,該指標叫做isa,其他結構如圖。

在記憶體布局中,最重要的就是invoke變數,這是乙個函式指標,指向塊的實現**。函式原型至少要接受乙個void*型 的引數,此引數代表塊。descriptor變數是指向結構體指標,每個塊裡都包含這個結構體,其中宣告了塊物件的總體大小,還宣告了copy與dispose這兩個輔助函式所對應的函式指標。輔助函式在拷貝及丟棄物件時執行,其中會執行一些操作,比方說,前者要保留捕獲的物件,後者將之釋放塊還會把它所捕獲的所有變數都拷貝乙份。這些拷貝放在descriptor變數後面,捕獲了多少個變數,就要佔據多少記憶體空間,請注意,拷貝並不是物件的本身,而是指向這些物件的指標變數。

定義塊的時候,其所站的記憶體區域是分配在棧中的。這就是說,塊只在定義它的那個範圍內有效。例如,下面這段**就是有危險的:

void (^block)();

if () ;

}else;

}定義在if和else語句中的兩個塊都分配在棧記憶體中。編譯器會給每個塊分配好棧記憶體,然而等離開了相應的範圍之後,編譯器有可能把分配給塊的記憶體覆寫掉。於是,這兩個塊只能保證在對應的if和else語句範圍內有效。這樣寫出來的**可以編輯,但是執行起來有時正確有時不正確,若編譯器沒有覆寫待執行的塊,程式正常執行,若覆寫,程式崩潰。為解決這個問題,可給塊物件傳送copy訊息以拷貝之。這樣的話,就可以把塊從棧複製到堆 了。拷貝後的塊,可以在定義它的那個範圍之外使用。而且,一旦複製到了堆上,塊就成了帶引用計數的物件了。後續的複製操作都不會真的執行複製,只是遞增塊物件的引用計數。如果不再使用這個塊,那就應將其釋放,在arc下會自動釋放,而在手動管理應用計數則需要自己來呼叫release方法。當應用計數降為0後,「分配到堆上的塊」就會被系統**。在「棧上的塊」無須釋放,佔記憶體本身就會自動**。

void (^block)();

if () copy];

}else copy];

}這樣就能夠變得安全了,如果手動管理引用計數,那麼在用完塊之後還需要將其釋放。

站在巨人的肩膀上,同時有個人的想法……

關於開始的那些事

人總是有惰性的,當然我自己深有體會。一直有個想法想寫寫自己的blog,但隨時間的推移,很久都沒付出行動。最近工作專案開始不那麼忙了,維護乙份自己的blog的想法愈發強烈了。想把自己的一些想法,或者看到的一些有用的東西給大家分享,也給自己留下成長的痕跡。我從小喜歡看書,各種各樣的書屬於不求甚解的狀態。...

關於coredump的那些事

今天在網上搜了一些有關coredump的知識,簡單記一下,以防忘記 core dump檔名的模式儲存在 proc sys kernel core pattern中,預設是core 主要是今天比較鬱悶,要除錯程式crash,就用ulimit c unlimited設定了一下core檔案的大小,但是測試...

關於STL 的那些事

今晚參加訓練。樹狀陣列的練習,傻乎乎的用stl做了一晚,雖然題沒做出來,不過對stl的查詢有了更深一層的理解。關於stl。輸入輸出 vector push back pop back stack push pop queue push pop 頭 front 尾 back priority queu...