iOS Block(1)-底層原理探索、block的類(lèi)型和copy

1. block的本質(zhì)

我們通過(guò)一個(gè)簡(jiǎn)單的demo,解析一下block的底層原理.
定義一個(gè)簡(jiǎn)單的block并調(diào)用:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ^(){
            NSLog(@"Hello world, I'm block!");
        }();
    }
    return 0;
}

將OC代碼轉(zhuǎn)換成C++代碼

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_a2e15e_mi_0);

        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
        
    }
    return 0;
}
//我們OC下的block相關(guān)代碼
 ^(){
        NSLog(@"Hello world, I'm block!");
    }();

裝換成的C++代碼就是:
   ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();

block的調(diào)用實(shí)際上就是__main_block_impl_0這個(gè)結(jié)構(gòu)體屈扎,結(jié)構(gòu)體實(shí)現(xiàn)如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0結(jié)構(gòu)體內(nèi)部有一個(gè)與結(jié)構(gòu)體同名的__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)函數(shù),這是C++結(jié)構(gòu)體的寫(xiě)法撩匕,該函數(shù)為結(jié)構(gòu)體的構(gòu)造函數(shù)鹰晨,相當(dāng)于OC類(lèi)中的- (instancetype)init;方法。__main_block_impl_0函數(shù)攜帶三個(gè)參數(shù)止毕,最后一個(gè)參數(shù)為可選的模蜡,默認(rèn)值為0。再看結(jié)構(gòu)體__main_block_impl_0扁凛,發(fā)現(xiàn)其第一個(gè)成員imp也是個(gè)結(jié)構(gòu)體忍疾,結(jié)構(gòu)體類(lèi)型為__block_impl

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

通過(guò)結(jié)構(gòu)體__block_impl實(shí)現(xiàn)代碼中的isa指針谨朝,顯而易見(jiàn)這是個(gè)對(duì)象卤妒,因此可以準(zhǔn)確地說(shuō)block的本質(zhì)是一個(gè)OC對(duì)象。結(jié)構(gòu)體的第二個(gè)成員仍然是個(gè)__main_block_desc_0類(lèi)型的結(jié)構(gòu)體.

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

該結(jié)構(gòu)體兩個(gè)成員一個(gè)是系統(tǒng)的保留值reserved = 0字币,另一個(gè)Block_size則代表了該block的大小则披。
接下來(lái)回到block的調(diào)用函數(shù)__main_block_impl_0,((void (*)())&__main_block_impl_0((void*)__main_block_func_0,&__main_block_desc_0_DATA))();該函數(shù)就是結(jié)構(gòu)體__main_block_impl_0的構(gòu)造函數(shù)__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0),它有兩個(gè)必傳的參數(shù)洗出,一個(gè)是函數(shù)指針fp ,一個(gè)是結(jié)構(gòu)體指針desc士复,關(guān)于結(jié)構(gòu)體指針?biāo)赶虻慕Y(jié)構(gòu)體就是上面分析到的__main_block_desc_0,那么第一個(gè)參數(shù)函數(shù)指針fp到底是什么?在這個(gè)demo的C++實(shí)現(xiàn)代碼中翩活,fp指向的函數(shù)為__main_block_func_0阱洪,__main_block_func_0的函數(shù)實(shí)現(xiàn)代碼如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_a2e15e_mi_0);
}

由于在OC代碼中便贵,我們block體內(nèi)打印了一個(gè)字符串,與這個(gè)__main_block_func_0函數(shù)內(nèi)的代碼完全一致冗荸。研究發(fā)現(xiàn)__main_block_func_0這個(gè)函數(shù)的作用就是將block體內(nèi)的代碼封裝成一個(gè)函數(shù)嫉沽,也就是說(shuō)block體內(nèi)的所有OC代碼被封裝成__main_block_func_0這個(gè)函數(shù)。與我們OC中的代碼NSLog(@"Hello world, I'm block!");相對(duì)應(yīng)的就是NSLog((NSString*)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_a2e15e_mi_0);.
通過(guò)上面的分析俏竞,可以肯定的是block本質(zhì)上也是一個(gè)OC對(duì)象,它內(nèi)部也有一個(gè)isa指針堂竟,block還是一個(gè)封裝了函數(shù)的調(diào)用的OC對(duì)象魂毁。

2. block的變量捕獲(capture)

一. 局部變量之a(chǎn)uto變量

什么是auto變量?局部變量有哪幾種出嘹?
所謂的auto變量就是非靜態(tài)的局部變量席楚,離開(kāi)作用于就會(huì)銷(xiāo)毀。例如下面這個(gè)函數(shù):

- (void)example{
   int a = 5;  //等價(jià)于auto int a = 5;
   NSString *name = @"Block"; //等價(jià)于 auto NSString *name = @"Block";
   static int b = 10;  //這個(gè)b就不是auto變量
}

常識(shí)小結(jié):通常情況下我們定義的局部非static變量都是auto變量税稼,系統(tǒng)會(huì)默認(rèn)在前面加上auto關(guān)鍵字的烦秩;但是靜態(tài)局部變量就不會(huì)有auto前綴,加了也會(huì)由于報(bào)錯(cuò)而編譯不通過(guò)郎仆。
為了保證block內(nèi)部能夠正常訪(fǎng)問(wèn)外部的變量只祠,block有個(gè)變量捕獲機(jī)制,這個(gè)變量捕獲機(jī)制之后block變成什么樣子?:

//demoA
#import <Foundation/Foundation.h>

typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        Block block = ^(){
            NSLog(@"你好世界扰肌!a = : %d ;",a);
        };
        
        a = 20;
        block();
//        2018-08-28 21:34:33.276996+0800 Interview03-block[99340:9151961] 你好世界抛寝!a = : 10 ;
        
    }
    return 0;
}

上面demo,block內(nèi)部訪(fǎng)問(wèn)局部變量a的值曙旭,后面在調(diào)用block之前修改了a的值盗舰,但是打印出來(lái)的a的結(jié)果仍然為修改之前的值,將上邊的代碼轉(zhuǎn)換成C++代碼:

typedef void(*Block)(void);


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_09a2a6_mi_0,a);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;

        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

        a = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}

對(duì)比之前沒(méi)有訪(fǎng)問(wèn)任何變量的block結(jié)構(gòu)體,此時(shí)的block所對(duì)應(yīng)的結(jié)構(gòu)體__main_block_impl_0里面多了一個(gè)成員int a桂躏,并且結(jié)構(gòu)體的構(gòu)造函數(shù)__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a)多出了一個(gè)參數(shù)_a钻趋,(知識(shí)點(diǎn):后面的: a(_a)為C++的語(yǔ)法弯蚜,意為將參數(shù)_a賦值給成員a)侠鳄。
在實(shí)現(xiàn)block的時(shí)候勺馆,對(duì)應(yīng)的C++代碼為Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA, a));可見(jiàn)轻局,系統(tǒng)將a作為函數(shù)__main_block_impl_0的參數(shù)傳遞進(jìn)去雁竞,所以block所對(duì)應(yīng)的結(jié)構(gòu)體中int a纽窟;這個(gè)成員所對(duì)應(yīng)的值a = 10;后面我們修改了a的值為20酒来,并使用block();調(diào)用block 打印a的值啡浊,這個(gè)時(shí)候調(diào)用了函數(shù)__main_block_func_0(struct __main_block_impl_0 *__cself)猾昆,實(shí)現(xiàn)如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_09a2a6_mi_0,a);
 }

其內(nèi)部訪(fǎng)問(wèn)變量a的方式為:int a = __cself->a;__cself為block所對(duì)應(yīng)的結(jié)構(gòu)體對(duì)象陶因,所以這個(gè)a也就是之前結(jié)構(gòu)體__main_block_impl_0中保存的成員變量a的值,即為10垂蜗,而不是后面修改的20楷扬。針對(duì)這個(gè)問(wèn)題解幽,我的看法是block在調(diào)用的時(shí)候,其實(shí)此時(shí)main()函數(shù)中的a變量相對(duì)于block來(lái)說(shuō)是個(gè)外部的變量烘苹,因?yàn)閎lock對(duì)應(yīng)的結(jié)構(gòu)體內(nèi)部有自己的變量a躲株,外面怎么修改不會(huì)影響到block結(jié)構(gòu)體內(nèi)部成員a的值。

二. 局部變量之static變量

根據(jù)demoA镣衡,我們?cè)赿emoB中中block內(nèi)部增加訪(fǎng)問(wèn)靜態(tài)的局部變量static int b以及修改a霜定、b變量的值后,調(diào)用block打印的結(jié)果:

//demoB
#import <Foundation/Foundation.h>

typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        auto NSString *name = @"Block";
        int a = 10;
        static int b = 10;
        Block block = ^(){
            NSLog(@"你好世界廊鸥!a = : %d ;",a);
            NSLog(@"你好世界望浩!b = : %d ;",b);
        };
        
        a = 20;
        b = 20;
        block();
//        2018-08-28 23:16:53.244791+0800 Interview03-block[861:9731638] 你好世界!a = : 10 ;
//        2018-08-28 23:16:53.245153+0800 Interview03-block[861:9731638] 你好世界惰说!b = : 20 ;
        
    }
    return 0;
}

發(fā)現(xiàn)局部靜態(tài)變量b修改之后磨德,block內(nèi)部打印的結(jié)果也變了!
局部變量a的訪(fǎng)問(wèn)過(guò)程demoA已經(jīng)分析過(guò)了,接下來(lái)仍舊通過(guò)C++代碼研究局部靜態(tài)變量b的捕獲過(guò)程:

typedef void(*Block)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_0b7d13_mi_0,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_0b7d13_mi_1,(*b));
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
        static int b = 10;
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));

        a = 20;
        b = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    }
    return 0;
}

通過(guò)C++代碼發(fā)現(xiàn)吆视,局部自動(dòng)變量a與靜態(tài)變量b的捕獲方式不同典挑,block結(jié)構(gòu)體中,aint變量啦吧,bint *變量您觉,也就是指針。在定義block的時(shí)候授滓,Block block = ((void (*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, a, &b))顾犹,傳遞的也是b變量的指針,調(diào)用block的時(shí)候褒墨,__main_block_func_0中獲取b也是通過(guò)block的結(jié)構(gòu)體__main_block_impl_0訪(fǎng)問(wèn)內(nèi)部成員變量b炫刷,與結(jié)構(gòu)體外部變量b指向的是同一塊內(nèi)存地址,所以只要有地方修改b郁妈,結(jié)構(gòu)體內(nèi)部也會(huì)跟隨變化浑玛,這樣就解釋了為啥“同樣修改了局部auto變量與局部static變量,block訪(fǎng)問(wèn)的結(jié)果不同”噩咪。
總而言之:在block內(nèi)部訪(fǎng)問(wèn)的auto變量為值傳遞顾彰,局部靜態(tài)變量為引用傳遞(也就是傳遞變量的指針)。

三. 全局變量

\\demoB
#import <Foundation/Foundation.h>

typedef void(^Block)(void);

int age_ = 25;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        static int b = 10;
        Block block = ^(){
            NSLog(@"你好世界胃碾!a = : %d ;",a);
            NSLog(@"你好世界涨享!b = : %d ;",b);
            NSLog(@"你好世界!age_ = : %d ;",age_);
        };
        
        a = 20;
        b = 20;
        age_ = 26;
        block();
//        2018-08-29 00:54:13.318712+0800 Interview03-block[2155:10283110] 你好世界仆百!a = : 10 ;
//        2018-08-29 00:54:13.319099+0800 Interview03-block[2155:10283110] 你好世界厕隧!b = : 20 ;
//        2018-08-29 00:54:13.319130+0800 Interview03-block[2155:10283110] 你好世界!age_ = : 26 ;

    }
    return 0;
}

block內(nèi)部訪(fǎng)問(wèn)全局變量age_,其變化同靜態(tài)局部變量一樣吁讨。同樣轉(zhuǎn)換成C++代碼分析:

typedef void(*Block)(void);

int age_ = 25;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_d8c19f_mi_0,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_d8c19f_mi_1,(*b));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_d8c19f_mi_2,age_);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int a = 10;
        static int b = 10;
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));

        a = 20;
        b = 20;
        age_ = 26;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

較demoA髓迎、demoB不同的是,block結(jié)構(gòu)體內(nèi)部沒(méi)有定義age_變量建丧,block內(nèi)部訪(fǎng)問(wèn)age_變量的時(shí)候排龄,傳入的也是全局的age_,因此在任何地方改變這個(gè)全局變量翎朱,block訪(fǎng)問(wèn)的時(shí)候都是這個(gè)全局變量的最新值橄维。
通過(guò)demoA\B\C,可以肯定對(duì)于局部auto變量拴曲、static變量争舞、全局變量,block的變量捕獲情況如下:

block變量捕獲機(jī)制.png

分析了block對(duì)自動(dòng)變量疗韵,static變量與全局變量的捕獲方式的不同,我認(rèn)為合理的解釋是:自動(dòng)變量侄非,內(nèi)存可能會(huì)銷(xiāo)毀蕉汪,將來(lái)執(zhí)行block的時(shí)候,訪(fǎng)問(wèn)變量的內(nèi)存逞怨,可能會(huì)因?yàn)椴淮嬖谝l(fā)壞內(nèi)存訪(fǎng)問(wèn)者疤。
靜態(tài)局部變量:static變量?jī)?nèi)存一直會(huì)保存在內(nèi)存中,所以可以取它的最新值叠赦,也就是通過(guò)指針去取驹马。

3. block的類(lèi)型

block有3種類(lèi)型,可以通過(guò)調(diào)用class方法或者isa指針查看具體類(lèi)型除秀,最終都是繼承自NSBlock類(lèi)型.
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )

我們通過(guò)關(guān)鍵字可以知道這三種類(lèi)型block分別存放在內(nèi)存的全局區(qū)糯累、棧區(qū)、堆區(qū)册踩,在內(nèi)存中對(duì)應(yīng)的區(qū)域圖示如下:

三種block在應(yīng)用程序內(nèi)存分配.png

程序區(qū)域存放的就是我們寫(xiě)的代碼泳姐,比如一個(gè)Person類(lèi)里面的代碼。
數(shù)據(jù)區(qū)也就全局區(qū)暂吉,存放著程序中使用到的全局變量胖秒。
堆存放的就是我們新建的對(duì)象。如[[Person alloc] init]出來(lái)的慕的,這部分內(nèi)存需要我們手動(dòng)釋放阎肝。
棧區(qū)存放的就是自動(dòng)變量,一般在函數(shù)調(diào)用之后肮街,這些自動(dòng)變量所占用內(nèi)存也就被系統(tǒng)回收了风题。
補(bǔ)充的知識(shí)
堆是動(dòng)態(tài)分配內(nèi)存的,是程序員管理的,自己申請(qǐng)內(nèi)存,自己管理內(nèi)存.
棧是系統(tǒng)會(huì)自動(dòng)分配內(nèi)存,自己釋放內(nèi)存.

由于在ARC環(huán)境下,編譯器為我們做了很多額外的工作,比如將棧區(qū)的block copy到堆區(qū)俯邓,我們?cè)贏RC下也就不容易捕獲到block初始狀態(tài)的位置骡楼。所以暫時(shí)將開(kāi)發(fā)環(huán)境切換至MRC下:

把開(kāi)發(fā)環(huán)境切換至MRC.jpg

在MRC下,定義兩個(gè)block稽鞭,一個(gè)訪(fǎng)問(wèn)auto變量鸟整,一個(gè)不訪(fǎng)問(wèn)auto變量,最后對(duì)訪(fǎng)問(wèn)auto變量的block調(diào)用copy方法朦蕴,依次查看三種情況下block所對(duì)應(yīng)的類(lèi)型如下:

#import <Foundation/Foundation.h>

typedef void(^Block)(void);

int age_ = 25;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int a = 10;
        static int b = 10;
        Block block = ^(){
            NSLog(@"你好世界篮条!a = : %d ;",a);
        };
        
        Block block1 = ^(){
            NSLog(@"你好世界");
        };
        NSLog(@"%@  %@  %@",[block class],[block1 class],[[block copy] class]);
        
//         __NSStackBlock__  __NSGlobalBlock__  __NSMallocBlock__

    }
    return 0;
}

訪(fǎng)問(wèn)了auto變量的block在棧區(qū),不訪(fǎng)問(wèn)auto變量的block在全局區(qū)吩抓。對(duì)棧區(qū)的block調(diào)用copy方法涉茧,block居然移到了堆區(qū)!后面我們對(duì)全局區(qū)的block調(diào)用copy疹娶,發(fā)現(xiàn)全局區(qū)域的block仍舊在全局區(qū)伴栓。

block類(lèi)型 環(huán)境
NSGlobalBlock 沒(méi)有訪(fǎng)問(wèn)auto變量
NSStackBlock 訪(fǎng)問(wèn)了auto變量
NSMallocBlock NSStackBlock調(diào)用了copy

stackBlock為什么要copy到堆上,因?yàn)槲覀兿氚裝lock存儲(chǔ)的東西保存下來(lái).棧上的block會(huì)在函數(shù)結(jié)束的時(shí)候釋放,block保存的東西不確定,我們一般都是將block保存下來(lái),在恰當(dāng)?shù)臅r(shí)候調(diào)用.

每一種類(lèi)型的block調(diào)用copy后的結(jié)果.png

4. block的copy

在ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,有以下情況:

  1. block作為函數(shù)返回值的時(shí).
  2. 將block賦值給_strong指針時(shí).
  3. block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí).(例如:[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];)
  4. block作為GCD API的方法參數(shù)時(shí)
  • MRC下block屬性的建議寫(xiě)法
    @property (copy, nonatomic) void (^block)(void);
  • ARC下block屬性的建議寫(xiě)法
    @property (strong, nonatomic) void (^block)(void);
    @property (copy, nonatomic) void (^block)(void);

那么問(wèn)題來(lái)了,為什么要對(duì)block進(jìn)行copy操作?
假如在MRC環(huán)境下雨饺,在某個(gè)函數(shù)內(nèi)定義了一個(gè)block變量钳垮,并在block中訪(fǎng)問(wèn)了局部變量,但是并沒(méi)有立即調(diào)用該block额港。后面等到調(diào)用該函數(shù)的時(shí)候饺窿,再調(diào)用block,看下面的demo:

MRC下未使用copy.png

調(diào)用block后移斩,block內(nèi)部訪(fǎng)問(wèn)的局部變量打印的結(jié)果很糟糕肚医,程序倒是沒(méi)奔潰,但是結(jié)果不如人所愿向瓷。
出現(xiàn)這種情況的原因很好理解:由于這個(gè)block訪(fǎng)問(wèn)了auto變量肠套,因此是一個(gè)NSStackBlock類(lèi)型的block,該block對(duì)應(yīng)的結(jié)構(gòu)體分配在棧內(nèi)存上猖任,等到test()函數(shù)調(diào)用完畢糠排,棧內(nèi)存會(huì)被回收,所以block被調(diào)用的時(shí)候超升,訪(fǎng)問(wèn)block結(jié)構(gòu)體內(nèi)部的變量a,a所對(duì)應(yīng)的內(nèi)存區(qū)域隨時(shí)可能被系統(tǒng)回收入宦,其內(nèi)存上的數(shù)據(jù)也是不確定的。
這種情況該如何保證我們調(diào)用block的時(shí)候室琢,還能正常訪(fǎng)問(wèn)局部變量呢乾闰?正如前面列出的,調(diào)用copy方法將block從棧區(qū)copy到堆區(qū)盈滴,事情就解決了涯肩〗文疲【當(dāng)然,換成ARC環(huán)境病苗,我們通常在聲明block屬性的時(shí)候疗垛,使用copystrong關(guān)鍵詞修飾,系統(tǒng)也會(huì)自動(dòng)幫我們將block從棧區(qū)拷貝到堆區(qū)硫朦。也就無(wú)需我們動(dòng)手調(diào)用block的copy方法了贷腕。但是系統(tǒng)底層還是幫我們對(duì)block做了copy操作。

MRC下未使用copy.png

"copy"這個(gè)操作在ARC下是沒(méi)有必要的咬展。由于我們的block賦值給了void(^block)(void)泽裳,這個(gè)變量默認(rèn)是__strong修飾的,滿(mǎn)足編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上的條件2破婆,即"將block賦值給__strong指針時(shí)"涮总。

                            想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祷舀,隨后出現(xiàn)的幾起案子瀑梗,更是在濱河造成了極大的恐慌,老刑警劉巖裳扯,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抛丽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嚎朽,警方通過(guò)查閱死者的電腦和手機(jī)铺纽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)柬帕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哟忍,“玉大人,你說(shuō)我怎么就攤上這事陷寝」埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵凤跑,是天一觀的道長(zhǎng)爆安。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仔引,這世上最難降的妖魔是什么扔仓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮咖耘,結(jié)果婚禮上翘簇,老公的妹妹穿的比我還像新娘。我一直安慰自己儿倒,他們只是感情好版保,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般彻犁。 火紅的嫁衣襯著肌膚如雪叫胁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天汞幢,我揣著相機(jī)與錄音驼鹅,去河邊找鬼。 笑死急鳄,一個(gè)胖子當(dāng)著我的面吹牛谤民,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疾宏,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼张足,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了坎藐?” 一聲冷哼從身側(cè)響起为牍,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岩馍,沒(méi)想到半個(gè)月后碉咆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛀恩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年疫铜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片双谆。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壳咕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顽馋,到底是詐尸還是另有隱情谓厘,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布寸谜,位于F島的核電站竟稳,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏熊痴。R本人自食惡果不足惜他爸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望果善。 院中可真熱鬧诊笤,春花似錦、人聲如沸岭埠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至许赃,卻和暖如春止喷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背混聊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工弹谁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人句喜。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓预愤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咳胃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子植康,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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