ps:看網上的東西最好自己試一下贝奇,別人講的東西不一定是正確的陕习。
Block概要
什么是Block
Blocks是C語言的擴充功能。可以用一句話來表示Blocks的擴充功能:帶有自動變量(局部變量)的匿名函數。命名就是工作的本質胳嘲,函數名汽纤、變量名呆瞻、方法名、屬性名、類名和框架名都必須具備归粉。而能夠編寫不帶名稱的函數對程序員來說相當有吸引力。例如:我們要進行一個URL的請求滴劲。那么請求結果以何種方式通知調用者呢?通常是經過代理(delegate)但是,寫delegate本身就是成本,我們需要寫類倦青、方法等等峭沦。這時候取募,我們就用到了block旺聚。block提供了類似由C++和OC類生成實例或對象來保持變量值的方法。像這樣使用block可以不聲明C++和OC類谐丢,也沒有使用靜態(tài)變量、靜態(tài)全局變量或全局變量衰腌,僅用編寫C語言函數的源碼量即可使用帶有自動變量值的匿名函數饶囚。其他語言中也有block概念歇万。
Block的實現(xiàn)
下面我們來定義一個簡單的block,block的語法看上去好像很特別壕曼,但實際上是作為極為普通的C語言代碼來處理的大渤。這里我們借住clang編譯器的能力:具有轉化為我們可讀源代碼的能力较曼『嵫眩控制臺命令是: clang -rewrite-objc 源代碼文件名仅炊。
經過 clang -rewrite-objc 之后呆馁,代碼編程這樣了(簡化后代碼,讀者可以搜索關鍵字在生成文件中查找):
看上去有點暈對不對誓沸,沒關系我們一個個來分析。
__block_impl:更像一個block的基類炭菌,所有block都具備這些字段。
__main_block_impl_0:block變量旭蠕。
__main_block_func_0:雖然block叫匿名函數停团。但是這個函數還是被編譯器起了個名字。(圖片里面 的顏色貌似不對勁)掏熬。
__main_block_desc_0:block的描述佑稠,注意,他有一個實例__main_block_desc_0_DATA孽江,上述命名是有規(guī)則的:main是block所在函數的名字讶坯,后綴0則是這個函數中的第0個block。由于上面是C++的代碼岗屏,可以將__main_block_impl_0的結構體總結一下辆琅,得到如下形式:
因此我可以看出來所謂的block就是一個object-c 對象漱办。為什么這么說呢,在runtime機制的時候會講到相關內容婉烟,到時候你就明白了娩井。
截獲自動變量值
我們看下面一段代碼,你猜猜是什么結果呢似袁?
上面這段代碼的值是10洞辣,你可能會感到很疑惑。block截獲自動變量的瞬時值昙衅。因為block保存了自動變量的值扬霜,所以在執(zhí)行block語法后,即使改寫block中使用的自動變量的值也不會影響block執(zhí)行時自動變量的值而涉。
如果你強行在block里面修改val的值著瓶,那么編譯器將會報錯,我們可以這么理解啼县,block捕獲的自動變量會默認轉化為const類型材原,不可修改了,如果我們要改的話只需要在定義變量的時候給它加__block修飾符就可以了季眷。但是如果我們捕獲的是oc對象呢余蟹?
就比如一個NSMutableArray *array。^{[array addObject:obj];};這么寫是沒有問題的子刮,因為array只是一個指針而已威酒,我們并沒有改變指針的值。
還有一種情況我們也可以很好的解釋:const char text[] = "hello";? ^{ printf("%c\n",text[2]);}; 這樣會編譯錯誤挺峡。為何兼搏?這是因為捕獲自動變量的方法并沒有實現(xiàn)C語言數組類型。
可以通過指針代替:const char *text= "hello";那么這個block的對象結構是什么樣呢沙郭,請看下面:
這個val是如何傳遞到block結構體中的呢?
就像C里面初始化結構體一樣的裳朋,注意函數調用最后一個參數病线,即val參數。那么函數調用的代碼頁轉化為下面這樣了.這里的cself跟C++的this和OC的self一樣鲤嫡。
所以送挑,block捕獲變量更像是:函數按值傳遞。是不是很簡單呢暖眼?
__block說明符
前面講過block所在函數中的惕耕,捕獲自動變量。但是不能修改它诫肠,不然就是編譯錯誤司澎。但是可以改變全局變量欺缘、靜態(tài)變量、全局靜態(tài)變量挤安。其實這兩個特點不難理解:
第一谚殊、為何不讓修改變量:這個是編譯器決定的。理論上當然可以修改變量了蛤铜,只不過block捕獲的是自動變量的副本嫩絮,名字一樣。為了不給開發(fā)者迷惑围肥,干脆不讓賦值剿干。道理有點像:函數參數,要用指針穆刻,不然傳遞的是副本置尔。
第二、可以修改靜態(tài)變量的值蛹批。靜態(tài)變量屬于類的撰洗,不是某一個變量。所以block內部不用調用cself指針腐芍。所以block可以調用差导。
解決block不能保存值這一問題的另外一個辦法是使用__block修飾符。
該源碼轉化后如下:
比__main_block_impl_0中自然多了__block_byreg_val_0的一個字段猪勇。注意:__block_byref_val_0結構體中有自身的指針對象设褐,難道要_block int val = 10;這一行代碼,轉化成了下面的結構體__block)byref_val_0 val = {0,&val,0,sizeof(__block_byref_val_0),10};//自己持有自己的指針泣刹。
它竟然變成了結構體了助析。之所以為啥要生成一個結構體,后面在詳細講講椅您。反正不能直接保存val的指針外冀,因為val是棧上的,保存棧變量的指針很危險掀泳。
block存儲區(qū)域
這就需要引入三個名詞:
● _NSConcretStackBlock
● _NSConcretGlobalBlock
● _NSConcretMallocBlock
正如它們名字說的那樣雪隧,說明了block的三種存儲方式:棧、全局员舵、堆脑沿。__main_block_impl_0結構體中的isa就是這個值。
定義在函數外面的block是global的马僻;另外如果函數內部的block庄拇,但是沒有捕獲任何自動變量,那么它也是全局的韭邓。比如下面這樣的代碼:
雖然措近,這個block在循環(huán)內溶弟,但是blk的地址總是不變的。說明這個block在全局段熄诡。
一種情況在非ARC下是無法編譯的:
typedef int(^blk_t)(int);
blk_t func(int rate){
return ^(int count){return rate*count;}
}
這是因為:block捕獲了棧上的rate自動變量可很,此時rate已經變成了一個結構體,而block中擁有這個結構體的指針凰浮。即如果返回block的話就是返回局部變量的指針我抠。而這一點恰是編譯器已經斷定了。在ARC下沒有這個問題袜茧,是因為ARC使用了autorelease了菜拓。
有時候我們需要調用block 的copy函數,將block拷貝到堆上笛厦∧啥Γ看下面的代碼:
我們經過調試發(fā)現(xiàn)如下結果:
它的第一個對象是在堆上面,第二個個在棧上面裳凸,我表示很困惑贱鄙。
這段代碼在最后一行blk()會異常,因為數組中的block是棧上姨谷。因為val是棧上的逗宁。解決辦法就是調用copy方法,將它復制到堆上面。
不管block配置在何處梦湘,用copy方法復制都不會引起任何問題瞎颗。在ARC環(huán)境下,如果不確定是否要copy block盡管copy即可捌议。ARC會打掃戰(zhàn)場哼拔。
注意:在棧上調用copy那么復制到堆上,在全局block調用copy什么也不做瓣颅,在堆上調用block 引用計數增加
__block變量存儲區(qū)域
當block被復制到堆上時倦逐,他所捕獲的對象、變量也全部復制到堆上宫补。
回憶一下block捕獲自動變量的時候僻孝,自動變量將編程一個結構體,結構體中有一個字段叫__forwarding守谓,用于指向自動這個結構體。那么有了這個__forwarding指針您单,無論是棧上的block還是被拷貝到堆上斋荞,那么都會正確的訪問自動變量的值。