Block源碼分析與詳解

iOS開發(fā)---Block詳解

Block的基礎

什么是Blocks蔑穴?

  • 用一句話來描述:帶有自動變量的匿名函數(shù)(是不是一臉懵逼,不要擔心惧浴,整篇博客都會圍繞這句話展開)顧名思義:Block沒有函數(shù)名澎剥,另外Block帶有"^"標記,插入記號便于查找到Block
  • Blocks 也被稱作閉包代碼塊赶舆。展開來講,Blocks就是一個代碼塊祭饭,把你想要執(zhí)行的代碼封裝在這個代碼塊里芜茵,等到需要的時候再去調(diào)用。
  • Block 共享局部作用域的數(shù)據(jù)倡蝙。Block 的這項特征非常有用九串,因為如果您實現(xiàn)一個方法,并且該方法定義一個塊寺鸥,則該塊可以訪問該方法的局部變量和參數(shù)(包括堆棧變量)猪钮,以及函數(shù)和全局變量(包括實例變量)。這種訪問是只讀的胆建,但如果使用 __block 修飾符聲明變量烤低,則可在 Block 內(nèi)更改其值。即使包含有塊的方法或函數(shù)已返回笆载,并且其局部作用范圍已銷毀扑馁,但是只要存在對該塊的引用,局部變量仍作為塊對象的一部分繼續(xù)存在凉驻。

Blocks的語法

  • Block的完整語法格式如下:
^ returnType (argument list) {
  expressions
}

用一張圖來表示

image
  • 也可以寫省略格式的Block,比如省略返回值類型:
^ (int x) {
  return x;
}

Block省略返回值類型時腻要,如果表達式中有return語句就使用該返回值的類型,沒有return語句就使用void類型雄家。

  • 如果沒有參數(shù)列表,也可以省略參數(shù)列表:
^ {
  NSLog(@"hello world");
}

  • 與c語言的區(qū)別
    1. 沒有函數(shù)名
    2. 帶有"^"符號

Block類型變量

  • Block類型變量與一般的C語言變量完全相同胀滚,可以作為自動變量趟济,函數(shù)參數(shù)乱投,靜態(tài)變量,全局變量使用
  • C語言函數(shù)將是如何將所定義的函數(shù)的地址賦值給函數(shù)指針類型變量中
int func (int count)
{
    return count + 1;
}

int (*funcptr) (int) = &func;

  • 使用Block語法就相當于生成了可賦值給Block類型變量的值咙好。
//Blocks 變量聲明與賦值
int (^blk) (int) = ^int (int count) {
    return count + 1;
};
//把Block語法生成的值賦值給Block類型變量
int (^myBlock)(int) = blk; 

與前面的使用函數(shù)指針的源代碼對比可知篡腌,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的""變?yōu)?“^”*

  • 在函數(shù)返回值中指定Block類型,可以將Block作為函數(shù)的返回值返回勾效。
int (^func()(int)) {
    return ^(int count){
        return count + 1;
    }
}

Block在oc中的使用

  • 通過property聲明成員變量:@property (nonatomic, copy) 返回值類型 (^變量名) (參數(shù)列表);
@property (nonatomic, copy) void (^callBack) (NSString *);

- (void)useBlock {
  self.callBack = ^ (NSString *str){
    NSLog(@"useBlock %@", str);
  };
  self.callBack(@"hello world");
}

  • 作為方法參數(shù):- (void)someMethodThatTaksesABlock:(返回值類型 (^)(參數(shù)列表)) 變量名;
- (void)callBackAsAParameter:(void (^)(NSString *print))callBack {
  callBack(@"i am alone");
}

//調(diào)用該函數(shù)
[self callbackAsAParameter:^(NSString *print) {
    NSLog(@"here is %@",print);
}];

  • 通過typedef定義變量類型
//typedef 返回值類型 (^聲明名稱)(參數(shù)列表);
//聲明名稱 變量名 = ^返回值類型(參數(shù)列表) { 表達式 };
typedef void (^callBlock)(NSSting *);

callBlock block = ^(NSString *str) {
  NSLog(@"%@", str);
}

與上一個知識點中指定Block為函數(shù)返回值對比

//原來的記述方式
void func(void (^blk)(NSString 8))
//用了 typedef 定義后的記述方式
void func(callBlock blk)

//原來的記述方式
void (^func()(NSString *)) 
//用了 typedef 定義后的記述方式
callBlock func()

Block截取變量

截獲自動變量的值

  • 我們先看一個??
// 使用 Blocks 截獲局部變量值
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    myLocalBlock();    // 打印結果:a = 10, b = 20

    a = 20;
    b = 30;

    myLocalBlock();    // 打印結果:a = 10, b = 20
}

為什么兩次打印結果都是 a = 10, b = 20嘹悼?

明明在第一次調(diào)用 myLocalBlock();之后已經(jīng)重新給變量 a、變量 b 賦值了层宫,為什么第二次調(diào)用 myLocalBlock();的時候杨伙,使用的還是之前對應變量的值?

因為 Block 語法的表達式使用的是它之前聲明的局部變量a萌腿、變量 b限匣。Blocks 中,Block 表達式截獲所使用的局部變量的值毁菱,保存了該變量的瞬時值米死。所以在第二次執(zhí)行 Block 表達式時,即使已經(jīng)改變了局部變量 ab 的值贮庞,也不會影響 Block 表達式在執(zhí)行時所保存的局部變量的瞬時值峦筒。

這就是 Blocks 變量截獲局部變量值的特性。

通過__block說明符賦值

  • 上面我們想修改變量a窗慎,變量b的值物喷,但是有沒有成功,那么我們難道就沒有方法來修改了么遮斥?別急峦失,__block來也,只要用這個說明符修飾變量术吗,就可以在塊中修改尉辑。
// 使用 __block 說明符修飾,更改局部變量值
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;
    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;
        printf("a = %d, b = %d\n",a, b);  // 打印結果:a = 20, b = 30
    };

    myLocalBlock();
}

可以看到较屿,使用__block說明符修飾之后材蹬,我們在 Block表達式中,成功的修改了局部變量值吝镣。

使用__block修飾符的自動變量被稱為__blcok變量

Block的實現(xiàn)

在上一部分我們知道了Blocks帶有局部變量的匿名函數(shù)堤器。但是 Block 的實質(zhì)究竟是什么呢?類型末贾?變量闸溃?

要想了解 Block 的本質(zhì),就需要從 Block 對應的 C++ 源碼來入手。

下面我們通過一步步的源碼剖析來了解 Block 的本質(zhì)辉川。

Block的實質(zhì)是什么表蝙?

準備工作

  1. 在項目中添加 blocks.m 文件,并寫好 block 的相關代碼乓旗。
  2. 打開『終端』府蛇,執(zhí)行 cd XXX/XXX 命令,其中 XXX/XXXblock.m所在的目錄屿愚。
  3. 繼續(xù)執(zhí)行clang -rewrite-objc block.m
  4. 執(zhí)行完命令之后汇跨,block.m 所在目錄下就會生成一個block.cpp文件,這就是我們需要的 block 相關的 C++源碼妆距。

Block源碼預覽

  • 轉(zhuǎn)換前OC代碼:
int main () {
    void (^myBlock)(void) = ^{
        printf("myBlock\n");
    };
    myBlock();
    return 0;
}

  • 轉(zhuǎn)換后c++代碼:
/* 包含 Block 實際函數(shù)指針的結構體 */
struct __block_impl {
    void *isa;
    int Flags;               
    int Reserved;       // 今后版本升級所需的區(qū)域大小
    void *FuncPtr;      // 函數(shù)指針
};

/* Block 結構體 */
struct __main_block_impl_0 {
    // impl:Block 的實際函數(shù)指針穷遂,指向包含 Block 主體部分的 __main_block_func_0 結構體
    struct __block_impl impl;
    // Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結構體
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 構造函數(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;
    }
};

/* Block 主體部分結構體 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("myBlock\n");
}

/* Block 附加信息結構體:包含今后版本升級所需區(qū)域大小娱据,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升級所需區(qū)域大小
    size_t Block_size;    // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/* main 函數(shù) */
int main () {
    //myBlock的初始化
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    //myBlock的調(diào)用
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

下面我們一步一步來拆解轉(zhuǎn)換后的源碼

Block結構體
  • 我們先來看看 __main_block_impl_0 結構體( Block 結構體)
/* Block 結構體 */
struct __main_block_impl_0 {
    // impl:Block 的實際函數(shù)指針蚪黑,指向包含 Block 主體部分的 __main_block_func_0 結構體
    struct __block_impl impl;
    // Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結構體
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 構造函數(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_0 結構體(Block 結構體)包含了三個部分:

  1. 成員變量 impl;
  2. 成員變量 Desc 指針;
  3. __main_block_impl_0 構造函數(shù)忌穿。
struct __block_impl 結構

我們先看看第一部分impl__block_impl結構體類型的成員變量

/* 包含 Block 實際函數(shù)指針的結構體 */
struct __block_impl {
    void *isa;          // 用于保存 Block 結構體的實例指針
    int Flags;          // 標志位
    int Reserved;       // 今后版本升級所需的區(qū)域大小
    void *FuncPtr;      // 函數(shù)指針
};

  • __block_impl包含了 Block 實際函數(shù)指針 FuncPtrFuncPtr指針指向 Block 的主體部分结啼,也就是 Block 對應 OC 代碼中的 ^{ printf("myBlock\n"); };部分掠剑。
  • 還包含了標志位 Flags,在實現(xiàn)block的內(nèi)部操作時會用到
  • 今后版本升級所需的區(qū)域大小 Reserved
  • __block_impl結構體的實例指針 isa妆棒。
static struct __main_block_desc_0結構

第二部分 Desc 是指向的是 __main_block_desc_0 類型的結構體的指針型成員變量,__main_block_desc_0 結構體用來描述該 Block 的相關附加信息:

/* Block 附加信息結構體:包含今后版本升級所需區(qū)域大小沸伏,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升級所需區(qū)域大小
    size_t Block_size;  // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

__main_block_impl_0 構造函數(shù)

第三部分是 __main_block_impl_0 結構體(Block 結構體) 的構造函數(shù)糕珊,負責初始化 __main_block_impl_0 結構體(Block 結構體) 的成員變量。

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

  • 關于結構體構造函數(shù)中對各個成員變量的賦值毅糟,我們需要先來看看 main()函數(shù)中红选,對該構造函數(shù)的調(diào)用。
  void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

  • 我們可以把上面的代碼稍微轉(zhuǎn)換一下姆另,去掉不同類型之間的轉(zhuǎn)換喇肋,使之簡潔一點:
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;

  • 這樣,就容易看懂了迹辐。該代碼將通過 __main_block_impl_0構造函數(shù)蝶防,生成的 __main_block_impl_0結構體(Block 結構體)類型實例的指針,賦值給 __main_block_impl_0結構體(Block 結構體)類型的指針變量 myBlock明吩。

可以看到间学, 調(diào)用 __main_block_impl_0構造函數(shù)的時候,傳入了兩個參數(shù)。

  1. 第一個參數(shù):__main_block_func_0低葫。

    • 其實就是 Block 對應的主體部分详羡,可以看到下面關于 __main_block_func_0結構體的定義 ,和OC 代碼中 ^{ printf("myBlock\n"); };部分具有相同的表達式嘿悬。
    • 這里參數(shù)中的 __cself是指向 Block 的值的指針變量实柠,相當于 OC 中的 self
    /* Block 主體部分結構體 */
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("myBlock\n");
    }
    
    
  • 第二個參數(shù):__main_block_desc_0_DATA__main_block_desc_0_DATA包含該 Block 的相關信息善涨。
    我們再來結合之前的 __main_block_impl_0結構體定義窒盐。

    • __main_block_impl_0結構體(Block 結構體)可以表述為:
    struct __main_block_impl_0 {
        void *isa;          // 用于保存 Block 結構體的實例指針
        int Flags;          // 標志位
        int Reserved;       // 今后版本升級所需的區(qū)域大小
        void *FuncPtr;      // 函數(shù)指針
        struct __main_block_desc_0* Desc;        // Desc:Desc 指針
    };
    
    
  • __main_block_impl_0構造函數(shù)可以表述為:

impl.isa = &_NSConcreteStackBlock;     // isa 保存 Block 結構體實例
impl.Flags = 0;        // 標志位賦值
impl.FuncPtr = __main_block_func_0;    // FuncPtr 保存 Block 結構體的主體部分
Desc = &__main_block_desc_0_DATA;      // Desc 保存 Block 結構體的附加信息

調(diào)用

在分析了Block結構體和成員變量委刘,現(xiàn)在我們看看main函數(shù)中是如何調(diào)用block的

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

  • myBlock結構體的第一個成員變量為__block_impl汗捡,所以myBlock首地址疙描,就是__block_impl impl 的首地址恩脂,即可以直接轉(zhuǎn)換為__block_impl 類型
  • (void ()(__block_impl ))__block_implFunc 的類型
  • *((__block_impl )myBlock)->FuncPtr() 調(diào)用函數(shù)
  • *((__block_impl )myBlock) 函數(shù)參數(shù)
Block的實質(zhì)總結

用一句話來說幌氮,Block是個對象

  • 在C語言的底層實現(xiàn)里糟红,它是一個結構體纸兔。這個結構體相當于objc_class的類對象結構體誓沸,用_NSConcreteStackBlock對其中成員變量isa初始化姿锭,_NSConcreteStackBlock相當于class_t結構體實例(在我的理解中就是該 block 實例的元類)塔鳍。在將 Block 作為OC對象處理時,關于該類的信息放置于_NSConcreteStackBlock中呻此。
  • 是對象:其內(nèi)部第一個成員為 isa 指針轮纫;
  • 封裝了函數(shù)調(diào)用:Block內(nèi)代碼塊,封裝了函數(shù)調(diào)用焚鲜,調(diào)用Block掌唾,就是調(diào)用該封裝的函數(shù);
  • 執(zhí)行上下文:Block 還有一個描述 Desc忿磅,該描述對象包含了Block的信息以及捕獲變量的內(nèi)存相關函數(shù)糯彬,及Block所在的環(huán)境上下文;

Block的類型

前面已經(jīng)說過了葱她,Block的本質(zhì)就是一個OC對象撩扒,既然它是OC對象,那么它就有類型吨些。

準備工作:

  • 先把ARC關掉搓谆,因為ARC幫我們做了太多的事,不方便我們觀察結果豪墅。關掉ARC的方法在Build Settings里面搜索Objective-C Automatic Reference Counting泉手,把這一項置為NO。
static int weight = 20;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(void) = ^{
            NSLog(@"%d %d", height, age);
        };

        NSLog(@"%@\n %@\n %@\n %@", [block class], [[block class] superclass], [[[block class] superclass] superclass], [[[[block class] superclass] superclass] superclass]);

        return 0;
    }
}

//打印結果
 __NSStackBlock__
 __NSStackBlock
 NSBlock
 NSObject

  • 這說明上面定義的這個block的類型是NSStackBlock偶器,并且它最終繼承自NSObject也說明Block的本質(zhì)是OC對象螃诅。

Block的三種類型

  • Block有三種類型啡氢,分別是NSGlobalBlock,MallocBlock,NSStackBlock。
    這三種類型的Block對象的存儲區(qū)域如下:
對象的存儲域
NSStackBlock(_NSConcreteStackBlock)
NSGlobalBlock(_NSConcreteGlobalBlock) 程序的數(shù)據(jù)區(qū)域(.data區(qū))
NSMallocBlock(_NSConcreteMallocBlock)

截獲了自動變量的Block是NSStackBlock類型术裸,沒有截獲自動變量的Block則是NSGlobalStack類型,NSStackBlock類型的Block進行copy操作之后其類型變成了NSMallocBlock類型倘是。

  • 下面我們用一張圖來看一下不同block的存儲區(qū)域
image
  • 從上圖可以發(fā)現(xiàn),根據(jù)block的類型不同袭艺,block存放在不同的區(qū)域中搀崭。
    1. 數(shù)據(jù)段中的__NSGlobalBlock__直到程序結束才會被回收,不過我們很少使用到__NSGlobalBlock__類型的block猾编,因為這樣使用block并沒有什么意義瘤睹。
    2. __NSStackBlock__類型的block存放在棧中,我們知道棧中的內(nèi)存由系統(tǒng)自動分配和釋放答倡,作用域執(zhí)行完畢之后就會被立即釋放轰传,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉。
    3. __NSMallocBlock__是在平時編碼過程中最常使用到的瘪撇。存放在堆中需要我們自己進行內(nèi)存管理获茬。
  1. _NSConcreteGlobalBlock

    • 在以下兩種情況下使用 Block 的時候,BlockNSConcreteGlobalBlock類對象倔既。
    1. 記述全局變量的地方恕曲,使用 Block 語法時;
    2. Block 語法的表達式中沒有截獲的自動變量時渤涌。
    • NSConcreteGlobalBlock類的 Block 存儲在『程序的數(shù)據(jù)區(qū)域』佩谣。因為存放在程序的數(shù)據(jù)區(qū)域,所以即使在變量的作用域外实蓬,也可以通過指針安全的使用茸俭。

    • 記述全局變量的地方,使用 Block 語法示例代碼:

    void (^myGlobalBlock)(void) = ^{
        printf("GlobalBlock\n");
    };
    
    int main() {
        myGlobalBlock();
    
        return 0;
    }
    
    
  2. 通過對應 C++ 源碼安皱,我們可以發(fā)現(xiàn):Block 結構體的成員變量 isa賦值為:impl.isa = &_NSConcreteGlobalBlock;调鬓,說明該 BlockNSConcreteGlobalBlock類對象。

  3. _NSConcreteStackBlock

除了_NSConcreteGlobalBlock中提到的兩種情形练俐,其他情形下創(chuàng)建的 Block 都是 NSConcreteStackBlock對象袖迎,平常接觸的 Block 大多屬于 NSConcreteStackBlock對象冕臭。

NSConcreteStackBlock類的 Block 存儲在『棧區(qū)』的腺晾。如果其所屬的變量作用域結束,則該 Block 就會被廢棄辜贵。如果 Block 使用了 __block變量悯蝉,則當 __block變量的作用域結束,則 __block變量同樣被廢棄托慨。

image
  1. _NSConcreteMallocBlock

為了解決棧區(qū)上的 Block 在變量作用域結束被廢棄這一問題鼻由,Block 提供了 『復制』功能。可以將 Block 對象和 __block變量從棧區(qū)復制到堆區(qū)上蕉世。當 Block 從棧區(qū)復制到堆區(qū)后蔼紧,即使棧區(qū)上的變量作用域結束時,堆區(qū)上的 Block__block變量仍然可以繼續(xù)存在狠轻,也可以繼續(xù)使用。

image

此時,『堆區(qū)』上的 Block 為 NSConcreteMallocBlock 對象,Block 結構體的成員變量 isa 賦值為:impl.isa = &_NSConcreteMallocBlock;

那么怕磨,什么時候才會將 Block 從棧區(qū)復制到堆區(qū)呢氢哮?

這就涉及到了 Block 的自動拷貝和手動拷貝蝎困。

Block的自動拷貝和手動拷貝

Block的自動拷貝

在使用ARC 時禾乘,大多數(shù)情形下編譯器會自動進行判斷澎埠,自動生成將 Block 從棧上復制到堆上的代碼:

  1. Block 作為函數(shù)返回值返回時,會自動拷貝始藕;
  2. 向方法或函數(shù)的參數(shù)中傳遞 Block 時蒲稳,使用以下兩種方法的情況下,會進行自動拷貝伍派,否則就需要手動拷貝:
    1. Cocoa 框架的方法且方法名中含有 usingBlock等時江耀;
    2. Grand Central Dispatch(GCD)的 API。
  3. Block 賦值給類的附有 __strong修飾符的id類型或 Block 類型成員變量時
Block的手動拷貝

我們可以通過『copy 實例方法(即 alloc / new / copy / mutableCopy)』來對 Block 進行手動拷貝诉植。當我們不確定 Block 是否會被遺棄祥国,需不需要拷貝的時候,直接使用 copy 實例方法即可晾腔,不會引起任何的問題舌稀。

關于 Block 不同類的拷貝效果總結如下:

Block 類 存儲區(qū)域 拷貝效果
_NSConcreteStackBlock 棧區(qū) 從棧拷貝到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 不做改變
_NSConcreteMallocBlock 堆區(qū) 引用計數(shù)增加
__block變量的拷貝

在使用 __block變量的 Block 從棧復制到堆上時灼擂,__block變量也會受到如下影響:

__block 變量的配置存儲區(qū)域 Block 從棧復制到堆時的影響
堆區(qū) 從棧復制到堆壁查,并被 Block 所持有
棧區(qū) 被 Block 所持有

當然,如果不再有 Block 引用該 __block變量剔应,那么 __block變量也會被廢除睡腿。

Block截獲變量實質(zhì)

我們下面見根據(jù)變量修飾符,來探查 Block 如何捕獲不同修飾符的類型變量峻贮。

  • auto:自動變量修飾符
  • static:靜態(tài)修飾符
  • const:常量修飾符

在這三種修飾符席怪,我們又細分為全局變量和局部變量

Block截獲自動局部變量的實質(zhì)

// 使用 Blocks 截獲局部變量值
int c = 30;//全局變量
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;//局部變量

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d, c = %d\n",a, b月洛, c);
    };
    void (^Block)(int, int, int) = ^(int a, int b, int c){
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };

    myLocalBlock();    // 輸出結果:a = 10, b = 20, c = 30

    a = 20;
    b = 30;

    myLocalBlock();    // 輸出結果:a = 10, b = 20, c = 30
    Block(a, b, c);              // 輸出結果:a = 20, b = 30, c = 30
}

Block塊中直接調(diào)用局部變量
  • 從中我們可以看出何恶,我們在第一次調(diào)用 myLocalBlock(); 之后已經(jīng)重新給變量 a孽锥、變量 b 賦值了嚼黔,但是第二次調(diào)用 myLocalBlock(); 的時候细层,使用的還是之前對應變量的值。

這是因為Block 語法的表達式使用的是它之前申明的局部變量a唬涧、變量b疫赎。Blocks中,Block表達式截獲所使用的局部變量的值碎节,保存了該變量的瞬時值捧搞。所以再第二次執(zhí)行Block表達式的時候,即使已經(jīng)改變了局部變量ab的值狮荔,也不會影響B(tài)lock表達式在執(zhí)行時所保存的局部變量的瞬時值胎撇。

這就是Block變量截獲局部變量值的特性

  • ??:Block 語法表達式中沒有使用的自動變量不會被追加到結構體中,Blocks 的自動變量截獲只針對 Block使用的自動變量殖氏。

  • 可是晚树,為什么 Blocks 變量使用的是局部變量的瞬時值,而不是局部變量的當前值呢雅采?讓我們看一下對應的C++代碼

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

    printf("a = %d, b = %d, c = %d\n",a, b, c);
}

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 a = 10, b = 20;

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    a = 20;
    b = 30;

    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
}

  1. 可以看到 __main_block_impl_0 結構體(Block 結構體)中多了兩個成員變量 ab爵憎,這兩個變量就是 Block 截獲的局部變量。 ab 的值來自與 __main_block_impl_0 構造函數(shù)中傳入的值婚瓜。
  struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int a;    // 增加的成員變量 a
        int b;    // 增加的成員變量 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;
        }
    };

  1. 還可以看出 __main_block_func_0(保存 Block 主體部分的結構體)中宝鼓,變量 a、b 的值使用的 __cself獲取的值巴刻。
    __cself->a愚铡、__cself->b 是通過值傳遞的方式傳入進來的,而不是通過指針傳遞胡陪。這也就說明了 a茂附、b 只是 Block 內(nèi)部的變量,改變 Block 外部的局部變量值督弓,并不能改變 Block 內(nèi)部的變量值营曼。
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
    printf("a = %d, b = %d\n",a, b);
}

  1. 我們可以看出全局變量并沒有存儲在Block的結構體中,而是在調(diào)用的時候通過直接訪問的方式來調(diào)用愚隧。
  • 下面用一張圖我們把上面所說的全局作用域和局部作用域做一個總結
變量類型 抓獲到Block對象內(nèi)部 訪問方式
局部變量 指傳遞
全局變量 直接訪問
Block通過傳值間接訪問局部變量
// 使用 Blocks 截獲局部變量值
int c = 30;
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^Block)(void) = ^(int a, int b){
        printf("a = %d, b = %d\n",a, b);
    };

    a = 20;
    b = 30;
    Block(a,b);              // 輸出結果:a = 20, b = 30, c = 30
}

  • 我們來看看直接傳值和通過block截獲局部變量的區(qū)別
struct __main_block_impl_1 {
    struct __block_impl impl;
    struct __main_block_desc_1* Desc;
    __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_1(struct __main_block_impl_1 *__cself, int a, int b, int c) {

    printf("a = %d, b = %d, c = %d\n",a, b, c);
}

static struct __main_block_desc_1 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10, b = 20;

        void (*Block)(int,int) = ((void (*)(int, int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));

        a = 20;
        b = 30;

        ((void (*)(__block_impl *, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
    }
    return 0;
}

  1. 可以看見__main_block_impl_1結構體中沒有變量a蒂阱、變量b,說明通過直接傳值的方式,變量并沒有存進Block的結構體中狂塘。
  2. __main_block_func_1函數(shù)中录煤,發(fā)現(xiàn)參數(shù)列表中多了int a, int b這兩個參數(shù),還有調(diào)用Block的時候荞胡,直接把變量a妈踊、b的值傳入進去了。

Block截獲static修飾變量的實質(zhì)

下面我們定義了三個變量:

  • 全局
    • 變量:c
  • 局部
    • 常量:a
    • 變量:b
static int c = 30;
- (void)useBlockInterceptLocalVariables {
    static const int a = 10;
    static int b = 20;

    void (^Block)(void) = ^{
        b = 50;
        c = 60;
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };

    b = 30;
    c = 40;
    Block();                 // 輸出結果:a = 10, b = 50, c = 60
}

  • 從以上測試結果我們可以得出:
    • Block 對象能獲取最新的靜態(tài)全局變量靜態(tài)局部變量泪漂;
    • 靜態(tài)局部常量由于值不會更改廊营,故沒有變化歪泳;
  • 我們來看看c++代碼,到底發(fā)生了什么
static int c = 30;//全局靜態(tài)變量

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *b;//捕獲變量露筒,獲取變量地址
    const int *a;//捕獲變量呐伞,獲取變量地址
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), 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) {
    //2.通過Block對象獲取到b和a的指針
    int *b = __cself->b; // bound by copy
    const int *a = __cself->a; // bound by copy
        //通過b指針,修改b指向的值
    (*b) = 50;
    c = 60;
    printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}

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 const int a = 10;
        static int b = 20;

                //1.傳入&a, &b地址進行Blcok對象的初始化
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b, &a));

        b = 30;
        c = 40;
        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
    }
    return 0;
}

  1. 可以看到在 __main_block_impl_0 結構體中慎式,靜態(tài)局部變量static int b以指針的形式添加為成員變量伶氢,而靜態(tài)局部常量static const int aconst int *的形式添加為成員變量。而全局靜態(tài)變量static int c 并沒有添加為成員變量
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *b;//捕獲變量瘪吏,獲取變量地址
    const int *a;//捕獲變量癣防,獲取變量地址
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

  1. 再來看看 __main_block_func_0 結構體部分,靜態(tài)全局變量static int c是直接訪問的掌眠,靜態(tài)局部變量static int b是通過『指針傳遞』的方式進行訪問劣砍,靜態(tài)局部常量static const int a也是通過『指針傳遞』的方式進行訪問,但是它是通過const修飾的無法進行賦值操作扇救。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    //2.通過Block對象獲取到b和a的指針
    int *b = __cself->b; // bound by copy
    const int *a = __cself->a; // bound by copy
        //通過b指針刑枝,修改b指向的值
    (*b) = 50;
    c = 60;
    printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}

  • 我們?yōu)槭裁茨塬@取 static 變量最新的值?

    1. static 修飾的迅腔,其作用區(qū)域不管是全局還是局部装畅,不管是常量還是變量,均存儲在全局存儲區(qū)中沧烈,存在全局存儲區(qū)掠兄,該地址在程序運行過程中一直不會改變,所以能訪問最新值锌雀。
    2. static 修飾后:
    • 全局變量蚂夕,直接訪問
    • 局部變量腋逆,指針訪問婿牍。其中在局部變量中,又有局部靜態(tài)常量惩歉,即被const 修飾的等脂。
      • const 存放在 text 段中,即使被 static 同時修飾撑蚌,也存放text 中的常量區(qū)上遥;

Block截獲const修飾變量的實質(zhì)

  • 如下定義:
    • const 全局變量:a
    • const 局部變量:b
const int a = 10;
- (void)useBlockInterceptLocalVariables {
    const int b = 20;

    void (^Block)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    Block();                 // 輸出結果:a = 10, b = 20
}

  • 看看轉(zhuǎn)換后的c++代碼
static int a = 10;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    const int b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int _b, int flags=0) : 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) {
    const int b = __cself->b; // bound by copy
    printf("a = %d, b = %d\n",a, 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; 
        const int b = 20;
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b));
        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}

  • 從上面看出:

    • const 全局變量直接訪問
    • const 局部變量争涌,其實仍然是 auto 修飾粉楚,值傳遞
  • 最后我們用一張圖來總結一下這一節(jié)所學的內(nèi)容

image

Block截獲對象實質(zhì)

  • 在前一節(jié)我們學習了Block如何截獲基本類型,這一節(jié)我們主要學習截獲對象類型的auto變量
Person *person = [[Person alloc] init];
person.age = 20;
void (^block)(void) = ^{
  NSLog(@"age = %d", person.age);
};
block();

  • 根據(jù)Block捕獲基本變量的規(guī)律模软,針對對象伟骨,仍然適用
    • auto 變量捕獲后,Block 中變量的類型和變量原類型一致撵摆;
    • static 變量捕獲后,Block 對應的變量是對應變量的指針類型害晦;

那么特铝,auto 對象與基本類型在 Block 內(nèi)部有什么區(qū)別呢。

  • 我們把上述代換轉(zhuǎn)換成C++
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *person;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Person *person = __cself->person; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_f5936b_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 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), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

  1. 我們看到__main_block_impl_0結構體中多了一個一個成員變量Person *person壹瘟,因為person是自動變量鲫剿,所以這里捕獲了自動變量person作為_main_block_impl_0結構體的成員變量。而且還要注意的是稻轨,由于是自動變量灵莲,所以在block外面,自動變量是什么類型殴俱,在結構體里面作為成員變量就是什么類型政冻。person在結構體外面作為自動變量是指針類型,所以作為結構體里面的成員變量也是指針類型线欲。
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *person;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

  1. 我們看到__main_block_desc_0結構中多了兩個函數(shù)指針void (*copy)void (*dispose)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

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

針對這兩個函數(shù)明场,它們的作用就是:

函數(shù) 作用 調(diào)用時機
__main_block_copy_0 調(diào)用 _Block_object_assign,相當于 retain李丰,將對象賦值在對象類型的結構體變量 __main_block_impl_0 中苦锨。 棧上的 Block 復制到堆時
__main_block_dispose_0 調(diào)用 _Block_object_dispose,相當于 release趴泌,釋放賦值在對象類型的結構體變量中的對象舟舒。 堆上 Block 被廢棄時

Blocks內(nèi)改寫被截獲變量的值的方式

在Block中修飾被截獲變量的值只有一下兩種情況,我們先分析通過__block修飾符來修改截獲變量的方式

__block修飾符

  • __block 說明符類似于 static嗜憔、auto秃励、register 說明符,它們用于指定將變量值設置到哪個存儲域中吉捶。例如auto 表示作為自動變量存儲在中莺治, static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)中。

__block修飾符又分為修飾局部變量和修飾對象

__blcok修飾局部變量
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;

        printf("a = %d, b = %d\n",a, b);    // 輸出結果:a = 20, b = 30
    };

    myLocalBlock();
}

  • 從中我們可以發(fā)現(xiàn):通過 __block 修飾的局部變量帚稠,可以在 Block 的主體部分中改變值谣旁。

  • 我們來轉(zhuǎn)換下源碼,分析一下:

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

struct __Block_byref_b_1 {
    void *__isa;
    __Block_byref_b_1 *__forwarding;
    int __flags;
    int __size;
    int b;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __Block_byref_b_1 *b; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__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_a_0 *a = __cself->a; // bound by ref
    __Block_byref_b_1 *b = __cself->b; // bound by ref

    (a->__forwarding->a) = 20;
    (b->__forwarding->b) = 30;

    printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}

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*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->b, 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() {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    __Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    return 0;
}

  • 可以看到滋早,只是加上了一個 __block榄审,代碼量就增加了很多。

  • 我們從 __main_block_impl_0 開始說起:

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

  • 我們在 __main_block_impl_0結構體中可以看到: 原 OC 代碼中杆麸,被 __block修飾的局部變量 __block int a搁进、__block int b分別變成了 __Block_byref_a_0浪感、__Block_byref_b_1類型的結構體指針 a、結構體指針 b饼问。這里使用結構體指針 a影兽、結構體指針 b說明 _Block_byref_a_0__Block_byref_b_1類型的結構體并不在 __main_block_impl_0結構體中莱革,而只是通過指針的形式引用峻堰,這是為了可以在多個不同的 Block 中使用 __block修飾的變量。

  • __Block_byref_a_0盅视、__Block_byref_b_1 類型的結構體聲明如下:

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

struct __Block_byref_b_1 {
    void *__isa;
    __Block_byref_b_1 *__forwarding;
    int __flags;
    int __size;
    int b;
};

  • 拿第一個 __Block_byref_a_0結構體定義來說明捐名,__Block_byref_a_0有 5 個部分:

    1. __isa:標識對象類的 isa實例變量

    2. __forwarding:傳入變量的地址

    3. __flags:標志位

    4. __size:結構體大小

    5. a:存放實變量 a實際的值,相當于原局部變量的成員變量(和之前不加__block修飾符的時候一致)闹击。

  • 再來看一下 main()函數(shù)中镶蹋,__block int a__block int b的賦值情況赏半。

__Block_byref_a_0 a = {
    (void*)0,
    (__Block_byref_a_0 *)&a, 
    0, 
    sizeof(__Block_byref_a_0), 
    10
};

__Block_byref_b_1 b = {
    0,
    &b, 
    0, 
    sizeof(__Block_byref_b_1), 
    20
};

  • 還是拿第一個__Block_byref_a_0 a的賦值來說明贺归。

  • 可以看到 __isa指針值傳空,__forwarding指向了局部變量 a本身的地址断箫,__flags分配了 0牧氮,__size為結構體的大小,a賦值為 10瑰枫。下圖用來說明 __forwarding指針的指向情況踱葛。

image
  • 這下,我們知道 __forwarding 其實就是局部變量 a 本身的地址光坝,那么我們就可以通過 __forwarding 指針來訪問局部變量尸诽,同時也能對其進行修改了。

  • 來看一下 Block 主體部分對應的 __main_block_func_0 結構體來驗證一下盯另。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    __Block_byref_b_1 *b = __cself->b; // bound by ref

    (a->__forwarding->a) = 20;
    (b->__forwarding->b) = 30;

    printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}

可以看到 (a->__forwarding->a) = 20;(b->__forwarding->b) = 30;是通過指針取值的方式來改變了局部變量的值性含。這也就解釋了通過 __block來修飾的變量,在 Block 的主體部分中改變值的原理其實是:通過『指針傳遞』的方式鸳惯。

__block修飾對象
- (void)useBlockQualifierChangeLocalVariables {
    __block Person *person = [[Person alloc] init];

    void(^block)(void) = ^ {
      person = [[Person alloc] init];//修改person創(chuàng)建的地址
      NSLog(@"person is %@", person);
    };
    block();
}

  • 把上述代碼轉(zhuǎn)換成C++
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);
}

struct __Block_byref_person_0 {
    void *__isa;
    __Block_byref_person_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *person;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_person_0 *person; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,         __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref

    (person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_1f2ea2_mi_0, (person->__forwarding->person));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 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_person_0 person = {
           (void*)0,
           (__Block_byref_person_0 *)&person,
           33554432,
           sizeof(__Block_byref_person_0), 
             __Block_byref_id_object_copy_131,
           __Block_byref_id_object_dispose_131,
           ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
           sel_registerName("alloc")),
           sel_registerName("init"))
       };

       void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

  • 我們先從 __main_block_impl_0 開始說起:
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_person_0 *person; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

  • 我們在 __main_block_impl_0 結構體中可以看到: 原 OC代碼中商蕴,被 __block 修飾的person變成了 __Block_byref_person_0類型結構體指針
  • __Block_byref_person_0類型結構體聲明和該結構體初始化如下:
struct __Block_byref_person_0 {
    void *__isa;
    __Block_byref_person_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *person;
};

__Block_byref_person_0 person = {
           (void*)0,
           (__Block_byref_person_0 *)&person,
           33554432,
           sizeof(__Block_byref_person_0), 
             __Block_byref_id_object_copy_131,
           __Block_byref_id_object_dispose_131,
           ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
           sel_registerName("alloc")),
           sel_registerName("init"))
       };

  • 我們發(fā)現(xiàn),在_Block_byref_person_0中多了兩個函數(shù)芝发,通過其初始化可以知道這兩個函數(shù)分別是__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131這兩個函數(shù)
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);
}

  • 這兩個函數(shù)其實和_main_block_copy_0_main_block_dispose_0一樣绪商,最終都是調(diào)用_Block_object_assign_Block_object_dispose這兩個函數(shù)。那么這里為什么都加上了40呢辅鲸?我們分析一下_Block_byref_person_0的結構:
struct __Block_byref_person_0 {
    void *__isa;    //指針格郁,8字節(jié)
    __Block_byref_person_0 *__forwarding; //指針,8字節(jié)
    int __flags; //int型,4字節(jié)
    int __size;  //int型例书,4字節(jié)
    void (*__Block_byref_id_object_copy)(void*, void*);//指針型锣尉,8字節(jié)
    void (*__Block_byref_id_object_dispose)(void*);//指針型,8字節(jié)
    Person *person;
};

  • 這樣一來决采,_Block_byref_person_0的地址和person指針的地址就相差40字節(jié)自沧,所以+40的目的就是找到person指針。

更改特殊區(qū)域變量值

  • C語言中有幾種特殊區(qū)域的變量树瞭,允許 Block 改寫值拇厢,具體如下:
    • 靜態(tài)局部變量
    • 靜態(tài)全局變量
    • 全局變量

下面我們通過代碼來看看這種情況

  • OC代碼
int global_val = 10; // 全局變量
static int static_global_val = 20; // 靜態(tài)全局變量

int main() {
    static int static_val = 30; // 靜態(tài)局部變量

    void (^myLocalBlock)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;

        printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, static_val);
    };

    myLocalBlock();

    return 0;
}

  • C++代碼
int global_val = 10;
static int static_global_val = 20;

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}

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() {
    static int static_val = 30;

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    return 0;

}

  • 從中可以看到:
    • __main_block_impl_0 結構體中,將靜態(tài)局部變量 static_val 以指針的形式添加為成員變量移迫,而靜態(tài)全局變量 static_global_val旺嬉、全局變量 global_val 并沒有添加為成員變量管行。
int global_val = 10;
static int static_global_val = 20;

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

  • 再來看一下 Block 主體部分對應的 __main_block_func_0 結構體部分厨埋。靜態(tài)全局變量 static_global_val、全局變量 global_val 是直接訪問的捐顷,而靜態(tài)局部變量 static_val 則是通過『指針傳遞』的方式進行訪問和賦值荡陷。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}

  • 靜態(tài)變量的這種方法似乎也適用于自動變量的訪問,但我們?yōu)槭裁礇]有這么做呢迅涮?

    實際上废赞,在由 Block 語法生成的值 Block 上,可以存有超過其變量域的被截獲對象的自動變量叮姑。變量作用域結束的同時唉地,原來的自動變量被廢棄,因此 Block 中超過變量作用域而存在的變量同靜態(tài)變量一樣猜欺,將不能通過指針訪問原來的自動變量婆廊。

  • 最后我們用一張圖來總結一下這一節(jié)所學

image

__block變量內(nèi)存管理

  • 我們先回顧一下之前的一些捕獲變量或?qū)ο笫侨绾喂芾韮?nèi)存的庸追。

  • 注:下面 “干預” 是指不用程序員手動管理,其實本質(zhì)還是要系統(tǒng)管理內(nèi)存的分配與釋放群嗤。

    • auto 局部基本類型變量,因為是值傳遞兵琳,內(nèi)存是跟隨 Block狂秘,不用干預;
    • static 局部基本類型變量躯肌,指針傳遞者春,由于分配在靜態(tài)區(qū),故不用干預清女;
    • 全局變量碧查,存儲在數(shù)據(jù)區(qū),不用多說,不用干預忠售;
    • 局部對象變量传惠,如果在棧上,不用干預稻扬。但 Block 在拷貝到堆的時候卦方,對其 retain,在 Block 對象銷毀時泰佳,對其 release盼砍;

在這里,__block 變量呢逝她?

注意點就是:__block 變量在轉(zhuǎn)換后封裝成了一個新對象浇坐,內(nèi)存管理會多出一層。

基本類型的Desc

上述 age 是基本類型黔宛,其轉(zhuǎn)換后的結構體為:

struct __Block_byref_age_0 {
    void *__isa;    
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

Block 中的 Desc 如下:

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

//下面兩個方法是Block內(nèi): 內(nèi)存管理相關函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

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

針對基本類型近刘,以 age 類型為例:

  • __Block_byref_age_0 對象同樣是在 Block對象從棧上拷貝到堆上,進行 retain臀晃;
  • Block對象銷毀時觉渴,對__Block_byref_age_0 進行 release
  • __Block_byref_age_0 內(nèi) age徽惋,由于是基本類型案淋,是不用進行內(nèi)存手動干預的。

對象類型的Desc

下面看__block 對象類型的轉(zhuǎn)換:

struct __Block_byref_person_1 {
    void *__isa;        //地址0---占用8字節(jié)
    __Block_byref_person_1 *__forwarding;   //地址8-16---占用8字節(jié)
    int __flags;        //地址16-20---占用4字節(jié)
    int __size;         //地址20-24---占用8字節(jié)
    void (*__Block_byref_id_object_copy)(void*, void*); //地址24-32---占用8字節(jié)
    void (*__Block_byref_id_object_dispose)(void*);     //地址32-40---占用8字節(jié)
    BFPerson *person;   //地址40-48---占用8字節(jié)
};

  • 因為捕獲的本身是一個對象類型险绘,所以該對象類型還需要進行內(nèi)存的干預踢京。

  • 這里有兩個熟悉的函數(shù),即用于管理對象 auto 變量時宦棺,我們見過瓣距,用于管理對象 auto 的內(nèi)存:

void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);

  • 那么這兩個函數(shù)對應的實現(xiàn),我們也找出來:

初始化__block對象

下面針對轉(zhuǎn)換來轉(zhuǎn)換去的細節(jié)做了刪減渺氧,方便閱讀:

struct __Block_byref_person_1 person = {
    0,  //isa
    &person,    //__forwarding
    33554432,   //__flags
    sizeof(__Block_byref_person_1),     //__size
    __Block_byref_id_object_copy_131,  //(*__Block_byref_id_object_copy)
    __Block_byref_id_object_dispose_131,// (*__Block_byref_id_object_dispose)
    (objc_msgSend)((objc_msgSend)(objc_getClass("BFPerson"), sel_registerName("alloc")), sel_registerName("init"))
};

//注意此處+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);
}

  • 我們注意觀察旨涝,在__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131 函數(shù)中,都會偏移 40 字節(jié)侣背,我們再看__block BFPerson 對象轉(zhuǎn)換后的__Block_byref_person_1 結構體發(fā)現(xiàn)白华,其 40 字節(jié)偏移處就是原本的 BFPerson *person 對象。

對象類型的內(nèi)存管理

BFPerson *person贩耐,在__block 修飾后弧腥,轉(zhuǎn)換為:__Block_byref_person_1 對象:

  • __Block_byref_person_1 對象同樣是在 Block對象從棧上拷貝到堆上,進行 retain潮太;
    • __Block_byref_person_1 進行 retain 同時管搪,會將 person 對象進行 retain
  • Block對象銷毀時虾攻,對__Block_byref_person_1 進行 release
    • __Block_byref_person_1 對象 release 時,會將 person 對象 release

與auto對象變量的區(qū)別

image

從棧到堆

Block 從棧復制到堆時更鲁,__block 變量產(chǎn)生的影響如下:

__block 變量的配置存儲域 Block 從棧復制到堆的影響
從棧復制到堆霎箍,并被 Block 持有
被 Block 持有

Block從棧拷貝到堆

image

當有多個 Block 對象澡为,持有同一個__block 變量漂坏。

  • 當其中任何 Block 對象復制到堆上,__block 變量就會復制到堆上媒至。
  • 后續(xù)顶别,其他 Block 對象復制到堆上,__block 對象引用計數(shù)會增加拒啰。
  • Block 復制到堆上的對象驯绎,持有__block 對象。

Block銷毀

image

總結

image

更多細節(jié)

__block捕獲變量存放在哪谋旦?

__block int age = 20;
__block BFPerson *person = [[BFPerson alloc] init];

void(^block)(void) = ^ {
    age = 30;
    person = [[BFPerson alloc] init];
    NSLog(@"malloc address: %p %p", &age, person);
    NSLog(@"malloc age is %d", age);
    NSLog(@"person is %@", person);
};
block();
NSLog(@"stack address: %p %p", &age, person);
NSLog(@"stack age is %d", age);

//輸出結果
Block-test[12866:2303749] malloc address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] malloc age is 30
Block-test[12866:2303749] person is <Person: 0x100612ff0>
Block-test[12866:2303749] stack address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] stack age is 30

可以看到剩失,不管是 age 還是 person,均在堆空間蛤织。

其實赴叹,本質(zhì)上鸿染,將 Block 從椫秆粒拷貝到堆,也會將__block 對象一并拷貝到堆涨椒,如下圖:

image

對象與__block變量的區(qū)別

__block BFPerson *blockPerson = [[BFPerson alloc] init];
BFPerson *objectPerson = [[BFPerson alloc] init];
void(^block)(void) = ^ {
    NSLog(@"person is %@ %@", blockPerson, objectPerson);
};

轉(zhuǎn)換后:

//Block對象
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    BFPerson *objectPerson;         //objectPerson對象摊鸡,捕獲
    __Block_byref_blockPerson_0 *blockPerson; // blockPerson封裝后的對象,內(nèi)部捕獲blockPerson
};

//__block blockPerson封裝后的對象
struct __Block_byref_blockPerson_0 {
    void *__isa;
    __Block_byref_blockPerson_0 *__forwarding;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    BFPerson *blockPerson;
};

//兩種對象不同的處理方式
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->blockPerson, (void*)src->blockPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->objectPerson, (void*)src->objectPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//__Block_byref_blockPerson_0內(nèi)部對__block blockPerson的處理
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

從上面可以得出

image
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚕冬,一起剝皮案震驚了整個濱河市免猾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囤热,老刑警劉巖猎提,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異旁蔼,居然都是意外死亡锨苏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門棺聊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞租,“玉大人,你說我怎么就攤上這事限佩】” “怎么了裸弦?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長作喘。 經(jīng)常有香客問我理疙,道長,這世上最難降的妖魔是什么泞坦? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任沪斟,我火速辦了婚禮,結果婚禮上暇矫,老公的妹妹穿的比我還像新娘主之。我一直安慰自己,他們只是感情好李根,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布槽奕。 她就那樣靜靜地躺著,像睡著了一般房轿。 火紅的嫁衣襯著肌膚如雪粤攒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天囱持,我揣著相機與錄音夯接,去河邊找鬼。 笑死纷妆,一個胖子當著我的面吹牛盔几,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掩幢,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逊拍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了际邻?” 一聲冷哼從身側(cè)響起芯丧,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎世曾,沒想到半個月后缨恒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡轮听,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年骗露,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕊程。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡椒袍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藻茂,到底是詐尸還是另有隱情驹暑,我是刑警寧澤玫恳,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站优俘,受9級特大地震影響惭婿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜叶雹,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一财饥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧折晦,春花似錦满着、人聲如沸谦炒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至豁鲤,卻和暖如春秽誊,著一層夾襖步出監(jiān)牢的瞬間鲸沮,已是汗流浹背琳骡。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留讼溺,地道東北人楣号。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像怒坯,于是被迫代替她去往敵國和親炫狱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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