接下來讓咱們經過源碼來看一看Block的本質 git
這裏咱們經過一個clang的編譯命令clang -rewrite-objc xxx.m來看一下源碼的實現github
咱們的那段代碼經過編譯器編寫後,首先第一行I表明的是一個實例方法後面的是對象和方法名,傳了兩個參數一個是self,一個是選擇器因子編程
而後咱們方法中的第一行代碼在編譯後沒有發生改變,咱們着重看一下Block方法編譯後的改變bash
首先咱們能夠看到__BlockOneObj__testMethod_block_impl_0這樣一個結構體,在這個結構體中傳遞了幾個參數,第一個參數(void*)__BlockOneObj__testMethod_block_func_0咱們經過名字能夠知道這是一個無類型的函數指針,第二個參數&__BlockOneObj__testMethod_block_desc_0_DATA是一個Block相關描述的結構體而後取地址符,第三個參數muIntNum就是咱們定義的局部變量。最後取這個結構體地址強制轉換賦值給咱們定義的這個Block數據結構
而後咱們來看看__BlockOneObj__testMethod_block_impl_0這個結構體中有什麼具體操做,以下圖多線程
其中第一個結構體裏面又是什麼數據結構呢,請看下圖 函數
在咱們上面介紹的結構體下面還有一個函數,具體解釋請看下圖 ui
Block的調用其實就是函數的調用,從源碼中咱們能夠看出來spa
首先先對這個Block進行一個強制類型轉換(__block_impl *)Block線程
以後又取出它之中的成員變量FuncPtr(函數指針),找到咱們上面解析的結構體和函數,在其中拿到對應的函數調用,而後把其中的參數傳遞進去,一個參數是咱們這個Block自己,一個是咱們傳遞的2,而後就回去調用__BlockOneObj__testMethod_block_func_0函數,最終進行調用
首先咱們先來看一段代碼
- (void)testMethod {
int muIntNum = 6;
int(^Block)(int) = ^int(int num){
return num *muIntNum;
};
muIntNum = 4;
Block(2);
}
複製代碼
這段代碼執行完Block(2)返回的值是多少呢?-------答案是12 接下來咱們看一下爲何是12以及Block截獲變量的本質是什麼
對於基本數據類型的局部變量截獲其值
對於對象類型的局部變量連同其全部權修飾符一塊兒截獲
對於局部靜態變量是以指針形式去截獲
對於全局變量和靜態全局變量不截獲
下面直接上代碼
#import "BlockTwoObj.h"
//全局變量
int global_var = 4;
//全局靜態變量
static int static_global_var = 5;
@implementation BlockTwoObj
- (void)testMethodTwo {
//基本數據類型的局部變量
int var = 1;
//對象類型的的局部變量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
//局部靜態變量
static int static_var = 3;
void(^Block)(void) = ^{
NSLog(@"基本數據類型局部變量:%d", var);
NSLog(@"對象類型局部變量(__unsafe_unretained修飾):%@", unsafe_obj);
NSLog(@"對象類型局部變量(__strong修飾):%@", strong_obj);
NSLog(@"局部靜態變量:%d", static_var);
NSLog(@"全局變量:%d", global_var);
NSLog(@"全局靜態變量:%d", static_global_var);
};
Block();
}
@end
複製代碼
接下來咱們經過clang命令clang -rewrite-objc -fobjc-arc xxx.m來看一下源碼
咱們在什麼狀況下使用__block修飾符呢? 通常狀況下,對被截獲變量進行賦值操做須要添加__block修飾符,這裏須要注意的是賦值不等因而使用,切記!!!
例如在下面的代碼中是否須要__block修飾符來修飾
NSMutableArray *muArr = [[NSMutableArray alloc] init];
void(^Block)(void) = ^{
//這裏只是作了添加操做,並不是賦值,因此不須要用__block進行修飾
[muArr addObject:@"111"];
};
Block();
複製代碼
那麼在下面的代碼段當中呢?
__block NSMutableArray *muArrOther = nil;
void(^BlockOther)(void) = ^{
//這裏作了賦值操做,因此須要用__block進行修飾,不然會出現編譯報錯
muArrOther = [NSMutableArray array];
};
BlockOther();
複製代碼
對變量進行賦值時
須要__block修飾符修飾的是局部變量(包括基本數據類型和對象類型)
不須要__block修飾符修飾的是靜態局部變量、全局變量和靜態全局變量,由於對於全局變量和靜態全局變量不涉及到變量的截獲,而對於靜態局部變量呢,是經過使用指針來操做對應的變量的,因此也不須要修飾
下面請看一段代碼,仍是咱們上面的那個例子
- (void)testMethod {
__block int muIntNum = 6;
int(^Block)(int) = ^int(int num){
return num *muIntNum;
};
muIntNum = 4;
Block(2);
}
複製代碼
此時Block返回的是8,這裏是爲何呢,咱們只是用了__block來修飾
請看下面的流程圖
首先__block int muIntNum會被轉化成第一個這樣一個結構體,其中具備isa指針,咱們也能夠理解成一個對象
從這個角度來看muIntNum通過編譯後就會變成一個對象,經過__forwarding指針去找到對應的對象,而後進行賦值
剛纔咱們看到的代碼段是在棧上,在__block變量中有一個__forwarding指針,而這個指針指向的是本身,這裏要注意的是前提是在棧上,若是在堆上,這個__forwarding指針指向的就不是本身了,在下面會講到
因此在棧上咱們修改這個變量的值,就會經過__forwarding指針找到本身本省去修改這個變量的值
那麼這裏有一個問題就是咱們在棧上這個__forwarding指向的是本身到底有什麼用呢?咱們徹底能夠經過訪問成員變量來修改,爲何還須要這個指針呢,請繼續往下看
Block有三種類型
_NSConcreteGlobalBlock 全局Block
_NSConcreteStackBlock 棧Block
_NSConcreteMallocBlock 堆Block
Block的Copy操做
當咱們棧上的Block經過copy在堆上產生一個同樣的Block,有相同的Block和__block變量,當變量做用於結束後,棧上的Block對象就會被銷燬,而堆上的block依舊存在,全部若是棧上Block不用copy拷貝到堆上,在做用於銷燬後會由於找不到Block對象而崩潰
固然咱們在這裏有一個問題,假如說在MRC環境下,若是在棧上進行了copy操做,會不會產生內存泄漏,答案是確定的,至關於一個對象alloc出來,可是並無對應的relese操做同樣
Objective - C 高級編程:iOS與OS X多線程和內存管理