Block 梳理與疑問

Block 梳理與疑問

時隔一年,再次讀 《Objective-C 高級編程》,看到 block 一章,這一次從頭至尾的跟著編譯了一次,理清楚了很多之前不理解的地方,但是也同時多出了許多疑問。本文是在和學渣裙的朋友們分享以后的梳理筆記,有問題歡迎指出,如果能解決最后的幾個小疑問,就更好了。

環境信息
macOS 10.12.1
Xcode 8.2.1
iOS 10.12

一個最基本的 block

Block 編譯后,有兩個最為重要的部分,impl 結構體 與 desc 結構體指針。我們從最為簡單基礎的開始:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義一個參數列表與返回值均為空的 block
    dispatch_block_t block = ^{
        // 僅輸出一句話
        NSLog(@"123");
    };
    // 調用
    block();
}
return 0;

}
使用 clang -rewrite-objc xxx.m 命令,編譯后(已刪除一些影響閱讀的字符,用 xxx 代替):

// block 結構體
struct __main_block_impl_0 {
struct __block_impl impl; // 實現
struct __main_block_desc_0* Desc; // 描述

// 在定義 block 時,所調用的 block 初始化方法
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; // block 的類型(之后會談到)
impl.Flags = flags;
impl.FuncPtr = fp; // block 實現編譯后的函數指針
Desc = desc; // 描述信息
}
};

// block 的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block 所占的內存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // block 描述的初始化方法,可以看出這里的大小計算,僅僅是進行了 sizeof

// block 實現編譯過后的函數
// 即在 block 初始化方法中,賦值給 impl.FuncPtr 的函數指針
// 參數 cself 是 __main_block_impl_0 類型,即與 block 類型相同,其實這里的參數,本身就是 block 自己
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 輸出
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_main_c44db5_mi_0);
}

// main 函數
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

// block 的定義
// 可以看出,在 block 定義的時候,就調用了 __main_block_impl_0,即 block 的構造方法
// 傳的參數分別為 __main_block_func_0,即 block 對應的編譯后的實現函數
// __main_block_desc_0_DATA,即 block 描述
dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
                
// block 的調用
// 這里也可以看出,block 的調用即是調用了,初始化時拿到的 FuncPtr 函數指針
// FuncPtr 函數有一個參數,即傳入的 block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}
return 0;
}
block 內存結構

通過編譯后得到的 block 結構體,能大致看出,在沒有引用外部變量的 block 是這樣的:

struct __block_impl impl;
struct __main_block_desc_0 *Desc;
此時的內存結構如下:

isa 指針

在 block 調用構造方法時,編譯器已經自動給 isa 指針賦了初值。我們知道,isa 指針其實很形象,就稱作 is a,在 OC 中表達了對象是什么類型,類所屬哪個元類,其實 block 也是對象,所以它的 isa 指針也是說明它是什么的。如果直接打印 block,則可看到以下三種情況:

<NSStackBlock: 0x1000010c0>,存儲在棧上的 block
<NSMallocBlock: 0x1000010c0>,存儲在堆上的 block
<NSGlobalBlock: 0x1000010c0>,存儲在全局區的 block
但是你會發現,如果直接打印上面我們所寫的 block,輸出的是 NSGlobalBlock 類型,而我們看到的編譯代碼,明明是 stack 的。這是因為 block 的存儲區域,與定義在什么位置、是否引用外部變量、是否作為范圍值、是被哪種類型的變量所接收等等情況相關,這個會在下一小節談到。

引用外部變量的 block

之前介紹了一個空(并未引用變量)的 block,下面來看一個稍微復雜一點的:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義局部變量 a
    int a = 10;
    // 定義 block
    dispatch_block_t block = ^{
        // 輸出 a 變量
        NSLog(@"%d", a);
    };
    // 調用 block
    block();
}
return 0;

}
在學習 block 的基礎知識時,就知道,此時如果在 block 定義之后,去修改 a 的值,block 中的輸出依然不會改變,我們來看一下為什么。

編譯文件:

// 下面僅標注了有變化的變量

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 在 block 結構體中,多了一個名為 a 的變量

// block 構造方法也多了一個 _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;
}
};

// 描述依然沒有變,size 是直接計算的 __main_block_impl_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)};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 在 block 的實現函數中,訪問 block 結構體中的 a 變量,并且編譯器在此還說明了是 bound by copy,即值拷貝
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, a);
}
從編譯代碼可以看出,在 block 定義時,就傳入了 block 內部需要用到的 a 變量的值,而并不是引用,所以即使在 block 定義之后,a 變量怎么變,之前 block 所有的 a 的瞬時值,是沒有變化的。

此時,block 的內存結構為:

多出了捕獲的變量 a 的存儲空間,并且,捕獲的變量會接在 Desc 內存后面。

被 __block 修飾的外部變量

如果沒有 __block 修飾,除了在 block 定義之后,就不能拿到變量最新的值以外,我們還不能對變量進行重新賦值(如果是堆上的內存,就是改變地址,即 NSMutableArray 是可以 addObject 的,只是不能 array = @[])。那么,想要解決這兩個問題,我們就需要引入 __block 修飾符:

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    // 定義變量 a,并使用 __block 修飾
    __block int a = 10;
    dispatch_block_t block = ^{
        // 輸出 a
        NSLog(@"%d", a); // 輸出 100
        // 在 block 內部對 a 重新賦值
        a = 50;
    };
    // 在 block 定義后,對 a 重新賦值
    a = 100;
    // 調用 block
    block();
    // 輸出 a
    NSLog(@"%d", a); // 輸出 50
}
return 0;

}
這一次,編譯后的代碼變得很復雜了:

// block 結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;

// 比起沒有被 __block 修飾的變量(編譯后,block 中是 int a),這里 block 卻不是簡單的拿到 a 地址,即 int *a,而是一個類型為 __Block_byref_a_0 的結構體指針
__Block_byref_a_0 *a; // by ref

// 構造方法多出的參數也變成了,__Block_byref_a_0 結構體指針,并且 a 的值是 a->__forwarding,這個 __forwarding 指針的作用,會在之后介紹
__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;
}
};

// a 變量的結構體定義
struct __Block_byref_a_0 {
void *__isa; // isa 指針
__Block_byref_a_0 *__forwarding; // 類型與 a 變量一模一樣的 __forwarding 結構體指針
int __flags;
int __size;
int a; // a 真正的值
};

// block 描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;

// 這是比起以前,多出的兩個函數指針,一個 copy,一個 dispose
// 這也是 block 中尤為重要的兩個函數
// copy 負責將 block 復制到堆
// dispose 負責在 block 釋放時,釋放 block 所持有的內存
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};

// block 實現
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 訪問到 block 中的 a 結構體指針變量
__Block_byref_a_0 *a = __cself->a; // bound by ref

// 輸出
// 可以看到,這里訪問的 a 的值,是通過 __forwarding 指針訪問的,包括之后的賦值,也是用的 __forwarding
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_0, (a->__forwarding->a));

// 將 a 的值重新賦為 50
(a->__forwarding->a) = 50;
}

// copy 函數
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {
// 使用 _Block_object_assign 函數進行拷貝
_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}

// dispose 函數
static void __main_block_dispose_0(struct __main_block_impl_0src) {
_Block_object_dispose((void
)src->a, 8/BLOCK_FIELD_IS_BYREF/);
}

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    // 初始化 a 的結構體變量
    // 傳入的參數分別是:
    // isa: void *0; 
    // __forwarding: &a,即 a 結構體變量的地址
    // __flags: 0
    // __size: sizeof(結構體)
    // a: 10,即 a 的值
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

    // block 定義
    dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    // 對 a 變量賦值
    (a.__forwarding->a) = 100;

    // block 的調用
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
                        
    // 輸出 a
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_xxx_1, (a.__forwarding->a));
}
return 0;

}
經過了這一坨編譯后代碼的理解,現在已經腦子已經是一團糨糊了,莫名多出的結構體、__forwarding 指針,copy 與 dispose 函數無一不在提高理解的門檻。代碼都看得懂,但是就是不知道這樣做的目的,下面我們便來將多出的東西,重新整理一次,然后注意理解:

現在的 block 內存結構是怎樣的?
為什么 a 被 __block 修飾以后,就變成了 __Block_byref_a_0 結構體?
多出的 __forwarding 指針是什么?
為什么之后無論是在 block 內部,還是在 block 外部,訪問 a 都變成了 a->__forwarding->a ?
既然定義了 copy 與 dispose 函數,為什么沒有看到顯式調用?如果是隱式調用,那么調用時機是什么時候?
多個 block 對 __block int a = 10; 進行使用,指針會怎樣指向?
當前的 block 內存結構

從 main 函數中 _Block_byref_a_0 的初始化可以看出,給 __forwarding 指針賦的值就是 (__Block_byref_a_0 *)&a,所以 __forwarding 指針是同樣是指向 a 結構體變量本身的。

為什么在 block 中的變量,超出作用域還能使用

在全局區或者是棧上的 block,我們并不能控制它的釋放時機,但是如果 block 在堆中,就可以由我們來控制了。所以,大多數情況下,比如將 block 作為回調方法等時候,block 一般都是在堆上的。

那么,block 是如何拷貝到堆上的呢?這就和 copy 函數有關了。在 ARC 環境下,如果將 block 聲明為:

@property (copy) block;
@property (strong) block;
在賦值時,其實都會調用 Block_copy() 函數,將棧上的 block 拷貝到堆中,此時,block 中所持有的變量就都在堆中了,我們通過管理 block 的生命周期,就能間接管理到 block 持有的變量的生命周期。

block 的 copy 時機

那么 block 何時會 copy 到堆上呢?是顯式,還是隱式?

顯式

在作為屬性定義時,用 copy 和 strong 修飾;
手動調用 [block copy];
隱式

賦值給 __strong 修飾的變量時。因為 ARC 下,__strong 是缺省值,所以只要不是顯式標記了 __unsafe_unretained 或 __weak,block 均會被拷貝到堆上;
作為函數返回值;
含有 usingBlock 的 Cocoa 框架中的方法,如枚舉器;
GCD 的 block。
__forwarding 指針存在的意義

在閱讀編譯代碼時可以發現,block 在讀寫被 __block 標記的變量時,均使用 var->__forwarding->var 來訪問。var 是指針能理解,因為它肯定是對臨時變量進行地址引用,要不然也不能獲得最新的值。但是為什么要在中間加一個 __forwarding 呢?而且 __forwarding 指針還是指向的自己。

來看一個例子(ARC 下):

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...

    __block int a = 10;
    NSLog(@"1. block 定義之前 a 的地址 : %p", &a);
    dispatch_block_t __unsafe_unretained block = ^{
        a = 100;
        NSLog(@"2. 調用 block 時 a 的地址 : %p", &a);
    };
    NSLog(@"3. block 定義之后 a 的地址 : %p", &a);
    dispatch_block_t heapBlock = block;
    NSLog(@"4. block 拷貝到堆上 a 的地址 : %p", &a);
    block();
}
return 0;

}
上面的例子中,一共輸出了四次 a 的地址。其中,1 和 3 的地址是一樣的,2 和 4 的地址是一樣。

這里我還對 block 特地標記了 __unsafe_unretained,防止在定義賦值的時候,就拷貝到堆。而這之后的 heapBlock 則是因為被 __strong 修飾所以將 block 拷貝到了堆。

在 1、3 輸出的時候,a 還在棧上,此時的 block 內存為:

而在 block 拷貝到堆上以后, __forwarding 指針則指向堆上的 a 結構體,所以,內存變成了這樣:

這樣就保證了棧上和堆上的 block,都能訪問到同一個 a 變量,這也是 __forwarding 指針的作用。

block 的存儲區域

之前談到 block 根據存儲位置不同,可分為三種,堆、棧、全局區。那么這三種 block 是怎樣的呢?

NSStackBlock:block 被定義為臨時變量,并且引用了外部變量;
NSMallocBlock:調用了 copy 函數,被拷貝到堆上的 block;
NSGlobalBlock:定義為全局變量,或者臨時變量但是沒有引用外部變量的 block。
多個 block 對 __block 變量的引用

在 block 引用使用 __block 修飾的外部變量時,編譯器去針對這個外部變量生成了結構體,比如我們上面談到的 __Block_byref_a_0 結構體。

之所以這樣做,也是為了能在多個 block 引用時,能夠給對 __Block_byref_a_0 進行復用。所以,當多個 block 引用該變量時,并不會重復生成結構體,而是對該結構體內存進行持有,在 block 銷毀,調用 dispose 時,對內存進行釋放。

循環引用

OC 中的循環引用是一個老生常談的問題,其中最容易出現循環引用的地方,就是 block。都知道,出現循環引用的原因,是因為兩個變量的相互持有,導致誰也無法釋放。斷開循環引用鏈,最常見的方式是:

在源頭斷開:一方不持有另一方;
通過置空斷開:在已經對象使用完畢,需要釋放的時候,將一方置空。
根據這兩種解決方案,block 解決循環引用對應著兩種方式:

使用 __weak 或者 __unsafe_unretained 修飾 block 內部要用到的變量。
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf);
};
self.block();
使用 __block 修飾變量,然后在 block 調用完畢后,在 block 內部對變量置空。
__block typeof(self) blockSelf = self;
self.block = ^{
NSLog(@"%@", blockSelf);
blockSelf = nil;
};
self.block();
使用第二種方式有一個弊端,就是必須要保證 block 會調用,這樣才有機會斷開循環引用,否則無法解決問題。當然,也有優點,即可以控制另一方的釋放時機,保證不調用,就不會釋放。

__weak 與 __strong

通常我們能看到以下寫法:

__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
self.block();
__weak 的作用我們剛才已經提到了,但是在 block 內部又使用 __strong 標記是為什么?這樣會造成循環引用嗎?我們來看看 block 實現編譯過后的代碼:

static void __Test__init_block_func_0(struct __Test__init_block_impl_0 *__cself) {
// 這里變成了值拷貝,而不是指針引用
typeof (self) weakSelf = __cself->weakSelf; // bound by copy

// 雖然是 strong 的,但是是在 block 調用時,才將 self 的值拷貝賦值給臨時變量 weakSelf,之后被 strongSelf 引用
// 根據 ARC 的規則,使用 __strong 修飾的變量,出作用域以后,會插入 release 語句,所以在 block 實現結束后,strongSelf 會釋放,并不會造成循環引用
__attribute__((objc_ownership(strong))) Test * strongSelf =  weakSelf;
NSLog((NSString *)&__NSConstantStringImpl__var_xxx_0, strongSelf);

}
也是因為 ARC 對 __strong 修飾的變量,出作用域才插入 release 的機制,我們可以知道,之所以在 block 內部使用 __strong 修飾變量,是因為防止在 block 執行過程中,變量被釋放的情況。

在 block 中調用的方法含有 self,是否會造成循環引用

再看下面這段代碼:

  • (void)testBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    // 在 block 實現中,調用了 run 方法,而 run 方法中,又用到了 self
    [strongSelf run];
    };
    self.block();
    }

  • (void)run {
    NSLog(@"%@", self);
    }
    之前有人問到這個問題,說如果 block 中調用的方法又用到了 self,會造成循環引用嗎?想想如果會造成,那豈不是很可怕,好像一直這樣寫,都沒什么問題。那這是為什么呢?

別忘了 OC 是消息機制,發送完消息之后就不管了,所以,并不影響消息的實現。

疑問

雖然能將 block 編譯出來看到代碼,但是還是有很多疑問的,希望大家能解答一下。

ARC 與 MRC 下,有無 __block 標識,對 block 持有對象的影響

其實這里是四種狀態:

ARC + 無 __block
MRC + 無 __block
ARC + 有 __block
MRC + 有 __block
首先來看問題 1、2:

  • (instancetype)init {
    self = [super init];
    if (self) {

      // 定義一個可變數組 arr
      NSMutableArray *arr = [NSMutableArray new];
      // 輸出 retainCount
      NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      // 為減少隱式的 __strong 造成拷貝到堆的影響,所以使用 __unsafe_unretained 修飾
      __unsafe_unretained dispatch_block_t block = ^{
          NSLog(@"%@", arr);
          // 輸出調用 block 時,arr 的 retainCount
          NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      };
      // 在定義完 block 后,arr 的 retainCount
      NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 2; MRC: 1
      // 顯示拷貝到堆
      self.block = block;
      // 在 block 拷貝到堆以后,arr 的 retainCount
      NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 3; MRC: 2
    
      // 如果是 MRC,則手動 release
      [arr release];
    

    }
    return self;
    }

// 調用 block

  • (void)run {
    self.block();
    }
    可以看到,同樣沒有使用 __block 修飾,ARC 在 block 定義完以后,arr 的 retainCount 要比 MRC 下多 1,這是因為在 block 的結構體中,所定義的 NSMutableArray *arr,默認的缺省值是 __strong,而導致的持有,而 MRC 下,缺省值不是 __strong 造成的。

再來看問題 2、4,還是借助上面的例子,只是在 arr 定義時,在前面使用 __block 進行修飾,但這一次的 retainCount 卻大為不同:

  • (instancetype)init {
    self = [super init];
    if (self) {

      __block NSMutableArray *arr = [NSMutableArray new];
      NSLog(@"1. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      __unsafe_unretained dispatch_block_t block = ^{
          NSLog(@"%@", arr);
          NSLog(@"2. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      };
      NSLog(@"3. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      self.block = block;
      NSLog(@"4. %ld", CFGetRetainCount((__bridge CFTypeRef)(arr))); // ARC: 1; MRC: 1
      
      [arr release];
    

    }
    return self;
    }

  • (void)run {
    self.block();
    }
    這一次,我們發現,無論是 ARC,還是 MRC,arr 的 retainCount 始終為 1,在查閱資料后,找到這樣一句話:

在 MRC 下,__block 說明符可被用來避免循環引用,是因為當 block 從棧復制到堆上時,如果變量被 __block 修飾,則不會再次 retain,如果沒有被 __block 修飾,則會被 retain。
但是,從上面的代碼輸出來看,ARC 和 MRC,block 是否拷貝到堆上,都沒有再次對變量進行持有,retainCount 始終為 1,所以,到這里我遇到幾個不太理解的地方:

__block 修飾符不再持有對象,僅僅是在 MRC 下有效,還是 ARC 與 MRC 下效果是相同的?
如果效果是相同的,為什么 __block 不能解決 ARC 下的循環引用問題?
不能解決 ARC 下的循環引用問題,是否是因為 ARC 下,arr 定義時,缺省值是 __strong 導致的?
在 ARC 下,變量出作用域,編譯器插入 release,為什么 arr 的 retainCount 是 1,經過一次 release 以后,并未出現問題,而在 MRC 下,在 block 調用的時候,就會出現 crash?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,778評論 0 23
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數 截取自動變量值 int main(){ ...
    南京小伙閱讀 952評論 1 3
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個新特性,它為我們開發提供了便利,比如GCD...
    西門吹雪123閱讀 932評論 0 4
  • Block基礎回顧 1.什么是Block? 帶有局部變量的匿名函數(名字不重要,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,786評論 5 61
  • 序言:翻閱資料,學習,探究,總結,借鑒,謝謝探路者,我只是個搬運工。參考、轉發資料:http://www.cnbl...
    Init_ZSJ閱讀 904評論 0 1