Block底層原理分析

iOSBlock底層原理解析

目錄

  • Block底層解析
    • 什么是block尝胆?
      • block編譯轉(zhuǎn)換結(jié)構(gòu)
      • block實(shí)際結(jié)構(gòu)
    • block的類(lèi)型
      • NSConcreteGlobalBlockNSConcreteStackBlock
      • NSConcreteMallocBlock
    • 捕捉變量對(duì)block結(jié)構(gòu)的影響
      • 局部變量
      • 全局變量
      • 局部靜態(tài)變量
      • __block修飾的變量
      • self隱式循環(huán)引用
    • 不同類(lèi)型block的復(fù)制
      • 棧block
      • 堆block
      • 全局block
    • block輔助函數(shù)
      • __block修飾的基本類(lèi)型的輔助函數(shù)
      • 對(duì)象的輔助函數(shù)
    • ARC中block的工作
      • block試驗(yàn)
      • block作為參數(shù)傳遞
      • block作為返回值
      • block屬性
  • 參考博文

Block底層解析

最近看了一些block的資料贪染,并動(dòng)手做了一些實(shí)踐,摘錄并添加了一些結(jié)論铣耘。

什么是block?

首先,看一個(gè)極簡(jiǎn)的block:

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ^{ };
    }
    return 0;
}

block編譯轉(zhuǎn)換結(jié)構(gòu)

對(duì)其執(zhí)行clang -rewrite-objc編譯轉(zhuǎn)換成C++實(shí)現(xiàn)水援,得到以下代碼:

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) {
}

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

不難看出其中的__main_block_impl_0就是block的一個(gè)C++的實(shí)現(xiàn)(最后面的_0代表是main中的第幾個(gè)block)奕扣,也就是說(shuō)也是一個(gè)結(jié)構(gòu)體

其中__block_impl的定義如下:

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

其結(jié)構(gòu)體成員如下:

  • isa芯杀,指向所屬類(lèi)的指針,也就是block的類(lèi)型
  • flags,標(biāo)志變量,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
  • Reserved唧垦,保留變量
  • FuncPtr坊秸,block執(zhí)行時(shí)調(diào)用的函數(shù)指針
    可以看出,它包含了isa指針(包含isa指針的皆為對(duì)象),也就是說(shuō)block也是一個(gè)對(duì)象(runtime里面,對(duì)象和類(lèi)都是用結(jié)構(gòu)體表示)困食。

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

其結(jié)構(gòu)成員含義如下:

  • reserved:保留字段
  • Block_size:block大小(sizeof(struct __main_block_impl_0))

以上代碼在定義__main_block_desc_0結(jié)構(gòu)體時(shí)待讳,同時(shí)創(chuàng)建了__main_block_desc_0_DATA南吮,并給它賦值,以供在main函數(shù)中對(duì)__main_block_impl_0進(jìn)行初始化比勉。

__main_block_impl_0定義了顯式的構(gòu)造函數(shù),其函數(shù)體如下:

 __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_0isa指針指向了_NSConcreteStackBlock
  • 從main函數(shù)中看, __main_block_impl_0FuncPtr指向了函數(shù)__main_block_func_0
  • __main_block_impl_0Desc也指向了定義__main_block_desc_0時(shí)就創(chuàng)建的__main_block_desc_0_DATA瞭稼,其中紀(jì)錄了block結(jié)構(gòu)體大小等信息。

以上就是根據(jù)編譯轉(zhuǎn)換的結(jié)果腌零,對(duì)一個(gè)簡(jiǎn)單的block的解析,后面會(huì)將block操作不同類(lèi)型的外部變量扭弧,對(duì)block結(jié)構(gòu)的影響進(jìn)行相應(yīng)的說(shuō)明。

block實(shí)際結(jié)構(gòu)

接下來(lái)觀察下Block_private.h文件中對(duì)block的相關(guān)結(jié)構(gòu)體的真實(shí)定義:

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};


struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

有了上文對(duì)編譯轉(zhuǎn)換的分析屑埋,這里只針對(duì)略微不同的成員進(jìn)行分析:

  • invoke敲街,同上文的FuncPtr,block執(zhí)行時(shí)調(diào)用的函數(shù)指針拨匆,block定義時(shí)內(nèi)部的執(zhí)行代碼都在這個(gè)函數(shù)中
  • Block_descriptor,block的詳細(xì)描述
    • copy/dispose,輔助拷貝/銷(xiāo)毀函數(shù),處理block范圍外的變量時(shí)使用

總體來(lái)說(shuō),block就是一個(gè)里面存儲(chǔ)了指向函數(shù)體中包含定義block時(shí)的代碼塊的函數(shù)指針以及block外部上下文變量等信息的結(jié)構(gòu)體辖源。

block的類(lèi)型

block的常見(jiàn)類(lèi)型有3種:

  • _NSConcreteGlobalBlock(全局)
  • _NSConcreteStackBlock (棧)
  • _NSConcreteMallocBlock(堆)

附上APUE的進(jìn)程虛擬內(nèi)存段分布圖:

虛擬內(nèi)存結(jié)構(gòu)圖.png

其中前兩種在Block.h中聲明亡脑,后一種在Block_private.h中聲明拍屑,所以最后一種基本不會(huì)在源碼中出現(xiàn)。

由于無(wú)法直接創(chuàng)建_NSConcreteMallocBlock類(lèi)型的block,所以這里只對(duì)前面2種進(jìn)行手動(dòng)創(chuàng)建分析崔泵,最后1種通過(guò)源代碼分析陈瘦。

NSConcreteGlobalBlock和NSConcreteStackBlock

首先鞍泉,根據(jù)前面兩種類(lèi)型托修,編寫(xiě)一下代碼:

void (^globalBlock)() = ^{

};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^stackBlock1)() = ^{

        };
    }
    return 0;
}

對(duì)其進(jìn)行編譯轉(zhuǎn)換后得到以下縮略代碼:

// globalBlock
struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
...

// stackBlock
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;
  }
};
...
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;
}

可以看出globalBlockisa指向了_NSConcreteGlobalBlock际长,即在全局區(qū)域創(chuàng)建郁轻,編譯時(shí)具體的代碼就已經(jīng)確定在上圖中的代碼段中了蜕提,block變量存儲(chǔ)在全局?jǐn)?shù)據(jù)存儲(chǔ)區(qū)stackBlockisa指向了_NSConcreteStackBlock,即在棧區(qū)創(chuàng)建坞生。

NSConcreteMallocBlock

接下來(lái)是在堆中的block赃泡,堆中的block無(wú)法直接創(chuàng)建,其需要由_NSConcreteStackBlock類(lèi)型的block拷貝而來(lái)(也就是說(shuō)block需要執(zhí)行copy之后才能存放到堆中)蓖柔。由于block的拷貝最終都會(huì)調(diào)用_Block_copy_internal函數(shù)镐捧,所以觀察這個(gè)函數(shù)就可以知道堆中block是如何被創(chuàng)建的了:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申請(qǐng)block的堆內(nèi)存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷貝棧中block到剛申請(qǐng)的堆內(nèi)存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改變isa指向_NSConcreteMallocBlock,即堆block類(lèi)型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

從以上代碼以及注釋可以很清楚的看出随夸,函數(shù)通過(guò)memmove將棧中的block的內(nèi)容拷貝到了堆中澜搅,并使isa指向了_NSConcreteMallocBlock

捕捉變量對(duì)block結(jié)構(gòu)的影響

接下來(lái)會(huì)編譯轉(zhuǎn)換捕捉不同變量類(lèi)型的block,以對(duì)比它們的區(qū)別。

局部變量

前:

- (void)test
{
    int a;
    ^{a;};
}

后:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  int a;
  // a(_a)是構(gòu)造函數(shù)的參數(shù)列表初始化形式,相當(dāng)于a = _a扮惦。從_I_Person_test看,傳入的就是a
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
a;}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {
    int a;
    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
}

可以看到沙绝,block相對(duì)于文章開(kāi)頭增加了一個(gè)int類(lèi)型的成員變量购笆,他就是用來(lái)存儲(chǔ)外部變量a的铺遂×肝耄可以看出姓迅,這次拷貝只是一次值傳遞解寝。并且當(dāng)我們想在block中進(jìn)行一下操作時(shí),將會(huì)發(fā)生錯(cuò)誤

^{a = 10;};

編譯器會(huì)提示:

error.png

因?yàn)開(kāi)I_Person_test函數(shù)中的a和__Person__test_block_func_0函數(shù)中的a并沒(méi)有在同一個(gè)作用域说铃,所以在block對(duì)a進(jìn)行賦值是沒(méi)有意義的幼苛,所以編譯器給出了錯(cuò)誤霍转。我們可以通過(guò)地址傳遞來(lái)消除以上錯(cuò)誤:

- (void)test
{
    int a = 0;
    // 利用指針p存儲(chǔ)a的地址
    int *p = &a;

    ^{
        // 通過(guò)a的地址設(shè)置a的值
        *p = 10;
    };
}

但是變量a的生命周期和方法test的棧相關(guān)聯(lián)的召夹,當(dāng)test運(yùn)行結(jié)束,棧隨之銷(xiāo)毀,那么變量a就會(huì)被銷(xiāo)毀信轿,p也就成為了野指針。如果block是作為參數(shù)或者返回值,這些類(lèi)型都是跨棧的遭庶,也就是說(shuō)再次調(diào)用會(huì)造成野指針錯(cuò)誤榨了。

全局變量

前:

// 全局靜態(tài)
static int a;
// 全局
int b;
- (void)test
{

    ^{
        a = 10;
        b = 10;
    };
}

static int a;
int b;

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

        a = 10;
        b = 10;
    }

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {

    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
}

可以看出作岖,因?yàn)槿肿兞慷际窃?code>靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)沉删,在程序結(jié)束前不會(huì)被銷(xiāo)毀,所以block直接訪(fǎng)問(wèn)了對(duì)應(yīng)的變量恍涂,而沒(méi)有在__Person__test_block_impl_0結(jié)構(gòu)體中給變量預(yù)留位置炒瘸。

局部靜態(tài)變量

前:

- (void)test
{
    static int a;
    ^{
        a = 10;
    };
}

后:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  int *a;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy
        // 這里通過(guò)局部靜態(tài)變量a的地址來(lái)對(duì)其進(jìn)行修改
        (*a) = 10;
    }

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};

static void _I_Person_test(Person * self, SEL _cmd) {
    static int a;
    // 傳入a的地址
    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
}

需要注意一點(diǎn)的是靜態(tài)局部變量時(shí)存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)域的汹胃,也就是和程序擁有一樣的生命周期,也就是說(shuō)在程序運(yùn)行時(shí)贵扰,都能夠保證block訪(fǎng)問(wèn)到一個(gè)有效的變量舞丛。但是其作用范圍還是局限于定義它的函數(shù)中绒障,所以只能在block通過(guò)靜態(tài)局部變量的地址來(lái)進(jìn)行訪(fǎng)問(wèn)鸵钝。

關(guān)于變量的存儲(chǔ)可以參考這篇文章:c語(yǔ)言臆想--全局---局部變量

__block修飾的變量

前:

- (void)test
{
   __block int a;
    ^{
        a = 10;
    };
}

后:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
        // 注意,這里的_forwarding用來(lái)保證操作的始終是堆中的拷貝a恩商,而不是棧中的a
        (a->__forwarding->a) = 10;
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

static void _I_Person_test(Person * self, SEL _cmd) {
    // __block將a包裝成了一個(gè)對(duì)象
   __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
;
    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}

可以看到变逃,對(duì)比上面的結(jié)果,明顯多了__Block_byref_a_0結(jié)構(gòu)體揽乱,這個(gè)結(jié)構(gòu)體中含有isa指針,所以也是一個(gè)對(duì)象粟矿,它是用來(lái)包裝局部變量a的。當(dāng)block被copy堆中時(shí)陌粹,__Person__test_block_impl_0的輔助拷貝函數(shù)__Person__test_block_copy_0會(huì)將__Block_byref_a_0拷貝至堆中撒犀,所以即使局部變量所在堆被銷(xiāo)毀,block依然能對(duì)堆中的局部變量進(jìn)行操作申屹。其中__Block_byref_a_0成員指針__forwarding用來(lái)指向它在堆中的拷貝绘证,其依據(jù)源碼如下:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;

    ...
    // 堆中拷貝的forwarding指向它自己
    copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
    // 棧中的forwarding指向堆中的拷貝
    src->forwarding = copy;  // patch stack to point to heap copy
    ...
}

這樣做是為了保證操作的值始終是堆中的拷貝,而不是棧中的值哗讥。(處理在局部變量所在棧還沒(méi)銷(xiāo)毀嚷那,就調(diào)用block來(lái)改變局部變量值的情況,如果沒(méi)有__forwarding指針杆煞,則修改無(wú)效)

至于block如何實(shí)現(xiàn)對(duì)局部變量的拷貝魏宽,下面會(huì)詳細(xì)說(shuō)明。

self隱式循環(huán)引用

前:

@implementation Person
{
    int _a;
    void (^_block)();
}
- (void)test
{
  void (^_block)() = ^{
        _a = 10;
    };
}

@end

后:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  // 可以看到决乎,block強(qiáng)引用了self
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

static void _I_Person_test(Person * self, SEL _cmd) {
  void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
}

如果在編譯轉(zhuǎn)換前队询,將.a改成self.a,能很明顯地看出是產(chǎn)生了循環(huán)引用(self強(qiáng)引用block构诚,block強(qiáng)引用self)蚌斩。那么使用_a呢?經(jīng)過(guò)編譯轉(zhuǎn)換后范嘱,依然可以在__Person__test_block_impl_0看見(jiàn)self的身影送膳。且在函數(shù)_I_Person_test中,傳入的參數(shù)也是self丑蛤。通過(guò)以下語(yǔ)句叠聋,可以看出不管是用什么形式訪(fǎng)問(wèn)實(shí)例變量,最終都會(huì)轉(zhuǎn)換成self+變量?jī)?nèi)存偏移的形式受裹。所以在上面例子中使用_a也會(huì)造成循環(huán)引用碌补。

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy
        // self+實(shí)例變量a的偏移值
        (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
    }

不同類(lèi)型的block的復(fù)制

block的復(fù)制代碼在_Block_copy_internal函數(shù)中。

棧block

從以下代碼可以看出,棧block的復(fù)制不僅僅復(fù)制了其內(nèi)容厦章,還添加了一些額外的東西

  • 1.往flags中并入了BLOCK_NEEDS_FREE(這個(gè)標(biāo)志表明block需要釋放镇匀,在release以及再次拷貝時(shí)會(huì)用到)
  • 2.如果有輔助copy函數(shù)(BLOCK_HAS_COPY_DISPOSE),那么就調(diào)用(這個(gè)輔助copy函數(shù)是用來(lái)拷貝block捕獲的變量的)
...
struct Block_layout *result = malloc(aBlock->descriptor->size);
      if (!result) return (void *)0;
      memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
      // reset refcount
      result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
      result->flags |= BLOCK_NEEDS_FREE | 1;
      result->isa = _NSConcreteMallocBlock;
      if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
          //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
          (*aBlock->descriptor->copy)(result, aBlock); // do fixup
      }
      return result;
...

堆block
從以下代碼看出闷袒,如果block的flags中有BLOCK_NEEDS_FREE標(biāo)志(block從棧中拷貝到堆時(shí)添加的標(biāo)志)坑律,就執(zhí)行了latching_incr_int操作,其功能就是讓block的引用計(jì)數(shù)加1囊骤。所以堆中block的拷貝只是單純的改變了引用計(jì)數(shù)

if (aBlock->flags & BLOCK_NEEDS_FREE) {
      // latches on high
      latching_incr_int(&aBlock->flags);
      return aBlock;
  }

全局block
從以下代碼看出晃择,對(duì)于全局block,函數(shù)沒(méi)有任何操作也物,直接返回了傳入的block

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
      return aBlock;
  }

block輔助函數(shù)

上文提及到了block輔助copydispose處理函數(shù)宫屠,這里分析下這兩個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)。在捕獲變量為__block修飾的基本類(lèi)型滑蚯,或者對(duì)象時(shí)浪蹂,block才會(huì)有這兩個(gè)輔助函數(shù)。

block捕捉變量拷貝函數(shù)為_Block_object_assign告材。在調(diào)用復(fù)制block的函數(shù)_Block_copy_internal時(shí)坤次,會(huì)根據(jù)block有無(wú)輔助函數(shù)來(lái)對(duì)捕捉變量拷貝函數(shù)_Block_object_assign進(jìn)行調(diào)用。而在_Block_object_assign函數(shù)中斥赋,也會(huì)判斷捕捉變量包裝而成的對(duì)象(Block_byref結(jié)構(gòu)體)是否有輔助函數(shù)缰猴,來(lái)進(jìn)行調(diào)用。

__block修飾的基本類(lèi)型的輔助函數(shù)
編寫(xiě)一下代碼:

typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a;
        Block block = ^ {
            a;
        };
}

轉(zhuǎn)換成C++代碼后:

typedef void(*Block)();
// __block int a
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

// block
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block函數(shù)體
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a);
        }
// 輔助copy函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

// 輔助dispose函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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;
        // 這里創(chuàng)建了疤剑,并將a的flags設(shè)置為0
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
;
        Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
    }
    return 0;
}

從上面代碼中滑绒,被__block修飾的a變量變?yōu)榱?code>__Block_byref_a_0類(lèi)型,根據(jù)這個(gè)格式隘膘,從源碼中查看得到相似的定義:

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    int flags; /* refcount; */
    int size;
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    void (*byref_destroy)(struct Block_byref *);
    /* long shared[0]; */
};

// 做下對(duì)比
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

// flags/_flags類(lèi)型
enum {
        /* See function implementation for a more complete description of these fields and combinations */
        // 是一個(gè)對(duì)象
        BLOCK_FIELD_IS_OBJECT   =  3,  /* id, NSObject, __attribute__((NSObject)), block, ... */
        // 是一個(gè)block
        BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
        // 被__block修飾的變量
        BLOCK_FIELD_IS_BYREF    =  8,  /* the on stack structure holding the __block variable */
        // 被__weak修飾的變量疑故,只能被輔助copy函數(shù)使用
        BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
        // block輔助函數(shù)調(diào)用(告訴內(nèi)部實(shí)現(xiàn)不要進(jìn)行retain或者copy)
        BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
    };

可以看出,__block將原來(lái)的基本類(lèi)型包裝成了對(duì)象弯菊。因?yàn)橐陨蟽蓚€(gè)結(jié)構(gòu)體的前4個(gè)成員的類(lèi)型都是一樣的纵势,內(nèi)存空間排列一致,所以可以進(jìn)行一下操作:

// 轉(zhuǎn)換成C++代碼
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

// _Block_object_assign源碼
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
...
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
...
}

// _Block_byref_assign_copy源碼
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    // 這里因?yàn)榍懊?個(gè)成員的內(nèi)存分布一樣管钳,所以直接轉(zhuǎn)換后吨悍,使用Block_byref的成員變量名,能訪(fǎng)問(wèn)到__Block_byref_a_0的前面4個(gè)成員
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
...
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 從main函數(shù)對(duì)__Block_byref_a_0的初始化蹋嵌,可以看到初始化時(shí)將flags賦值為0
        // 這里表示第一次拷貝,會(huì)進(jìn)行復(fù)制操作葫隙,并修改原來(lái)flags的值
        // static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
        // 可以看出栽烂,復(fù)制后,會(huì)并入BLOCK_NEEDS_FREE,后面的2是block的初始引用計(jì)數(shù)
        ...
        copy->flags = src->flags | _Byref_flag_initial_value;
        ...
    }
    // 已經(jīng)拷貝到堆了腺办,只增加引用計(jì)數(shù)
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // 普通的賦值焰手,里面最底層就*destptr = value;這句表達(dá)式
    _Block_assign(src->forwarding, (void **)destp);
}

主要操作都在代碼注釋中了,總體來(lái)說(shuō)怀喉,__block修飾的基本類(lèi)型會(huì)被包裝為對(duì)象书妻,并且只在最初block拷貝時(shí)復(fù)制一次,后面的拷貝只會(huì)增加這個(gè)捕獲變量的引用計(jì)數(shù)躬拢。

對(duì)象的輔助函數(shù)

  • 沒(méi)有__block修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    }
    return 0;
}

首先躲履,在沒(méi)有__block修飾時(shí),對(duì)象編譯轉(zhuǎn)換的結(jié)果如下聊闯,刪除了一些變化不大的代碼:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *a = __cself->a; // bound by copy
            a;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

對(duì)象在沒(méi)有__block修飾時(shí)工猜,并沒(méi)有產(chǎn)生__Block_byref_a_0結(jié)構(gòu)體,只是將標(biāo)志位修改為BLOCK_FIELD_IS_OBJECT菱蔬。而在_Block_object_assign中對(duì)應(yīng)的判斷分支代碼如下:

...
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
    _Block_retain_object(object);
    _Block_assign((void *)object, destAddr);
}
...

可以看到篷帅,block復(fù)制時(shí),會(huì)retain捕捉對(duì)象拴泌,以增加其引用計(jì)數(shù)魏身。

  • __block修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    }
    return 0;
}

在這種情況下,編譯轉(zhuǎn)換的部分結(jié)果如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *a;
};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};
Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}

// 以下的40表示__Block_byref_a_0對(duì)象a的位移(4個(gè)指針(32字節(jié))+2個(gè)int變量(8字節(jié))=40字節(jié))
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

可以看到蚪腐,對(duì)于對(duì)象箭昵,__Block_byref_a_0另外增加了兩個(gè)輔助函數(shù)__Block_byref_id_object_copy__Block_byref_id_object_dispose,以實(shí)現(xiàn)對(duì)對(duì)象內(nèi)存的管理削茁。其中兩者的最后一個(gè)參數(shù)131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT宙枷,BLOCK_BYREF_CALLER表示在內(nèi)部實(shí)現(xiàn)中不對(duì)a對(duì)象進(jìn)行retain或copy巷送;以下為相關(guān)源碼:

case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *dest = object;
        break;

直接賦值屯远。
所以通過(guò)__block修飾可以避免調(diào)用到_Block_retain_object方法,也就是在MRC環(huán)境下我們可以通過(guò)__block來(lái)避免Block強(qiáng)持有變量套鹅,進(jìn)而避免循環(huán)引用瘾杭。但是在ARC環(huán)境下不行诅病,還是有可能會(huì)導(dǎo)致循環(huán)引用。

if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
    ...
    else {
        // do *not* retain or *copy* __block variables whatever they are
        _Block_assign((void *)object, destAddr);
    }
}

_Block_byref_assign_copy函數(shù)的以下代碼會(huì)對(duì)上面的輔助函數(shù)(__Block_byref_id_object_copy_131)進(jìn)行調(diào)用粥烁;570425344表示BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR贤笆,所以會(huì)執(zhí)行以下相關(guān)源碼:

if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
    // Trust copy helper to copy everything of interest
    // If more than one field shows up in a byref block this is wrong XXX
    copy->byref_keep = src->byref_keep;
    copy->byref_destroy = src->byref_destroy;
    (*src->byref_keep)(copy, src);
}

ARC中block的工作

蘋(píng)果說(shuō)明.png

蘋(píng)果文檔提及,在ARC模式下讨阻,在棧間傳遞block時(shí)芥永,不需要手動(dòng)copy棧中的block,即可讓block正常工作钝吮。主要原因是ARC對(duì)棧中的block自動(dòng)執(zhí)行了copy埋涧,將_NSConcreteStackBlock類(lèi)型的block轉(zhuǎn)換成了_NSConcreteMallocBlock的block板辽。

block試驗(yàn)

下面對(duì)block做點(diǎn)實(shí)驗(yàn):

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int i = 10;
        void (^block)() = ^{i;};

        __weak void (^weakBlock)() = ^{i;};

        void (^stackBlock)() = ^{};

        // ARC情況下

        // 創(chuàng)建時(shí),都會(huì)在棧中
        // <__NSStackBlock__: 0x7fff5fbff730>
        NSLog(@"%@", ^{i;});

        // 因?yàn)閎lock為strong類(lèi)型棘催,且捕獲了外部變量劲弦,所以賦值時(shí),自動(dòng)進(jìn)行了copy
        // <__NSMallocBlock__: 0x100206920>
        NSLog(@"%@", block);

        // 如果是weak類(lèi)型的block醇坝,依然不會(huì)自動(dòng)進(jìn)行copy
        // <__NSStackBlock__: 0x7fff5fbff728>
        NSLog(@"%@", weakBlock);

        // 如果block是strong類(lèi)型邑跪,并且沒(méi)有捕獲外部變量,那么就會(huì)轉(zhuǎn)換成__NSGlobalBlock__
        // <__NSGlobalBlock__: 0x100001110>
        NSLog(@"%@", stackBlock);

        // 在非ARC情況下呼猪,產(chǎn)生以下輸出
        // <__NSStackBlock__: 0x7fff5fbff6d0>
        // <__NSStackBlock__: 0x7fff5fbff730>
        // <__NSStackBlock__: 0x7fff5fbff700>
        // <__NSGlobalBlock__: 0x1000010d0>
    }
    return 0;
}

可以看出画畅,ARC對(duì)類(lèi)型為strong且捕獲了外部變量的block進(jìn)行了copy。并且當(dāng)block類(lèi)型為strong郑叠,但是創(chuàng)建時(shí)沒(méi)有捕獲外部變量夜赵,block最終會(huì)變成__NSGlobalBlock__類(lèi)型(這里可能因?yàn)閎lock中的代碼沒(méi)有捕獲外部變量,所以不需要在棧中開(kāi)辟變量乡革,也就是說(shuō)寇僧,在編譯時(shí),這個(gè)block的所有內(nèi)容已經(jīng)在代碼段中生成了沸版,所以就把block的類(lèi)型轉(zhuǎn)換為全局類(lèi)型)

blok作為參數(shù)傳遞

再來(lái)看下使用在棧中的block需要注意的情況:

NSMutableArray *arrayM;
void myBlock()
{
    int a = 5;
    Block block = ^ {
        NSLog(@"%d", a);
    };

    [arrayM addObject:block];
    NSLog(@"%@", block);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        arrayM = @[].mutableCopy;

        myBlock();

        Block block = [arrayM firstObject];
        // 非ARC這里崩潰
        block();
 }

// ARC情況下輸出
// <__NSMallocBlock__: 0x100214480>

// 非ARC情況下輸出
// <__NSStackBlock__: 0x7fff5fbff738>
// 崩潰嘁傀,野指針錯(cuò)誤

可以看到,ARC情況下因?yàn)樽詣?dòng)執(zhí)行了copy,所以返回類(lèi)型為__NSMallocBlock__视粮,在函數(shù)結(jié)束后依然可以訪(fǎng)問(wèn)细办;而非ARC情況下,需要我們手動(dòng)調(diào)用[block copy]來(lái)講block拷貝到堆中蕾殴,否則因?yàn)闂V械腷lock生命周期和函數(shù)中的棧聲明周期關(guān)聯(lián)笑撞,當(dāng)函數(shù)退出后,相應(yīng)的堆被銷(xiāo)毀钓觉,block也就不存在了茴肥。

如果把block的以下代碼刪除:

NSLog(@"%d", a);

那么block就會(huì)變成全局類(lèi)型,在main中訪(fǎng)問(wèn)也不會(huì)崩潰荡灾。

block作為返回值

在非ARC情況下瓤狐,如果返回值是block,則一般這樣操作:

return [[block copy] autorelease];

對(duì)于外部要使用的block批幌,更趨向于把它拷貝到堆中础锐,使其脫離棧生命周期的約束。

block屬性

這里還有一點(diǎn)關(guān)于block類(lèi)型的ARC屬性荧缘。上文也說(shuō)明了皆警,ARC會(huì)自動(dòng)幫strong類(lèi)型捕獲外部變量的block進(jìn)行copy,所以在定義block類(lèi)型的屬性時(shí)也可以使用strong截粗,不一定使用copy耀怜。也就是以下代碼:

/** 假如有棧block賦給以下兩個(gè)屬性 **/

// 這里因?yàn)锳RC恢着,當(dāng)棧block中會(huì)捕獲外部變量時(shí),這個(gè)block會(huì)被copy進(jìn)堆中
// 如果沒(méi)有捕獲外部變量财破,這個(gè)block會(huì)變?yōu)槿诸?lèi)型
// 不管怎么樣,它都脫離了棧生命周期的約束

@property (strong, nonatomic) Block *strongBlock;

// 這里都會(huì)被copy進(jìn)堆中
@property (copy, nonatomic) Block *copyBlock;

本文主要為學(xué)習(xí)筆記从诲,轉(zhuǎn)載參考自如下文章:
iOS基礎(chǔ):Block底層實(shí)現(xiàn)及理解
Block技巧與底層解析
__weak與__block修飾符的區(qū)別
史上最詳細(xì)的Block源碼剖析
libclosure-73

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末左痢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子系洛,更是在濱河造成了極大的恐慌俊性,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件描扯,死亡現(xiàn)場(chǎng)離奇詭異定页,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绽诚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)典徊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人恩够,你說(shuō)我怎么就攤上這事卒落。” “怎么了蜂桶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵儡毕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我扑媚,道長(zhǎng)腰湾,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任疆股,我火速辦了婚禮费坊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘押桃。我一直安慰自己葵萎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布唱凯。 她就那樣靜靜地躺著羡忘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕昼。 梳的紋絲不亂的頭發(fā)上卷雕,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音票从,去河邊找鬼漫雕。 笑死滨嘱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浸间。 我是一名探鬼主播太雨,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼魁蒜!你這毒婦竟也來(lái)了囊扳?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兜看,失蹤者是張志新(化名)和其女友劉穎锥咸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體细移,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搏予,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弧轧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雪侥。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖劣针,靈堂內(nèi)的尸體忽然破棺而出校镐,到底是詐尸還是另有隱情,我是刑警寧澤捺典,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布鸟廓,位于F島的核電站,受9級(jí)特大地震影響襟己,放射性物質(zhì)發(fā)生泄漏引谜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一擎浴、第九天 我趴在偏房一處隱蔽的房頂上張望员咽。 院中可真熱鬧,春花似錦贮预、人聲如沸贝室。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滑频。三九已至,卻和暖如春唤冈,著一層夾襖步出監(jiān)牢的瞬間峡迷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绘搞,地道東北人彤避。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像夯辖,于是被迫代替她去往敵國(guó)和親琉预。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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

  • 前言 Blocks是C語(yǔ)言的擴(kuò)充功能楼雹,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,768評(píng)論 0 23
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動(dòng)變量值 int main(){ ...
    南京小伙閱讀 929評(píng)論 1 3
  • Block語(yǔ)法 Block可以認(rèn)為是一個(gè)匿名函數(shù)模孩。語(yǔ)法聲明如下: return_type (^block_name...
    smallSun15閱讀 260評(píng)論 0 1
  • 摘要 Blocks是C語(yǔ)言的擴(kuò)充功能, iOS 4中引入了這個(gè)新功能“Blocks”贮缅,那么block到底是什么東西...
    CholMay閱讀 1,166評(píng)論 2 10
  • 昨天晚上看完了阿加莎的《無(wú)人生還》,膽小的我失去了睡眠介却。 還是想象力太豐富了的緣故谴供,雖然阿加莎的文字并沒(méi)有細(xì)膩的陰...
    傾和閱讀 282評(píng)論 1 1