iOS底層原理總結(jié) - 探尋block本質(zhì)(一)

本篇主要是對小碼哥底層視頻學習的總結(jié)丘损。方便日后復習。
上一篇《iOS底層原理總結(jié) - 探尋關(guān)聯(lián)對象本質(zhì)》:
http://www.reibang.com/p/2f0626c76c81

本篇學習總結(jié):

  • 探尋block的本質(zhì)
  • 代碼驗證block底層實現(xiàn)
  • block的變量捕獲
  • block的類型
  • block內(nèi)存儲存
  • ARC跟MRC下block的特性

好了兔魂,帶著問題咬荷,我們一一開始閱讀吧 ??

一.探尋block的本質(zhì)

我們說到block并不陌生笋除,先寫一個簡單的block代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(int ,int) = ^(int a, int b){
            NSLog(@"this is block,a = %d,b = %d",a,b);
            NSLog(@"this is block,age = %d",age);
        };
        block(3,5);
    }
    return 0;
}

//打印結(jié)果如下
this is block,a = 3,b = 5
this is block,age = 10

然后用命令行轉(zhuǎn)化成c++文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

這些在前面的文章中都講過了寂屏,可以回去直接查找。
我們搜索int main 可以看到如下結(jié)果

main函數(shù)轉(zhuǎn)化成c++文件.png

我們可以看到一個__main_block_impl_0 蠕趁,全局搜索__main_block_impl_0薛闪,搜索如下結(jié)果:

oc跟c++代碼對比.png

那么我們將c++中block的聲明和調(diào)用部分分別取出來看一下內(nèi)部實現(xiàn)是怎么樣的

1.定義block變量

// 定義block變量代碼
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

上述定義的代碼中可以發(fā)現(xiàn),block定義中調(diào)用了__main_block_impl_0函數(shù)俺陋,并且將__main_block_impl_0函數(shù)的地址賦給了block豁延,我們再來看一下__main_block_impl_0函數(shù)結(jié)構(gòu)

2.__main_block_imp_0結(jié)構(gòu)體

__main_block_imp_0函數(shù)內(nèi)部結(jié)構(gòu).png

__main_block_impl_0函數(shù)中包含如下信息:
struct __block_impl impl :結(jié)構(gòu)體封裝了定義block的塊內(nèi)容,即^{}
struct __main_block_desc_0 Desc :結(jié)構(gòu)體封裝了定義block的大小
int age:這個age屬性是block塊中捕獲的局部變量age
__main_block_impl_0():同名構(gòu)造函數(shù)腊状,構(gòu)造函數(shù)中對一些變量進行了賦值最終會返回一個結(jié)構(gòu)體诱咏,也就是說最終將一個__main_block_imp_0結(jié)構(gòu)體的地址賦值給block變量,__main_block_imp_0結(jié)構(gòu)體內(nèi)可以發(fā)現(xiàn)傳入了四個參數(shù)缴挖,(void *)__main_block_func_0袋狞、&__main_block_desc_0_DATA、age映屋、flags*苟鸯。其中flage有默認值,也就說flage參數(shù)在調(diào)用的時候可以省略不傳棚点,而最后的age(_age)則表示傳入的_age參數(shù)會自動賦值給age成員早处,相當于age=_age。

3. (void )__main_block_func_0函數(shù)

__main_block_func_0函數(shù).png

__main_block_func_0函數(shù)中看到局部變量age的值瘫析,緊接著可以看到兩個NSLog方法砌梆,可以發(fā)現(xiàn)這兩段代碼恰恰是我們在block塊中寫下的代碼默责。
__main_block_func_0函數(shù)中其實存儲著我們block中寫下的代碼,而__main_block_impl_0函數(shù)中傳入的是(void )__main_block_func_0么库,也就說將我們寫的block塊中的代碼封裝成__main_block_func_0函數(shù)傻丝,并將__main_block_func_0函數(shù)的地址傳入了__main_block_impl_0的構(gòu)造函數(shù)中保存到結(jié)構(gòu)體內(nèi)甘有。

4.&__main_block_desc_0_DATA

& __main_block_desc_0_DATA結(jié)構(gòu)體數(shù)據(jù).png

我們可以看到__main_block_desc_0結(jié)構(gòu)體中存儲著兩個參數(shù)诉儒,reservedBlock_size,其中reserved賦值為0亏掀,而Block_size則存儲著__main_block_impl_0的占用空間大小忱反,最后將__main_block_desc_0結(jié)構(gòu)體的地址傳入__main_block_func_0中賦值給Desc。

5.age

age是我們在最初定義的局部變量滤愕,因為在block塊中使用到age局部變量温算,所以在block聲明的時候這里才會將age作為參數(shù)傳入,也就說block會捕獲age间影,如果沒有在block中使用age注竿,這里將只會傳入(void *)__main_block_func_0&__main_block_desc_0_DATA兩個參數(shù)魂贬。
以上就是我們對block底層結(jié)構(gòu)的分析巩割。

這里我們再來思考一個問題,為什么我們在定義block之后修改局部變量age的值付燥,在block調(diào)用的時候無法生效呢宣谈?

int age = 10;
void(^block)(int ,int) = ^(int a, int b){
     NSLog(@"this is block,a = %d,b = %d",a,b);
     NSLog(@"this is block,age = %d",age);
};
  age = 20;
  block(3,5); 
  //打印結(jié)果如下:
this is block,a = 3,b = 5
this is block,age = 10

因為block在定義的時候已經(jīng)將age的值傳入并存儲在__main_block_imp_0結(jié)構(gòu)體中,在調(diào)用的時候?qū)ge從block中取出來使用键科,因此在block定義后對局部變量進行修改在block捕獲的局部變量中是無法更改的闻丑。

我們分析完了main函數(shù)中構(gòu)造block的各個參數(shù),我們點擊__main_block_impl_0找到對應(yīng)結(jié)構(gòu)體,我們看__block_impl結(jié)構(gòu)體勋颖。

6. __block_impl

__block_impl結(jié)構(gòu)體內(nèi)部.png

我們發(fā)現(xiàn)在__block_impl結(jié)構(gòu)體內(nèi)部有一個 isa指針嗦嗡,可以推測block本質(zhì)上就是一個oc對象,而在構(gòu)造函數(shù)中將函數(shù)中傳入的值分別存儲在__main_block_impl_0結(jié)構(gòu)體實例中饭玲,最終將其結(jié)構(gòu)體的地址賦值給block侥祭。
接著通過上面對__main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)三個參數(shù)的分析我們可以得出結(jié)論:

  1. __block_impl結(jié)構(gòu)體中的isa指針存儲著& _NSConcreteStackBlock地址,可以暫時理解為其類對象的地址咱枉,block就是_NSConcreteStackBlock類型的卑硫。
  2. block代碼塊中的代碼被封裝在__main_block_func_0函數(shù)中,FuncPtr則存儲著__main_block_func_0函數(shù)的地址蚕断。
  3. Desc指向__main_block_desc_0結(jié)構(gòu)體對象欢伏,其中存儲__main_block_impl_0結(jié)構(gòu)體所占用的內(nèi)存。

7.調(diào)用block執(zhí)行內(nèi)部代碼

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);

通過上述代碼可以發(fā)現(xiàn)調(diào)用block是通過block找到FunPtr直接調(diào)用亿乳。

為什么block可以直接調(diào)用FunPtr呢硝拧?

通過上面分析我們知道block指向的是__main_block_impl_0類型的結(jié)構(gòu)體径筏,但是我們發(fā)現(xiàn)__main_block_impl_0結(jié)構(gòu)體中并不直接可以找到FunPtr,而FunPtr是存儲在__block_impl中的障陶。重新查看上述源碼可以發(fā)現(xiàn)滋恬,(__block_impl *)block將block強制轉(zhuǎn)化為__block_impl類型的,因為__block_impl__main_block_impl_0結(jié)構(gòu)體第一個成員變量抱究,相當于將__block_impl結(jié)構(gòu)體的成員直接拿出來放在__main_block_impl_0中恢氯,那么也可以說明__block_impl的內(nèi)存地址就是__main_block_impl_0結(jié)構(gòu)體的內(nèi)存地址開頭,所以將block強制轉(zhuǎn)化成(__block_impl *)block)是可以的鼓寺,那我們通過block調(diào)用FuncPtr也是可以的勋拟。
上面我們知道,F(xiàn)unPtr中存儲通過代碼封裝的函數(shù)地址妈候,那么調(diào)用此函數(shù)敢靡,也就是會執(zhí)行代碼塊中的代碼。并且回頭查看__main_block_func_0函數(shù)苦银,可以發(fā)現(xiàn)第一個參數(shù)就是__main_block_impl_0類型的指針啸胧,也就說將block傳入__main_block_func_0函數(shù)中,便于重新取出block捕獲的值幔虏。

二.代碼驗證block的本質(zhì)

通過代碼證明一下block的本質(zhì)確實是__main_block_impl_0結(jié)構(gòu)體類型纺念。
還是使用之前的方法,自定義block內(nèi)部結(jié)構(gòu)體所计,并將block內(nèi)部的結(jié)構(gòu)體強制轉(zhuǎn)化為自定義的結(jié)構(gòu)體柠辞。

先上一段代碼:

struct __main_block_desc_0 { 
    size_t reserved;
    size_t Block_size;
};
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
// 模仿系統(tǒng)__main_block_impl_0結(jié)構(gòu)體
struct __main_block_impl_0 { 
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age;
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(int ,int) = ^(int a, int b){
            NSLog(@"this is block,a = %d,b = %d",a,b);
            NSLog(@"this is block,age = %d",age);
        };
// 將底層的結(jié)構(gòu)體強制轉(zhuǎn)化為我們自己寫的結(jié)構(gòu)體,通過我們自定義的結(jié)構(gòu)體探尋block底層結(jié)構(gòu)體
        struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
        block(3,5);
    }
    return 0;
}

通過打斷點我們可以看出我們自定義的結(jié)構(gòu)體被賦值成功主胧,每個類型的變量都能看到數(shù)據(jù)

轉(zhuǎn)化結(jié)構(gòu)__main_block_impl_0.png

總結(jié)
此時已經(jīng)對block的底層結(jié)構(gòu)有了基本的認識叭首,上述代碼可以通過一張圖展示其中各個結(jié)構(gòu)體之間的關(guān)系

block底層結(jié)構(gòu)關(guān)系圖.png

block底層的數(shù)據(jù)也可以通過一張圖來展示:

block底層的數(shù)據(jù)結(jié)構(gòu).png
三.block的變量捕獲

為了保證block內(nèi)部能夠正常訪問外部的變量,block有一個變量捕獲的機制踪栋。

1.局部變量

auto變量

上述代碼中我們已經(jīng)了解過block對age變量的捕獲焙格。
auto自動變量,離開作用域就銷毀夷都,局部變量默認添加auto關(guān)鍵字眷唉。自動變量會被捕獲到block內(nèi)部,也就是說block內(nèi)部結(jié)構(gòu)中會自動生成一個局部變量數(shù)據(jù)囤官,存儲捕獲的變量數(shù)據(jù)冬阳。auto只存在局部變量中,傳遞方式為值傳遞党饮,通過對上述age變量在block后修改肝陪,block中的age并沒有改變可以看出確實是值傳遞。

static變量
static修飾的變量為指針傳遞刑顺,同樣會被block捕獲
我們通過代碼看一下這兩者的區(qū)別:

int main(int argc, const char * argv[]) {
   @autoreleasepool {
       auto int a = 10;
       static int b = 11;
       void(^block)(void) = ^{
           NSLog(@"hello, a = %d, b = %d", a,b);
       };
       a = 1;
       b = 2;
       block();
   }
   return 0;
}

重新生成main.cpp文件氯窍,可以看到兩個參數(shù)的區(qū)別


兩個參數(shù)轉(zhuǎn)化代碼.png

從上述代碼中可以看出饲常,a,b兩個基本數(shù)據(jù)類型的變量都捕獲到block內(nèi)部了,但是a傳入的是值狼讨,b傳入的是b變量的地址贝淤。

說明局部變量不管用auto修飾還是static修飾,都能被捕獲到block底層結(jié)構(gòu)體中政供,只不過auto是值傳遞播聪,block中不能修改捕獲變量數(shù)據(jù),static是指針傳遞鲫骗,block中修改捕獲變量數(shù)據(jù)

修改block內(nèi)捕獲變量的數(shù)值.png

同樣是局部變量犬耻,為啥會有差異呢?

因為自動變量離開作用域就會自動銷毀执泰,系統(tǒng)自動管理內(nèi)存,block在執(zhí)行的時候剛要用這個局部變量時發(fā)現(xiàn)局部變量已經(jīng)銷毀了渡蜻,那么此時如果再去訪問被銷毀的地址肯定會發(fā)生壞內(nèi)存訪問术吝,所以出于此考慮,block對于auto自動變量的捕獲也得是值傳遞而不能是指針傳遞茸苇。而static修飾的局部變量超出作用域也不會銷毀排苍,會繼續(xù)在內(nèi)存中保存,所以完全可以是值傳遞学密,方便后期block及時調(diào)用內(nèi)部捕獲變量的數(shù)據(jù)淘衙。

2.全局變量

還是先上代碼

int a = 0;
static int b = 0;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"hello, a = %d, b = %d", a,b);
        };
        a = 10;
        b = 20;
        block();
    }
    return 0;
}
//打印結(jié)果如下:
hello, a = 10, b = 20

轉(zhuǎn)化成c++文件


全局變量c++代碼.png

通過上述代碼可以發(fā)現(xiàn),__main_block_imp_0結(jié)構(gòu)體中并沒有添加任何變量腻暮,而是添加到__main_block_imp_0結(jié)構(gòu)體的上面了彤守,因此block不需要捕獲全局變量,因為全局變量無論在哪里都可以訪問哭靖。

局部變量以為跨函數(shù)訪問所以需要捕獲具垫,全局變量在哪里都可以訪問,所以不需要捕獲试幽。

最后用一張圖總結(jié)上述結(jié)論筝蚕,方便記憶:


block的變量捕獲總結(jié).png

如果我們在實例方法或者類方法中添加block,會捕獲變量嗎?
還是上代碼吧

#import "Person.h"
@implementation Person
- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"%@",self);
    };
    block();
}
- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}
+ (void) test2
{
    NSLog(@"類方法test2");
}
@end

同樣轉(zhuǎn)化為c++代碼查看其內(nèi)部結(jié)構(gòu)


c++轉(zhuǎn)化代碼.png

上圖中可以發(fā)現(xiàn)铺坞,self同樣被block捕獲起宽,接著我們找到test方法可以發(fā)現(xiàn),test方法默認傳遞了兩個參數(shù)self_cmd济榨,而類方法test2也同樣默認傳遞了類對象self和方法選擇器_cmd坯沪。

對象方法和類方法對比.png

不論對象方法還是類方法都會默認將self作為參數(shù)傳遞給方法內(nèi)部,既然是作為參數(shù)傳入腿短,那么self肯定是局部變量屏箍,上面講到了局部變量肯定會被block捕獲绘梦。
接著我們來看一下如果在block中使用成員變量或者調(diào)用實例的屬性會有什么不同的結(jié)果。

- (void)test
{
    void(^block)(void) = ^{
        NSLog(@"%@",self.name);
        NSLog(@"%@",_name);
    };
    block();
}
c++ 代碼.png

上圖中可以發(fā)現(xiàn)赴魁,即使block中使用的是實例對象的屬性卸奉,block中捕獲的仍然是實例對象,并通過實例對象通過不同的方式去獲取使用到的屬性颖御。

四.block的類型

block對象是什么類型榄棵,之前稍微提到過,通過源碼可以知道block中的isa指針指向的是_NSConcreteStackBlock類對象地址潘拱。那么block是否就是_NSConcreteStackBlock類型的呢疹鳄?
還是先上代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject
        void (^block)(void) = ^{
            NSLog(@"Hello");
        };
        
        NSLog(@"%@", [block class]);
        NSLog(@"%@", [[block class] superclass]);
        NSLog(@"%@", [[[block class] superclass] superclass]);
        NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    }
    return 0;
}

打印結(jié)果如下:


block類型打印.png

從上述打印內(nèi)容可以看出block最終都是繼承自NSBlock類型,而NSBlock繼承于NSObject芦岂,那么block其實都是繼承NSObject瘪弓,這也更加印證了block的本質(zhì)就是OC對象。

block的3種類型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )
  • NSStackBlock ( _NSConcreteStackBlock )
  • NSMallocBlock ( _NSConcreteMallocBlock )

通過代碼查看一下block在什么情況下會是什么類型呢禽最?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 內(nèi)部沒有調(diào)用外部變量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用外部變量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接調(diào)用的block的class
        NSLog(@"block1 = %@ block2 = %@ 直接調(diào)用 = %@", [block1 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}
//打印結(jié)果如下:
block1 = __NSGlobalBlock__ 
block2 = __NSMallocBlock__ 
直接調(diào)用 = __NSStackBlock__

但是我們上面提到過腺怯,上述代碼轉(zhuǎn)化為c++代碼查看源碼時發(fā)現(xiàn)block的類型與打印出來的類型不一樣,c++源碼中三個block的isa指針全部都指向_NSConcreteStackBlock類型地址川无。
我們可以猜測runtime運行時也許對類型進行了轉(zhuǎn)變呛占。最終類型當然以runtime運行時類型即我們打印出來的類型為準。

五.block內(nèi)存儲存

1.block內(nèi)存分配地址

不同類型的block存放區(qū)域.png

從上圖可以看出懦趋,__NSGlobalBlock__存放在數(shù)據(jù)段中晾虑,數(shù)據(jù)段信息中存儲的變量生命周期跟隨應(yīng)用程序的生命周期存在,程序結(jié)束時變量內(nèi)存地址被回收仅叫,不過我們一般很少使用__NSGlobalBlock__類型的block帜篇,因為這樣的block并沒有什么意義。

__NSStackBlock__類型的block存放在棧中惑芭,我們知道棧中的內(nèi)存由系統(tǒng)自動分配和釋放坠狡,作用域結(jié)束后被立刻釋放,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉遂跟。

__NSMallocBlock__類型的block存放在堆上逃沿,內(nèi)存需要我們自己內(nèi)存管理。

2.block類型定義依據(jù)

block定義類型的依據(jù).png

接著我們使用代碼驗證上述問題幻锁,首先從ARC回到MRC環(huán)境凯亮,因為ARC會幫助我們做很多事情,有些問題我們檢驗不出來哄尔。

切換MRC環(huán)境.png

還是剛才的代碼
MRC 環(huán)境<傧!岭接!

MRC 環(huán)境8晦帧>视琛!
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1. 內(nèi)部沒有調(diào)用外部變量的block
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        // 2. 內(nèi)部調(diào)用外部變量的block
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d",a);
        };
       // 3. 直接調(diào)用的block的class
        NSLog(@"block1 = %@ block2 = %@ 直接調(diào)用 = %@", [block1 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}
//打印結(jié)果如下:
 block1 = __NSGlobalBlock__ 
block2 = __NSStackBlock__ 
直接調(diào)用 = __NSStackBlock

通過上述代碼發(fā)現(xiàn)啃沪,

在MRC環(huán)境下粘拾,
沒有訪問auto變量的block是__NSGlobalBlock__類型的,存放在數(shù)據(jù)段中创千。
訪問了auto變量的block是__NSStackBlock__類型的缰雇,存放在棧中。

棧中的內(nèi)存管理是系統(tǒng)自動管理追驴,棧中的代碼在作用域結(jié)束之后就會被銷毀械哟,那么我們很有可能在block內(nèi)存銷毀以后去調(diào)用它,那么就會發(fā)現(xiàn)問題殿雪,代碼驗證訪問壞內(nèi)存問題暇咆。

void (^block)(void);
void test()
{
    // __NSStackBlock__
    int a = 10;
    block = ^{
        NSLog(@"block---------%d", a);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}
//打印結(jié)果:
block----------272632792

可以發(fā)現(xiàn)block中的a數(shù)據(jù)變成了不可控數(shù)據(jù),因為在MRC環(huán)境下冠摄,test()方法中聲明的block是在棧中創(chuàng)建糯崎,當test()方法調(diào)用完畢,block所占內(nèi)存空間會自動被系統(tǒng)回收河泳,因此當我們在調(diào)用block()時,訪問被釋放內(nèi)存區(qū)域年栓,數(shù)據(jù)就會不可控拆挥。

通過查看c++轉(zhuǎn)化代碼也能看出來


c++代碼.png

為了避免這種情況,在MRC環(huán)境下我們手動給NSStackBlock類型添加copy方法會轉(zhuǎn)化成NSMallocBlock類型的block某抓,NSMallocBlock類型的block存儲在堆上纸兔,block內(nèi)存系統(tǒng)是手動釋放
先上代碼

void (^block)(void);
void test()
{
    // __NSStackBlock__ 調(diào)用copy 轉(zhuǎn)化為__NSMallocBlock__
    int age = 10;
    block = [^{
        NSLog(@"block---------%d", age);
    } copy];
    [block release];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
}
//打印結(jié)果:
block---------10

同樣的代碼,我們切換到ARC環(huán)境
ARC環(huán)境7窀薄汉矿!

ARC環(huán)境!备禀!
void (^block)(void);
void test()
{
    // __NSStackBlock__
    int a = 10;
    block = ^{
        NSLog(@"block---------%d", a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        block();
    }
    return 0;
//打印結(jié)果:
block---------10
}

總結(jié):在MRC環(huán)境下經(jīng)常使用copy來保存__NSStackBlock__洲拇,將棧上的block拷貝到堆上,即使棧上的block被銷毀曲尸,堆上的block不會被銷毀赋续,需要我們自己調(diào)用release操作來銷毀,而在ARC環(huán)境下編譯器會自動copy另患,堆上的block也不會被銷毀纽乱。

那么其他類型的block調(diào)用copy會改變block類型嗎?下面的表格已經(jīng)展示很清晰了

不同類型調(diào)用copy結(jié)果.png
六.ARC跟MRC下block的特性

在ARC環(huán)境下昆箕,編譯器會根據(jù)情況自動將棧上的block進行一次copy操作鸦列,將block復制到堆上租冠。
什么情況下ARC會自動將block進行一次copy操作呢?(下面代碼都在ARC環(huán)境下進行)

1.block作為函數(shù)返回值時

typedef void(^MyBlock)();
MyBlock test (){
    int a = 10;
    MyBlock myblock = ^{
        NSLog(@"a ==== %d",a);
    };
    return myblock;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyBlock block = test();
        block();
       // 打印block類型為 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
//打印結(jié)果:
a ==== 10
__NSMallocBlock__
}

上文提到過薯嗤,如果在block中訪問了auto變量時顽爹,block的類型為__NSStackBlock__,上面打印內(nèi)容發(fā)現(xiàn)blcok為__NSMallocBlock__類型的应民,并且可以正常打印出a的值话原,說明block內(nèi)存并沒有被銷毀。
上面提到過诲锹,block進行copy操作會轉(zhuǎn)化為__NSMallocBlock__類型繁仁,來講block復制到堆中,那么說明RAC在 block作為函數(shù)返回值時會自動幫助我們對block進行copy操作归园,以保存block黄虱,并在適當?shù)牡胤竭M行release操作。

2.將block賦值給__strong指針時
block被強指針引用時庸诱,ARC也會自動對block進行一次copy操作捻浦。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1.block內(nèi)沒有訪問auto變量,賦值給__strong指針
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"block--------%@",[block class]);
        int a = 10;
        // 2.block內(nèi)訪問了auto變量桥爽,但沒有賦值給__strong指針
        NSLog(@"block1--------%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        // 3.block內(nèi)訪問auto變量,賦值給__strong指針
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"block2-------%@",[block2 class]);
       
    }
    return 0;
}
//打印結(jié)果如下:
block--------__NSGlobalBlock__
block1--------__NSStackBlock__
block2-------__NSMallocBlock__

可以看出朱灿,__NSGlobalBlock__變量即使賦值給__strong指針也是__NSGlobalBlock__變量類型,__NSStackBlock__變量不賦值給__strong指針還是__NSStackBlock__類型钠四,賦值給__strong指針時會變成__NSMallocBlock__類型

3.block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
例如:遍歷數(shù)組的block方法盗扒,將block作為參數(shù)的時候

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
}];

4.block作為GCD API的方法參數(shù)時
例如:GDC的一次性函數(shù)或延遲執(zhí)行的函數(shù),執(zhí)行完block操作之后系統(tǒng)才會對block進行release操作

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
            
});        
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
});

5.block聲明建議寫法
通過上面對MRC及ARC環(huán)境下block的不同類型的分析缀去,總結(jié)出不同環(huán)境下block屬性建議寫法侣灶。

MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

因為block內(nèi)容有點多,面試題總結(jié)在下一篇缕碎。
本篇學習先記錄到此褥影,感謝閱讀,如有錯誤咏雌,不吝賜教凡怎。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市处嫌,隨后出現(xiàn)的幾起案子栅贴,更是在濱河造成了極大的恐慌,老刑警劉巖熏迹,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件檐薯,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機坛缕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門墓猎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赚楚,你說我怎么就攤上這事毙沾。” “怎么了宠页?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵左胞,是天一觀的道長。 經(jīng)常有香客問我举户,道長烤宙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任俭嘁,我火速辦了婚禮躺枕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘供填。我一直安慰自己拐云,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布近她。 她就那樣靜靜地躺著叉瘩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粘捎。 梳的紋絲不亂的頭發(fā)上房揭,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音晌端,去河邊找鬼。 笑死恬砂,一個胖子當著我的面吹牛咧纠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泻骤,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼漆羔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狱掂?” 一聲冷哼從身側(cè)響起演痒,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趋惨,沒想到半個月后鸟顺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年讯嫂,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹦锋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡欧芽,死狀恐怖莉掂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情千扔,我是刑警寧澤憎妙,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站曲楚,受9級特大地震影響厘唾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洞渤,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一阅嘶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧载迄,春花似錦讯柔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惋耙,卻和暖如春捣炬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绽榛。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工湿酸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灭美。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓推溃,卻偏偏與公主長得像蛋欣,于是被迫代替她去往敵國和親娃殖。 傳聞我的和親對象是個殘疾皇子趣惠,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容