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

面試題

  1. block的原理是怎樣的?本質(zhì)是什么拒逮?
  2. __block的作用是什么罐氨?有什么使用注意點?
  3. block的屬性修飾詞為什么是copy滩援?使用block有哪些使用注意栅隐?
  4. block在修改NSMutableArray,需不需要添加__block租悄?

首先對block有一個基本的認識

block本質(zhì)上也是一個oc對象谨究,他內(nèi)部也有一個isa指針。block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象泣棋。

探尋block的本質(zhì)

Block-Demo
快速寫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;
}

使用命令行將代碼轉(zhuǎn)化為c++查看其內(nèi)部結(jié)構(gòu)胶哲,與OC代碼進行比較

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

image.png

上圖中將c++中block的聲明和定義分別與oc代碼中相對應顯示。將c++中block的聲明和調(diào)用分別取出來查看其內(nèi)部實現(xiàn)潭辈。

定義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ù)內(nèi)部結(jié)構(gòu)。

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

image.png

__main_block_imp_0結(jié)構(gòu)體內(nèi)有一個同名構(gòu)造函數(shù)__main_block_imp_0技竟,構(gòu)造函數(shù)中對一些變量進行了賦值最終會返回一個結(jié)構(gòu)體冰肴。

那么也就是說最終將一個__main_block_imp_0結(jié)構(gòu)體的地址賦值給了block變量

__main_block_impl_0結(jié)構(gòu)體內(nèi)可以發(fā)現(xiàn)__main_block_impl_0構(gòu)造函數(shù)中傳入了四個參數(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锨推。

接下來著重看一下前面三個參數(shù)分別代表什么铅歼。

(void *)__main_block_func_0

image.png

__main_block_func_0函數(shù)中首先取出block中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)。

&__main_block_desc_0_DATA

image.png

我們可以看到__main_block_desc_0中存儲著兩個參數(shù)译荞,
reserved
Block_size瓤的,
并且reserved賦值為0Block_size則存儲著__main_block_impl_0的占用空間大小。最終將__main_block_desc_0結(jié)構(gòu)體的地址傳入__main_block_func_0中賦值給Desc吞歼。

age

age也就是我們定義的局部變量圈膏。因為在block塊中使用到age局部變量,所以在block聲明的時候這里才會將age作為參數(shù)傳入篙骡,也就說block會捕獲age本辐,如果沒有在block中使用age桥帆,這里將只會傳入(void *)__main_block_func_0医增,&__main_block_desc_0_DATA兩個參數(shù)慎皱。

這里可以根據(jù)源碼思考一下為什么當我們在定義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); 
     // log: 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_block_impl_0結(jié)構(gòu)體

image.png

首先我們看一下__block_impl第一個變量就是__block_impl結(jié)構(gòu)體忽刽。 來到__block_impl結(jié)構(gòu)體內(nèi)部

image.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ù),F(xiàn)uncPtr則存儲著__main_block_func_0函數(shù)的地址黎泣。

3. Desc指向__main_block_desc_0結(jié)構(gòu)體對象恕刘,其中存儲__main_block_impl_0結(jié)構(gòu)體所占用的內(nèi)存。

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

// 執(zhí)行block內(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指向的是__main_block_impl_0類型結(jié)構(gòu)體褐着,但是我們發(fā)現(xiàn)__main_block_impl_0結(jié)構(gòu)體中并不直接就可以找到FunPtr,而FunPtr是存儲在__block_impl中的托呕,為什么block可以直接調(diào)用__block_impl中的FunPtr呢含蓉?

重新查看上述源代碼可以發(fā)現(xiàn),(__block_impl *)blockblock強制轉(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)存地址開頭呆抑。所以可以轉(zhuǎn)化成功岂嗓。并找到FunPtr成員。

上面我們知道鹊碍,FunPtr中存儲著通過代碼塊封裝的函數(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ì)確實是__main_block_impl_0結(jié)構(gòu)體類型。

通過代碼證明一下上述內(nèi)容: 同樣使用之前的方法,我們按照上面分析的block內(nèi)部結(jié)構(gòu)自定義結(jié)構(gòu)體罐柳,并將block內(nèi)部的結(jié)構(gòu)體強制轉(zhuǎn)化為自定義的結(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)體可以被賦值成功齿梁,以及里面的值。

image.png

接下來斷點來到block代碼塊中肮蛹,看一下堆棧信息中的函數(shù)調(diào)用地址勺择。Debuf workflow -> always show Disassembly

image.png

通過上圖可以看到地址確實和FuncPtr中的代碼塊地址一樣。

總結(jié)

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

image.png

block底層的數(shù)據(jù)結(jié)構(gòu)也可以通過一張圖來展示

image.png

block的變量捕獲

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

局部變量

auto變量

上述代碼中我們已經(jīng)了解過block對age變量的捕獲气忠。 auto自動變量,離開作用域就銷毀未桥,通常局部變量前面自動添加auto關(guān)鍵字笔刹。自動變量會捕獲到block內(nèi)部,也就是說block內(nèi)部會專門新增加一個參數(shù)來存儲變量的值冬耿。 auto只存在于局部變量中舌菜,訪問方式為值傳遞,通過上述對age參數(shù)的解釋我們也可以確定確實是值傳遞亦镶。

static變量

static 修飾的變量為指針傳遞日月,同樣會被block捕獲。

接下來分別添加aotu修飾的局部變量和static修飾的局部變量缤骨,重看源碼來看一下他們之間的差別爱咬。

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;
}
// log : block本質(zhì)[57465:18555229] hello, a = 10, b = 2
// block中a的值沒有被改變而b的值隨外部變化而變化。

重新生成c++代碼看一下內(nèi)部結(jié)構(gòu)中兩個參數(shù)的區(qū)別绊起。

image.png

從上述源碼中可以看出精拟,a,b兩個變量都有捕獲到block內(nèi)部。但是a傳入的是值虱歪,而b傳入的則是地址蜂绎。

為什么兩種變量會有這種差異呢?
因為自動變量可能會銷毀,block在執(zhí)行的時候有可能自動變量已經(jīng)被銷毀了笋鄙,那么此時如果再去訪問被銷毀的地址肯定會發(fā)生壞內(nèi)存訪問师枣,因此對于自動變量一定是值傳遞而不可能是指針傳遞了。
靜態(tài)變量不會被銷毀萧落,所以完全可以傳遞地址践美。而因為傳遞的是值得地址洗贰,所以在block調(diào)用之前修改地址中保存的值,block中的地址是不會變得陨倡。所以值會隨之改變敛滋。

全局變量

我們同樣以代碼的方式看一下block是否捕獲全局變量

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

同樣生成c++代碼查看全局變量調(diào)用方式

image.png

通過上述代碼可以發(fā)現(xiàn),__main_block_imp_0并沒有添加任何變量.
因此block不需要捕獲全局變量玫膀,因為全局變量無論在哪里都可以訪問矛缨。
局部變量因為跨函數(shù)訪問所以需要捕獲,全局變量在哪里都可以訪問 帖旨,所以不用捕獲。

最后以一張圖做一個總結(jié)

image.png
總結(jié):局部變量都會被block捕獲灵妨,自動變量是值捕獲解阅,靜態(tài)變量為地址捕獲。全局變量則不會被block捕獲

疑問:以下代碼中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)

image.png

上圖中可以發(fā)現(xiàn)货抄,self同樣被block捕獲,接著我們找到test方法可以發(fā)現(xiàn)朱转,test方法默認傳遞了兩個參數(shù)self和_cmd蟹地。而類方法test2也同樣默認傳遞了類對象self和方法選擇器_cmd。

image.png

不論對象方法還是類方法都會默認將self作為參數(shù)傳遞給方法內(nèi)部藤为,既然是作為參數(shù)傳入怪与,那么self肯定是局部變量。上面講到局部變量肯定會被block捕獲缅疟。

接著我們來看一下如果在block中使用成員變量或者調(diào)用實例的屬性會有什么不同的結(jié)果分别。

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

上圖中可以發(fā)現(xiàn),即使block中使用的是實例對象的屬性存淫,block中捕獲的仍然是實例對象耘斩,并通過實例對象通過不同的方式去獲取使用到的屬性

block的類型

block對象是什么類型的桅咆,之前稍微提到過括授,通過源碼可以知道block中的isa指針指向的是_NSConcreteStackBlock類對象地址。那么block是否就是_NSConcreteStackBlock類型的呢岩饼?

我們通過代碼用class方法或者isa指針查看具體類型荚守。

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;
}

打印內(nèi)容:

image.png

從上述打印內(nèi)容可以看出block最終都是繼承自NSBlock類型奔誓,而NSBlock繼承于NSObjcet。那么block其中的isa指針其實是來自NSObject中的。這也更加印證了block的本質(zhì)其實就是OC對象澎灸。

block的3種類型

__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
image.png

通過代碼查看一下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 class], [block2 class], [^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}

通過打印內(nèi)容確實可以發(fā)現(xiàn)block的三種類型

image.png

但是我們上面提到過,上述代碼轉(zhuǎn)化為c++代碼查看源碼時卻發(fā)現(xiàn)block的類型與打印出來的類型不一樣爽撒,c++源碼中三個block的isa指針全部都指向_NSConcreteStackBlock類型地址。

我們可以猜測runtime運行時過程中也許對類型進行了轉(zhuǎn)變腊徙。最終類型當然以runtime運行時類型也就是我們打印出的類型為準

block在內(nèi)存中的存儲

通過下面一張圖看一下不同block的存放區(qū)域

image.png

上圖中可以發(fā)現(xiàn)檬某,根據(jù)block的類型不同撬腾,block存放在不同的區(qū)域中。
__NSGlobalBlock__(數(shù)據(jù)段中)直到程序結(jié)束才會被回收恢恼,不過我們很少使用到__NSGlobalBlock__類型的block民傻,因為這樣使用block并沒有什么意義。

__NSStackBlock__類型的block存放在棧中场斑,我們知道棧中的內(nèi)存由系統(tǒng)自動分配和釋放漓踢,作用域執(zhí)行完畢之后就會被立即釋放,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉漏隐。

__NSMallocBlock__是在平時編碼過程中最常使用到的喧半。存放在堆中需要我們自己進行內(nèi)存管理。

block是如何定義其類型

block是如何定義其類型青责,依據(jù)什么來為block定義不同的類型并分配在不同的空間呢挺据?首先看下面一張圖

image.png

接著我們使用代碼驗證上述問題,首先關(guān)閉ARC回到MRC環(huán)境下脖隶,因為ARC會幫助我們做很多事情扁耐,可能會影響我們的觀察。

調(diào)試MRC環(huán)境.png
// MRC環(huán)境2濉M癯啤!
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Global:沒有訪問auto變量:__NSGlobalBlock__
        void (^block1)(void) = ^{
            NSLog(@"block1---------");
        };   
        // Stack:訪問了auto變量: __NSStackBlock__
        int a = 10;
        void (^block2)(void) = ^{
            NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@ %@", [block1 class], [block2 class]);
        // __NSStackBlock__調(diào)用copy : __NSMallocBlock__
        NSLog(@"%@", [[block2 copy] class]);
    }
    return 0;
}

查看打印內(nèi)容

image.png

通過打印的內(nèi)容可以發(fā)現(xiàn)正如上圖中所示心墅。

  • 沒有訪問auto變量的block是__NSGlobalBlock__類型的酿矢,存放在數(shù)據(jù)段中。
  • 訪問了auto變量的block是__NSStackBlock__類型的怎燥,存放在棧中瘫筐。
  • __NSStackBlock__類型的block調(diào)用copy成為__NSMallocBlock__類型并被復制存放在堆中。

上面提到過__NSGlobalBlock__類型的我們很少使用到铐姚,因為如果不需要訪問外界的變量策肝,直接通過函數(shù)實現(xiàn)就可以了,不需要使用block隐绵。

但是__NSStackBlock__訪問了auto變量之众,并且是存放在棧中的,上面提到過依许,棧中的代碼在作用域結(jié)束之后內(nèi)存就會被銷毀棺禾,那么我們很有可能block內(nèi)存銷毀之后才去調(diào)用他,那樣就會發(fā)生問題峭跳,通過下面代碼可以證實這個問題膘婶。

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;
}

此時查看打印內(nèi)容

image.png

可以發(fā)現(xiàn)a的值變?yōu)榱瞬豢煽氐囊粋€數(shù)字缺前。為什么會發(fā)生這種情況呢?因為上述代碼中創(chuàng)建的block是__NSStackBlock__類型的悬襟,因此block是存儲在棧中的衅码,那么當test函數(shù)執(zhí)行完畢之后,棧內(nèi)存中block所占用的內(nèi)存已經(jīng)被系統(tǒng)回收脊岳,因此就有可能出現(xiàn)亂得數(shù)據(jù)逝段。查看其c++代碼可以更清楚的理解。

image.png

為了避免這種情況發(fā)生割捅,可以通過copy__NSStackBlock__類型的block轉(zhuǎn)化為__NSMallocBlock__類型的block奶躯,將block存儲在堆中,以下是修改后的代碼棺牧。

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

此時在打印就會發(fā)現(xiàn)數(shù)據(jù)正確

image.png

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

image.png

所以在平時開發(fā)過程中MRC環(huán)境下經(jīng)常需要使用copy來保存block颊乘,將棧上的block拷貝到堆中,即使棧上的block被銷毀醉锄,堆上的block也不會被銷毀乏悄,需要我們自己調(diào)用release操作來銷毀。而在ARC環(huán)境下系統(tǒng)會自動調(diào)用copy操作恳不,使block不會被銷毀檩小。

ARC幫我們做了什么

在ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block進行一次copy操作烟勋,將block復制到堆上规求。

什么情況下ARC會自動將block進行一次copy操作? 以下代碼都在RAC環(huán)境下執(zhí)行卵惦。

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

typedef void (^Block)(void);
Block myblock()
{
    int a = 10;
    // 上文提到過阻肿,block中訪問了auto變量,此時block類型應為__NSStackBlock__
    Block block = ^{
        NSLog(@"---------%d", a);
    };
    return block;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = myblock();
        block();
       // 打印block類型為 __NSMallocBlock__
        NSLog(@"%@",[block class]);
    }
    return 0;
}

看一下打印的內(nèi)容

image.png

上文提到過沮尿,如果在block中訪問了auto變量時丛塌,block的類型為__NSStackBlock__,上面打印內(nèi)容發(fā)現(xiàn)blcok為__NSMallocBlock__類型的畜疾,并且可以正常打印出a的值赴邻,說明block內(nèi)存并沒有被銷毀。

上面提到過啡捶,block進行copy操作會轉(zhuǎn)化為__NSMallocBlock__類型姥敛,來講block復制到堆中,那么說明ARC在 block作為函數(shù)返回值時會自動幫助我們對block進行copy操作瞎暑,以保存block彤敛,并在適當?shù)牡胤竭M行release操作与帆。

2. 將block賦值給__strong指針時

block被強指針引用時,ARC也會自動對block進行一次copy操作臊泌。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // block內(nèi)沒有訪問auto變量
        Block block = ^{
            NSLog(@"block---------");
        };
        NSLog(@"%@",[block class]);
        int a = 10;
        // block內(nèi)訪問了auto變量鲤桥,但沒有賦值給__strong指針
        NSLog(@"%@",[^{
            NSLog(@"block1---------%d", a);
        } class]);
        // block賦值給__strong指針
        Block block2 = ^{
          NSLog(@"block2---------%d", a);
        };
        NSLog(@"%@",[block1 class]);
    }
    return 0;
}

查看打印內(nèi)容可以看出,當block被賦值給__strong指針時渠概,ARC會自動進行一次copy操作茶凳。

image.png

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(), ^{

});

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);

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

參考文章:https://juejin.im/post/5b0181e15188254270643e88

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雇庙,隨后出現(xiàn)的幾起案子谓形,更是在濱河造成了極大的恐慌,老刑警劉巖疆前,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寒跳,死亡現(xiàn)場離奇詭異,居然都是意外死亡竹椒,警方通過查閱死者的電腦和手機童太,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胸完,“玉大人书释,你說我怎么就攤上這事∩蘅” “怎么了爆惧?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長誓琼。 經(jīng)常有香客問我检激,道長,這世上最難降的妖魔是什么腹侣? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任叔收,我火速辦了婚禮,結(jié)果婚禮上傲隶,老公的妹妹穿的比我還像新娘饺律。我一直安慰自己,他們只是感情好跺株,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布复濒。 她就那樣靜靜地躺著脖卖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巧颈。 梳的紋絲不亂的頭發(fā)上畦木,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機與錄音砸泛,去河邊找鬼十籍。 笑死,一個胖子當著我的面吹牛唇礁,可吹牛的內(nèi)容都是我干的勾栗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼盏筐,長吁一口氣:“原來是場噩夢啊……” “哼围俘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琢融,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤界牡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后漾抬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欢揖,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年奋蔚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烈钞。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡泊碑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出毯欣,到底是詐尸還是另有隱情馒过,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布酗钞,位于F島的核電站腹忽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏砚作。R本人自食惡果不足惜窘奏,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望葫录。 院中可真熱鬧着裹,春花似錦、人聲如沸米同。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至少孝,卻和暖如春继低,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稍走。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工袁翁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钱磅。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓梦裂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盖淡。 傳聞我的和親對象是個殘疾皇子年柠,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

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