內容大綱:
1、Blocks概要
2、Blocks模式
3、Block實質(面試常問重點)
1、Blocks概要
什么是Blocks:Blocks是C語言的擴充的功能,可以用一句話來表示Blocks的擴充功能:帶有局部變量(有的資料局部變量也叫自動變量)的匿名函數。這個函數叫block。 (注意Blocks是一種功能,block是一種函數)
1-1、關於"匿名函數"
匿名函數:不帶有名稱的函數就是匿名函數。(但是C語言的標准不允許存在這樣的函數。)
例如下面的源代碼:在賦值給函數指針時,若不使用賦值的函數的名稱,就無法取得該函數的地址:

然而通過Block,源代碼中就能夠使用匿名函數,即不帶名稱的函數。
1-2、關於"帶有局部變量(有的資料講局部變量也叫自動變量)"
這里為了能夠讓你們更好的理解這個"帶有局部變量(有的資料局部變量也叫自動變量)",我需要拿普通的函數被回調的過程
和Block函數被回調的過程
進行對比,這樣能夠讓讀者更好的理解這個block的"帶有局部變量(有的資料局部變量也叫自動變量)"的意義,以及這個block的特點。
(瞎扯兩句:通過對比出與眾不同的地方是可以成為特點的😉)
普通函數被回調的過程 和 block函數被回調的過程 的對比
為了能讓讀者進一步體會block的特性,本人分別在上面兩個源碼的函數被調用執行之前相應的地方添加了大括號設置局部變量的作用域,請看下面代碼理解
總結:

補充:可能有讀者覺得那block肯定能獲取全局變量,可是全局變量普通函數也能獲取啊,所以沒必要在這里扯全局變量。(這句補充的話讀者要是還不懂,那你就自行腦補吧。👻)
另 外介紹:"帶有局部變量的匿名函數"這一概念並不僅指Blocks,它還存在於其他許多編程語言中,在計算機中,此概念也稱為閉包(Closure)、 lambda計算(λ計算,lambda calculus)等,Objective-C的Block在其他程序語言中的名稱如下表格:
2、Blocks模式
2-1、Block 語法
Block語法格式:^ 返回值類型 (參數列表) {表達式}

省略形式的語法(只有兩種):
-
-
省略了返回值類型:^ (參數列表) {表達式}
-
省略了返回值類型和參數列表:^ {表達式}
-
2-2、Block 類型變量
先簡單講講基本數據類型的類型變量,int a = 2
這個a就是int類型的變量,這個變量a存儲着具體的值:2。
再講講函數指針類型變量:

這個funcptr就是函數指針類型的變量。這個變量指向(指針類型所以說是指向)func函數的地址。
那么同樣的,在Block語法下,可將前面講的"Block語法"賦值給聲明為Block類型的變量中。
聲明Block類型變量的格式:
返回值 (^變量名)(參數列表)
使用Block語法將Block賦值為Block類型變量:

關於Block類型變量聲明部分的快熟記憶的方法:首先你肯定知道普通函數聲明函數頭部分:int func(),那么block的聲明就是int (^func)(),其實就是在函數名的地方加了(^函數名),對比一下,是不是很好記了。
2-3 Block類型變量的使用
block類型變量,作為變量,它可以用在函數參數和返回值,但是這樣的話,記述方式極為復雜。這時,我們可以像使用函數指針類型那樣,使用typedef來解決這個問題:

2-4、截獲局部變量值
關於截獲局部變量值,其實在前面"1、Block概要"中其實已經介紹了,通過在適當的位置使用大括號,能夠驗證出Block會將局部變量拷貝一份為自己所擁有。
下面,通過另一種情況來驗證,其實也很簡單的,就是通過對外部的局部變量重新賦值。讓我們再來看看這個"帶有局部變量的匿名函數":

這個就是局部變量值的截獲,截獲之后被這個block所持有,因此叫做"帶有局部變量的匿名函數"。
2-5、__block說明符
實際上,雖然block可以截獲並拿到這個局部變量的值,但是卻不能在block內部直接更改它,下面的代碼會產生編譯錯誤:

解決方法就是在這個m變量前面使用__block說明符

使用附有block說明符的自動變量可以在Block中賦值,該變量稱為blcok變量。
2-6、截獲的局部變量相關的問題
后期補充
3、Blocks的實現(面試常問重點)
3-1、Block的實質
Block是"帶有局部變量的匿名函數",但是Block究竟是什么呢?本節將通過Block的實現進一步幫大家加深理解。
要想理解Block的實質,需要通過下面的終端命令將Block反編譯成底層C++的源代碼,雖然說是C++代碼,其實也是僅僅使用了C++的struct結構,其本質還是C語言的源代碼。
clang -rewrite-objc 源代碼文件名
然后我們打開這個main.cpp文件,你會發現內容好多啊,多的你不要不要的:
沒關系,很多代碼都不是重點,重點的是和Block相關的代碼,我們在main.cpp中可以先找到如下圖的main函數,然后利用XCode顏色高亮插件DDHighlight,選擇相關的關鍵字,同樣關鍵字都會呈現出顏色,因為他們之間肯定存在調用和被調用的關系這樣main函數中block低層實現的源碼相關的東西,都可以找出,然后刪除其他上百行無關的東西,也就剩下下面這30多行:
開始分析C++源碼,如圖是main函數中的block聲明部分和block執行部分的代碼和對應C++的源碼:
我們先分析分析block聲明的那段代碼,block執行的部分先放一邊,然后如圖我做了進一步的處理,讀者可以自己看圖,我將聲明結構體__block_impl內部的變量抽離出來,替換調用調用了結構體類型__block_impl聲明impl的部分,以及處理了使用結構體變量impl的部分,不難,本質還是沒變的:
按照上面意思我去掉不必要的注釋,並且為了大家能夠看得更清楚,我將其中長長的名字替換成簡單的一目了然的名字,
比如__main_block_impl_0我全部替換成block_impl:
好,接下來就不得不多提一個C++的基礎了,還好本人學過C++的基礎部分敲過代碼,上面一個代碼的圖中黃色框起來的部分是C++的結構體,在結構體blcok_impl中,多了一個看起來像C函數的函數,好像和我們習慣用的C語言結構體內部不帶函數有區別,對於有一定Java基礎或者是Swift基礎或者是C++基礎就會很熟悉這部分基礎,如果不熟悉C++的結構體知識,那么讀者可以閱讀本人的《C語言的結構體和C++結構體的區別》。
也就是說,黃色框框的部分,結構體內部使用了構造函數或者是叫構造方法,我們通過初始化構造方法就能創建這個結構體的實例對象。
再看綠色框起來的代碼,我把它拷貝過來:
block_impl((void *)block_func, &block_desc_DATA),
可以看得出,調用了block_impl這個結構體的構造方法,創建了這個block_impl這個結構體的實例對象,只不過,傳入了兩個參數:
1、(void *)block_func 2、&block_desc_DATA ,
而原構造函數需要三個參數:
1、void *fp 2、struct block_desc *desc 3、int flags=0,
很顯然第三個參數已經被默認賦值為0,所以可以不需要傳第三個參數。
關於block_desc_DATA這個我就不詳說了,知道這么個東西就好,直接說說block_func,很明顯,這個block的底層實現,就是創建了一個函數指針指向了一個函數,這個函數內部的代碼邏輯就是我們Objective-C使用block所包含的代碼邏輯。
下面,我們加進去之前為了避免干擾而刪除的執行block部分的代碼:
去掉多余的部分,我們可以看到:
本人雖然會一點C++基礎,但是對於這黃色框起來的代碼的寫法本人也是第一次見過,所以不懂其基礎細節,有大神懂得話,可以指教指教,
但是不管不懂還是不懂,不管你不懂還是我不懂,我們都可以大概的看的出來這黃色框起來的代碼的意思就是
執行指針變量blk指向的函數FuncPtr
而回到前面 FuncPtr = void block_func(struct block_impl *__cself) { printf("Block\n"); }
就這樣,完成了Objective-C的block的創建和執行。
3-2、截獲局部變量值的block底層實質分析
根據上面簡單的介紹,下面就不多累述了,直接截圖出邏輯原理的代碼圖:
為了方便用"——>"畫圖,我將block_func和block_impl兩個結構體調換位置。大家能理解就行。
總結一下,如果面試問到讀者:請說說block的底層實現原理,讀者可以這么回答:
"將block源碼反編譯成C++源碼,可以看到,block底層通過使用C++的帶有可以初始化成員變量的構造方法的結構體,來存儲OC源碼block截獲的局部變量,並且這個結構體中還有一個成員函數指針,通過構造函數初始化可以指向另外聲明的一個函數,而這個函數就包含了OC源碼中block大括號括起來的代碼邏輯,當我們執行這個block的時候,block底層C++源碼就會取出這個結構體的成員函數指針變量,然后執行成員函數指針所指向的函數,這個函數包含兩部分:1、在這個函數中取出了結構體的成員變量,這個成員變量存儲了OC源碼截獲的局部變量,2、執行了所包含的OC源碼中block大括號括起來的那部分代碼邏輯"。
學習來自:《Objective-C高級編程 iOS與OS X多線程和內存管理》
轉載需要注明出處:http://www.cnblogs.com/goodboy-heyang/p/5240004.html,尊重勞動成果。