iOS Block從入門(mén)到完全詳解

歡迎大家關(guān)注我的個(gè)人博客:https://darkknightkazuma.github.io

一、Block基礎(chǔ)介紹

1厨埋、概念介紹

Block又稱為塊或塊對(duì)象,它是蘋(píng)果在OSX10.6和iOS4.0中新加入的功能,是C語(yǔ)言層面的特性及功能實(shí)現(xiàn),類似其它語(yǔ)言的閉包(closure)功能.當(dāng)時(shí)蘋(píng)果正使用LLVM的clang作為C語(yǔ)言的編譯器來(lái)改進(jìn)C/OC/C++/OC++等的編譯處理,Block也是當(dāng)時(shí)的重要成果.

2谤绳、塊的定義及語(yǔ)法

Block是帶有自動(dòng)變量(局部變量)的匿名函數(shù).因?yàn)榈讓咏Y(jié)構(gòu)體實(shí)現(xiàn)有isa指針,也被看作是塊對(duì)象.Block的出現(xiàn)其實(shí)是為了代替指針函數(shù)的一種語(yǔ)法結(jié)構(gòu),之前傳遞狀態(tài)需要使用不透明的void指針來(lái)傳遞狀態(tài),而B(niǎo)lock把C語(yǔ)言特性所編寫(xiě)的代碼封裝成簡(jiǎn)明且易用的接口.

下面是Block的實(shí)現(xiàn)語(yǔ)法結(jié)構(gòu):
block的語(yǔ)法是脫字符^加花括號(hào),花括號(hào)里是塊的實(shí)現(xiàn).塊可以當(dāng)成是變量的值使用.如下:

^{
    //Block implementation
}

Block類型的聲明語(yǔ)法結(jié)構(gòu):

return_type (^block_name)(parameters)
//中間的block_name可以看作是變量名

Block的調(diào)用:

block_name(parameters)

二退渗、Block的關(guān)于捕獲變量的底層實(shí)現(xiàn)

在塊聲明的范圍里,所有變量都可以為其所捕獲.捕獲的自動(dòng)變量不可修改,修改要加__block前綴.
塊總能修改實(shí)例變梢卸、靜態(tài)變量累魔、全局變量摔笤、全局靜態(tài)變量,無(wú)需加__block.

我們經(jīng)常會(huì)看到block相關(guān)的文章或面試題中有這些內(nèi)容.那么Block底層實(shí)現(xiàn)是怎么樣的呢?

1、我們首先來(lái)看一下Block的內(nèi)存布局,如下:
/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved; //預(yù)留內(nèi)存大小
    unsigned long int size; //塊大小
    void (*copy)(void *dst, void *src); //指向拷貝函數(shù)的函數(shù)指針
    void (*dispose)(void *); //指向釋放函數(shù)的函數(shù)指針
};


struct Block_layout {
    void *isa; //指向Class對(duì)象
    int flags; //狀態(tài)標(biāo)志位
    int reserved; //預(yù)留內(nèi)存大小
    void (*invoke)(void *, ...); //指向塊實(shí)現(xiàn)的函數(shù)指針
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

其中最重要的就是invoke函數(shù)指針和descriptor塊的描述.invoke函數(shù)指針?biāo)赶蛄藟K的實(shí)現(xiàn),它的void*參數(shù)傳入的是塊的結(jié)構(gòu)體. descriptor的結(jié)構(gòu)體中包含了塊大小以及兩個(gè)重要的輔助函數(shù)指針等.我們注意到塊的布局中,最下面一部分是捕獲到的變量,前面提到的void*參數(shù)傳入塊的結(jié)構(gòu)體也是為了拿到捕獲到的變量.那么下面讓我們來(lái)看一下塊的實(shí)際源碼是什么樣的:

cd到目錄下,在終端通過(guò) clang -rewrite-objc 文件名 的方法將文件轉(zhuǎn)換為C/C++代碼.如果發(fā)現(xiàn)不能轉(zhuǎn)換需要下載插件,指令為xcode-select --install.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int ivar = 1;
        void (^tmpBlock)(void) = ^{
            NSLog(@"tmpBlock:%d",ivar);
        };
        tmpBlock();
    }
    return 0;
}

轉(zhuǎn)換后點(diǎn)開(kāi)目錄下的.cpp文件,會(huì)看到上萬(wàn)行的代碼,屏蔽掉無(wú)用的代碼,直接找到主要代碼如下:

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;
  int ivar;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _ivar, int flags=0) : ivar(_ivar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int ivar = __cself->ivar; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_459ed6_mi_2,ivar);
        }

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 ivar = 1;
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}

我們來(lái)挨個(gè)看一下:

__block_impl 結(jié)構(gòu)體是包含我們剛剛在Block布局中提到的前四個(gè)成員變量.

__main_block_impl_0結(jié)構(gòu)體是包含了__block_impl結(jié)構(gòu)體變量和__main_block_desc_0結(jié)構(gòu)體指針以及捕獲到的變量.最后是初始化構(gòu)造函數(shù)中對(duì)impl成員和desc進(jìn)行賦值.

__main_block_func_0這個(gè)函數(shù)他傳遞了__main_block_impl_0*型的參數(shù),并從中讀取塊結(jié)構(gòu)體中捕獲的參數(shù)進(jìn)行實(shí)際操作.看到這里你有個(gè)明白了這個(gè)就是對(duì)應(yīng)上面那個(gè)invoke函數(shù)指針?biāo)赶虻暮瘮?shù).

__main_block_desc_0這個(gè)結(jié)構(gòu)體就是上面所說(shuō)的塊的描述結(jié)構(gòu)體.可能你會(huì)發(fā)現(xiàn)他少了兩個(gè)輔助函數(shù)的函數(shù)指針,原因后面會(huì)說(shuō).

最后是main函數(shù)中的具體初始化和函數(shù)調(diào)用.剛好對(duì)應(yīng)塊變量的聲明實(shí)現(xiàn)部分和塊的調(diào)用.

2垦写、關(guān)于捕獲自動(dòng)變量的分析

捕獲自動(dòng)變量這部分首先我們要明確有哪幾種變量,如下:

  • auto自動(dòng)變量:默認(rèn)方法的作用域中不加前綴就是自動(dòng)變量,而且ARC下默認(rèn)還會(huì)加__strong.
  • static靜態(tài)變量:存放在內(nèi)存的可讀寫(xiě)區(qū),初始化只在第一次執(zhí)行時(shí)起作用吕世,運(yùn)行過(guò)程中語(yǔ)句塊變量將保持上一次執(zhí)行的值.
  • static全局靜態(tài)變量:也放在可讀寫(xiě)區(qū),作用域?yàn)楫?dāng)前文件.
  • 全局變量:也在可讀寫(xiě)區(qū),整個(gè)程序可用.
  • 另外在OC中它們又分為對(duì)象類型和非對(duì)象類型.

下面讓我們看一下他們?cè)贐lock中的表現(xiàn).
首先是前面那個(gè)是用自動(dòng)變量ivar的例子,如果我們修改它的值編譯器會(huì)報(bào)警告:

Variable is not assignable (missing __block type specifier)

提示我們不能修改變量,這是為什么呢?我們來(lái)打印一下block內(nèi)外的指針變量的地址:

    int ivar = 1;
        NSLog(@"塊外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            NSLog(@"塊內(nèi)%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:20:40.063273+0800 LearningDemo[69358:5789849] 塊外0x7ffeefbff4dc
2019-12-05 18:20:40.063840+0800 LearningDemo[69358:5789849] 塊內(nèi)0x103400ad0

你會(huì)發(fā)現(xiàn)它們不是同一個(gè)地址,是的,block的__main_block_impl_0結(jié)構(gòu)體拷貝了一份自動(dòng)變量進(jìn)去作為結(jié)構(gòu)體的成員變量,你修改的是結(jié)構(gòu)體內(nèi)部的ivar的值,而不是外部ivar的值,他們并不是同一塊內(nèi)存上的東西.蘋(píng)果讓編譯器在這種情況下報(bào)警告,提示開(kāi)發(fā)者是改不了的.
接下來(lái)我們給它加一個(gè)static前綴,如下:

        static int ivar = 1;
        NSLog(@"塊外%p",&ivar);
        void (^tmpBlock)(void) = ^{
            ivar = 2;
            NSLog(@"塊內(nèi)%p",&ivar);
        };
        tmpBlock();

2019-12-05 18:41:36.083804+0800 LearningDemo[69801:5806136] 塊外0x100003320
2019-12-05 18:41:36.084770+0800 LearningDemo[69801:5806136] 塊內(nèi)0x100003320

有意思的事情發(fā)生了,可以修改變量了,而且地址居然一樣了~那我們看一下轉(zhuǎn)換后的源碼:

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

            (*ivar) = 2;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_3,&(*ivar));
        }

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; 

        static int ivar = 1;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_2,&ivar);
        void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &ivar));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_532557_mi_4,tmpBlock);
    }
    return 0;
}

從上面的代碼可以看到,它捕獲到的變量變成了指針變量int *ivar,然后再看打印的代碼中的&(*ivar),它拿到的是指針變量中所存的地址,也就是說(shuō)結(jié)構(gòu)體捕獲的指針變量存儲(chǔ)的地址和Block外部的靜態(tài)變量是同一個(gè)地址!而B(niǎo)lock實(shí)現(xiàn)的賦值是通過(guò)*運(yùn)算符引用變量來(lái)賦值的,像這樣(*ivar) = 2;.到這里你應(yīng)該已經(jīng)明白為什么可以改變外部變量了吧,因?yàn)樗鼈冊(cè)L問(wèn)的是同一個(gè)內(nèi)存地址.總結(jié)一下就是地址訪問(wèn)是可以修改外部變量的. 那你可能會(huì)說(shuō),對(duì)象類型本質(zhì)上不就是一個(gè)指針變量嗎,為什么不能修改,我們以NSString*為例看一下它轉(zhuǎn)換后的實(shí)現(xiàn)源碼:

            NSString * strvar = @"strvar";
            NSLog(@"塊外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                NSLog(@"塊內(nèi)%p",&strvar);
            };
            tmpBlock();

2019-12-05 19:01:07.019544+0800 LearningDemo[69970:5816051] 塊外0x7ffeefbff4d8
2019-12-05 19:01:07.020200+0800 LearningDemo[69970:5816051] 塊內(nèi)0x100701780

從上面的結(jié)果可知,地址是不一樣的,為什么不一樣呢 看一下源碼就知道了

 NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_ca150a_mi_4,&strvar);

從源碼我們得知他取得的是指針變量的地址,而非指針變量存儲(chǔ)的地址,所以我們給對(duì)象類型賦值其實(shí)是賦值一個(gè)新的對(duì)象地址上去,而不是通過(guò)同一個(gè)地址去修改替換分配在堆上的對(duì)象. 當(dāng)我們加上static前綴之后發(fā)現(xiàn)可以修改了,其實(shí)是因?yàn)锽lock捕獲的是NSString **strvar;指針的指針,也就是存的是外部對(duì)象類型變量的地址而不是變量?jī)?nèi)存儲(chǔ)的地址,所以我們可以修改strvar了.

            NSMutableString * strvar = [NSMutableString stringWithFormat:@"NSMutableString"];
            NSLog(@"塊外%p",&strvar);
            void (^tmpBlock)(void) = ^{
                [strvar appendString:@"111"];
                NSLog(@"塊內(nèi)%p",&strvar);
            };
            NSLog(@"tmpBlock %@",strvar);
            tmpBlock();
            NSLog(@"tmpBlock %@",strvar);

2019-12-06 12:01:25.339194+0800 LearningDemo[81031:6194158] 塊外0x7ffeefbff4d8
2019-12-06 12:01:25.339677+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString
2019-12-06 12:01:25.339739+0800 LearningDemo[81031:6194158] 塊內(nèi)0x102841ef0
2019-12-06 12:01:25.339780+0800 LearningDemo[81031:6194158] tmpBlock NSMutableString111

你會(huì)發(fā)現(xiàn)如果是可變對(duì)象的話是可以通過(guò)方法來(lái)修改堆上的對(duì)象的,也就是說(shuō)只要可以通過(guò)同一個(gè)地址訪問(wèn)到變量就可以對(duì)其賦值修改等.

全局變量靜態(tài)全局變量因?yàn)樽饔糜蚝軓V,在它們的作用域里都可以訪問(wèn),所以并不需要捕獲,都可以Block內(nèi)修改,下面是測(cè)試代碼:

int globalVar = 1;
static int staticGlobalVar = 1;
NSString *globalStr = @"globalStr";
static NSString *staticGlobalStr = @"staticGlobalStr";

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"塊外%p",&globalVar);
        NSLog(@"塊外%p",&staticGlobalVar);
        NSLog(@"塊外%p",&globalStr);
        NSLog(@"塊外%p",&staticGlobalStr);
         void (^tmpBlock)(void) = ^{
             globalVar += 1;
             staticGlobalVar += 1;
             globalStr = @"修改globalStr";
             staticGlobalStr = @"修改staticGlobalStr";
             NSLog(@"塊內(nèi)%p",&globalVar);
             NSLog(@"塊內(nèi)%p",&staticGlobalVar);
             NSLog(@"塊內(nèi)%p",&globalStr);
             NSLog(@"塊內(nèi)%p",&staticGlobalStr);
         };
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
        tmpBlock();
        NSLog(@"globalVar: %d",globalVar);
        NSLog(@"staticGlobalVar: %d",staticGlobalVar);
        NSLog(@"globalStr: %@",globalStr);
        NSLog(@"staticGlobalStr: %@",staticGlobalStr);
    }
    return 0;
}
2019-12-06 13:54:10.126960+0800 LearningDemo[82231:6240152] 塊外0x100003308
2019-12-06 13:54:10.127585+0800 LearningDemo[82231:6240152] 塊外0x100003320
2019-12-06 13:54:10.127687+0800 LearningDemo[82231:6240152] 塊外0x100003310
2019-12-06 13:54:10.127738+0800 LearningDemo[82231:6240152] 塊外0x100003318
2019-12-06 13:54:10.127774+0800 LearningDemo[82231:6240152] globalVar: 1
2019-12-06 13:54:10.127802+0800 LearningDemo[82231:6240152] staticGlobalVar: 1
2019-12-06 13:54:10.127847+0800 LearningDemo[82231:6240152] globalStr: globalStr
2019-12-06 13:54:10.127891+0800 LearningDemo[82231:6240152] staticGlobalStr: staticGlobalStr
2019-12-06 13:54:10.127970+0800 LearningDemo[82231:6240152] 塊內(nèi)0x100003308
2019-12-06 13:54:10.128051+0800 LearningDemo[82231:6240152] 塊內(nèi)0x100003320
2019-12-06 13:54:10.128112+0800 LearningDemo[82231:6240152] 塊內(nèi)0x100003310
2019-12-06 13:54:10.128170+0800 LearningDemo[82231:6240152] 塊內(nèi)0x100003318
2019-12-06 13:54:10.128221+0800 LearningDemo[82231:6240152] globalVar: 2
2019-12-06 13:54:10.128271+0800 LearningDemo[82231:6240152] staticGlobalVar: 2
2019-12-06 13:54:10.128365+0800 LearningDemo[82231:6240152] globalStr: 修改globalStr
2019-12-06 13:54:10.128870+0800 LearningDemo[82231:6240152] staticGlobalStr: 修改staticGlobalStr

3、關(guān)于_ _block的底層實(shí)現(xiàn)原理

我們理解上面那些內(nèi)容之后接下來(lái)正式講解__block的原理:

                __block int ivar = 1;
                NSLog(@"塊外%p",&ivar);
                void (^tmpBlock)(void) = ^{
                    ivar += 1;
                    NSLog(@"塊內(nèi)%p",&ivar);
                };
                ivar += 1;
                NSLog(@"ivar1: %d",ivar);
                tmpBlock();
                NSLog(@"ivar2: %d",ivar);
                NSLog(@"tmpBlock %@",tmpBlock);

2019-12-06 14:23:05.294076+0800 LearningDemo[82616:6257418] 塊外0x7ffeefbff4d8
2019-12-06 14:23:05.294589+0800 LearningDemo[82616:6257418] ivar1: 2
2019-12-06 14:23:05.294673+0800 LearningDemo[82616:6257418] 塊內(nèi)0x102c00a78
2019-12-06 14:23:05.294726+0800 LearningDemo[82616:6257418] ivar2: 3
2019-12-06 14:23:05.295064+0800 LearningDemo[82616:6257418] tmpBlock <__NSMallocBlock__: 0x102c009f0>

將上面代碼轉(zhuǎn)換成底層實(shí)現(xiàn)源碼:

struct __Block_byref_ivar_0 {
  void *__isa;  //對(duì)象特性
__Block_byref_ivar_0 *__forwarding;//棧上指向自己,如果結(jié)構(gòu)體被拷貝到堆上指向堆上的拷貝的__Block_byref_ivar_0.
 int __flags;//狀態(tài)標(biāo)準(zhǔn)位
 int __size;//大小
 int ivar;//原變量
};

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

                    (ivar->__forwarding->ivar) += 1;
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_3,&(ivar->__forwarding->ivar));
                }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->ivar, (void*)src->ivar, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->ivar, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

                __attribute__((__blocks__(byref))) __Block_byref_ivar_0 ivar = {(void*)0,(__Block_byref_ivar_0 *)&ivar, 0, sizeof(__Block_byref_ivar_0), 1};
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_2,&(ivar.__forwarding->ivar));
                void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));
                (ivar.__forwarding->ivar) += 1;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_4,(ivar.__forwarding->ivar));
                ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_5,(ivar.__forwarding->ivar));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_76e773_mi_6,tmpBlock);
    }
    return 0;
}

會(huì)發(fā)現(xiàn)多了一個(gè) __Block_byref_ivar_0 結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體就是__block修飾的變量要轉(zhuǎn)換的結(jié)構(gòu)體, __main_block_impl_0捕獲的就是這個(gè)結(jié)構(gòu)體指針,Block的實(shí)現(xiàn)函數(shù)__main_block_func_0中通過(guò)__forwarding這個(gè)成員變量指向自己或堆上的拷貝的結(jié)構(gòu)體,然后再訪問(wèn)成員變量中的原變量ivar來(lái)修改值. ivar打印的地址不同是因?yàn)開(kāi)_Block_byref_ivar_0結(jié)構(gòu)體被拷貝到了堆區(qū),自然地址也不同,至于為什么會(huì)被拷貝到堆區(qū),后面會(huì)講.

接下來(lái)再看一下對(duì)象類型變量加__block前綴的代碼及轉(zhuǎn)換的源碼:

   __block NSArray *arr = @[@"1"];
        
           void (^tmpBlock)(void) = ^{
                arr = @[@"2"];
           };
        tmpBlock();
struct __Block_byref_arr_0 {
  void *__isa;
__Block_byref_arr_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSArray *arr;
};

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

                (arr->__forwarding->arr) = ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_1).arr, 1U);
           }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->arr, (void*)src->arr, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arr, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

        __attribute__((__blocks__(byref))) __Block_byref_arr_0 arr = {(void*)0,(__Block_byref_arr_0 *)&arr, 33554432, sizeof(__Block_byref_arr_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(1U, (NSString *)&__NSConstantStringImpl__var_folders_y4_0279cbtj42s5p4r1j0yxvfmc0000gn_T_main_aeef45_mi_0).arr, 1U)};

           void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_arr_0 *)&arr, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)tmpBlock)->FuncPtr)((__block_impl *)tmpBlock);
    }
    return 0;
}

會(huì)發(fā)現(xiàn)__Block_byref_arr_0結(jié)構(gòu)體多了兩個(gè)函數(shù)指針,它們分別指向__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131.再看上面的__block修飾的非對(duì)象類型和這個(gè)對(duì)象類型它們的__main_block_desc_0都有copy和dispose函數(shù)指針,它們都指向了__main_block_copy_0和__main_block_dispose_0函數(shù).這些函數(shù)是干什么的?接下來(lái)讓我們一探究竟.

這里提一下,Block在使用的時(shí)候會(huì)分成棧塊(_NSConcreteStackBlock)梯投、堆塊(_NSConcreteMallocBlock)命辖、全局塊(_NSConcreteGlobalBlock).下面的代碼也會(huì)根據(jù)塊的類型進(jìn)行判斷,這里提一下方便理解代碼.具體介紹會(huì)放在第三節(jié).

下面的代碼是Block拷貝時(shí)底層執(zhí)行的代碼:

//ARC下給Block變量賦值的時(shí)候會(huì)調(diào)用
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//
id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

通過(guò)上面的代碼及注釋我們可以了解到了Block拷貝的基本流程,里面有一些枚舉值,如下:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

通過(guò)注釋得知這些枚舉值用于描述塊對(duì)象的Block_layout-> flags的值.沒(méi)錯(cuò)就是上面void (*tmpBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_ivar_0 *)&ivar, 570425344));中傳的實(shí)參570425344.我們來(lái)打印一下按位邏輯或的值NSLog(@"%d",1 << 25 | 1 << 29); LearningDemo[43493:8342154] 570425344 剛好是傳的那個(gè)值,也就是BLOCK_HAS_COPY_DISPOSE| BLOCK_USE_STRET.也就是代碼會(huì)繼續(xù)走(*desc->copy)(result, aBlock);方法, 然后就調(diào)用到了我們剛剛所提到的__main_block_copy_0.而__main_block_copy_0中的_Block_object_assign,它到底干了什么,下面是它源碼:

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

根據(jù)上面拷貝邏輯可知,該函數(shù)傳的參數(shù)destArg是拷貝到堆上的目標(biāo)Block的成員變量, object是源Block的成員變量,也就是說(shuō)該函數(shù)主要是針對(duì)捕獲變量的操作.最后的flags也是個(gè)枚舉,如下:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ... 對(duì)象類型變量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable 塊變量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable     __Block_byref_arr_0結(jié)構(gòu)體
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers   弱引用變量
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines. 
};

接下來(lái)看看這些枚舉值的case中都做了什么.

BLOCK_FIELD_IS_OBJECT中執(zhí)行了_Block_retain_object,它的實(shí)現(xiàn)就是下面的代碼,所以它其實(shí)什么都沒(méi)做.然后將object的地址賦值給拷貝后的block成員變量.

static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

BLOCK_FIELD_IS_BLOCK當(dāng)塊中捕獲到了Block變量時(shí)的操作,又會(huì)去調(diào)用_Block_copy函數(shù).

BLOCK_FIELD_IS_BYREF也就是__block修飾變量拷貝的操作,也就是__main_block_copy_0函數(shù)傳的那個(gè)8/*BLOCK_FIELD_IS_BYREF*/,然后內(nèi)部又調(diào)用了下面函數(shù)

 static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

而其中的131則是128+3,官方解釋如下:

When a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions. Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor. And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.
So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
__block id 128+3 (0x83)
__block (^Block) 128+7 (0x87)
__weak __block id 128+3+16 (0x93)
__weak __block (^Block) 128+7+16 (0x97)

下面看看執(zhí)行的_Block_byref_copy函數(shù),源碼如下:

// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack   ?? 拷貝Block_byref結(jié)構(gòu)體
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
 //****??上面這段代碼就是棧上的Block成員變量forwarding指向堆上的Block的過(guò)程****//

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {//??初始化__Block_byref_arr_0的時(shí)候,傳的33554432剛好是 1<<25打的值.
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

上面的代碼執(zhí)行順序就是 塊拷貝->__main_block_copy_0 ->__Block_byref_id_object_copy_131

剩下的case都是指向源變量地址.

到此為止你應(yīng)該已經(jīng)發(fā)現(xiàn)一開(kāi)始的例子__main_block_desc_0中沒(méi)有copy和dispose函數(shù)指針的原因就是它捕獲的是非對(duì)象類型,而有這兩個(gè)函數(shù)指針的是對(duì)象類型和__block修飾的變量,用于拷貝捕獲的變量.另外,__block修飾的非對(duì)象類型是沒(méi)有__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131的,因?yàn)開(kāi)_Block_byref作為對(duì)象是需要這些拷貝操作的,而里面的非對(duì)象類型變量不需要.
另外關(guān)于_Block_object_dispose的底層實(shí)現(xiàn)我就不在贅述了,和copy過(guò)程類似,看一下源碼就懂了.

三、關(guān)于棧塊晚伙、堆塊、全局塊

根據(jù)Block所在內(nèi)存區(qū)域不同分為棧塊(_NSConcreteStackBlock)俭茧、堆塊(_NSConcreteMallocBlock)咆疗、全局塊(_NSConcreteGlobalBlock).它們?cè)贏RC和MRC下的表現(xiàn)是有差異的.

  • MRC 下的表現(xiàn)
    堆塊:為Block執(zhí)行copy方法才會(huì)變成堆塊.賦值到有copy、strong修飾符的屬性變量也是堆塊.
    棧塊:寫(xiě)在方法里的默認(rèn)就是棧塊.
    全局塊:全局變量區(qū)域定義的塊.不賦值且捕獲了自動(dòng)變量的也是全局塊.
  • ARC下的表現(xiàn)
    堆塊:塊實(shí)現(xiàn)賦值到塊自動(dòng)變量上就會(huì)變成堆塊.因?yàn)槟J(rèn)有__strong修飾符的緣故,被持有.
    棧塊:不賦值的塊且捕獲了自動(dòng)變量的默認(rèn)是棧塊.
    全局塊:全局變量區(qū)域?qū)懙膲K.塊里面使用的是全局變量母债、全局靜態(tài)變量午磁、靜態(tài)變量、或者不使用任何變量的時(shí)候也是全局塊.

了解當(dāng)前的Block是那種類型的塊以及如何會(huì)變成這種類型的塊之后,再結(jié)合上面的關(guān)于Block捕獲變量的底層實(shí)現(xiàn)和拷貝過(guò)程我們就清楚的明白當(dāng)前的塊能不能修改變量,變量有沒(méi)有執(zhí)行拷貝了.

四毡们、常見(jiàn)實(shí)戰(zhàn)應(yīng)用

1迅皇、適配器模式

用Block作為適配器來(lái)傳值,可以降低代碼分散程度,在數(shù)據(jù)種類不是很多的時(shí)候可以代替委托協(xié)議來(lái)使用.被適配者在Block的實(shí)現(xiàn)中執(zhí)行操作.相對(duì)協(xié)議更加輕松的實(shí)現(xiàn)了適配器模式.

2、循環(huán)引用問(wèn)題

一般我們會(huì)用__weak 來(lái)修飾self變量來(lái)打破循環(huán)引用,因?yàn)開(kāi)_weak修飾的變量Block不會(huì)持有它,執(zhí)行拷貝操作引用計(jì)數(shù)也不會(huì)增加,但是在Block的實(shí)現(xiàn)內(nèi)記得用__strong再修飾一邊self變量,防止外面的變量提前釋放或者被置空導(dǎo)致訪問(wèn)錯(cuò)誤.原理也很簡(jiǎn)單,因?yàn)锽lock的結(jié)構(gòu)體會(huì)捕獲self,加上__weak修飾符就可以不持有self變量來(lái),也就不會(huì)造成循環(huán)引用.而__strong是加在Block的實(shí)現(xiàn)里的,也不會(huì)造成循環(huán)引用,又可以保證代碼的安全訪問(wèn).

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衙熔,一起剝皮案震驚了整個(gè)濱河市登颓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌红氯,老刑警劉巖框咙,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痢甘,居然都是意外死亡喇嘱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)塞栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)者铜,“玉大人,你說(shuō)我怎么就攤上這事∽餮蹋” “怎么了愉粤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)俗壹。 經(jīng)常有香客問(wèn)我科汗,道長(zhǎng),這世上最難降的妖魔是什么绷雏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任头滔,我火速辦了婚禮,結(jié)果婚禮上涎显,老公的妹妹穿的比我還像新娘坤检。我一直安慰自己,他們只是感情好期吓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布早歇。 她就那樣靜靜地躺著,像睡著了一般讨勤。 火紅的嫁衣襯著肌膚如雪箭跳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天潭千,我揣著相機(jī)與錄音谱姓,去河邊找鬼。 笑死刨晴,一個(gè)胖子當(dāng)著我的面吹牛屉来,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狈癞,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼茄靠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蝶桶?” 一聲冷哼從身側(cè)響起慨绳,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎真竖,沒(méi)想到半個(gè)月后儡蔓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疼邀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年喂江,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旁振。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡获询,死狀恐怖涨岁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吉嚣,我是刑警寧澤梢薪,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站尝哆,受9級(jí)特大地震影響秉撇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秋泄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一琐馆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恒序,春花似錦肛循、人聲如沸顾稀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至喊巍,卻和暖如春屠缭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背崭参。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工呵曹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阵翎。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓逢并,卻偏偏與公主長(zhǎng)得像之剧,于是被迫代替她去往敵國(guó)和親郭卫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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

  • Block 最早出現(xiàn)是在 Mac OS X 10.6 和 iOS 4 中背稼,作為對(duì) C 語(yǔ)言的擴(kuò)展贰军,用來(lái)實(shí)現(xiàn)匿名函數(shù)...
    iOS大蝠閱讀 338評(píng)論 0 2
  • Block原理 Block變量捕獲 Block類型 copy操作和Block內(nèi)部訪問(wèn)對(duì)象類型的變量 __block...
    75b9020bd6db閱讀 642評(píng)論 0 0
  • Block作為Objective-C中閉包的實(shí)現(xiàn)在iOS開(kāi)發(fā)中占有非常重要的地位,尤其是作為回調(diào)(callback...
    NotFunGuy閱讀 748評(píng)論 0 4
  • 今天坐地鐵回家時(shí)遇到了站內(nèi)限流蟹肘,急迫回家的我與其他乘客一樣充滿著各種不滿词疼。這時(shí)我不禁想,地鐵站為什么要·限流呢帘腹?讓...
    247大為兄閱讀 581評(píng)論 0 0
  • 最終自己還是會(huì)被情緒所影響贰盗,還是被孩子們給氣到了,本以為結(jié)果還是被影響了阳欲。 時(shí)間久了舵盈,對(duì)于他們的調(diào)皮自己越來(lái)越不能...
    希雅的花園閱讀 89評(píng)論 0 0