iOS block捉邢,你要看的這都有

Blocks are a non-standard extension added by Apple Inc. to Clang's implementations of the C, C++, and Objective-C programming languages that uses a lambda expression-like syntax to create closures within these languages. Blocks are supported for programs developed for Mac OS X 10.6+ and iOS 4.0+,although third-party runtimes allow use on Mac OS X 10.5 and iOS 2.2+ and non-Apple systems.

參照wikipedia琼稻,block吮螺,其實(shí)就是C、C++、OC中閉包的表達(dá)鸠补。

一萝风、block結(jié)構(gòu)剖析

如果非要想一句話來(lái)形容block的話,我會(huì)將其比作能截獲變量的函數(shù)紫岩。本節(jié)中规惰,我會(huì)用一個(gè)特別簡(jiǎn)單的block來(lái)進(jìn)行結(jié)構(gòu)剖析。

分析:

// 首先貼一段最基本的block使用被因,寫(xiě)在main.m里面
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    // block實(shí)現(xiàn)
    Block block = ^{
        return 0;
    };
    // block調(diào)用
    block();
    return 0;
}

// 命令行在main.m文件夾執(zhí)行命令:clang -rewrite-objc main.m
// 得到main.cpp卿拴,我們看下main.cpp中與上述代碼直接相關(guān)的代碼(里面代碼很多)
// block內(nèi)部結(jié)構(gòu)體
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
typedef int (*Block)(void);
// block結(jié)構(gòu)體
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;
  }
};
// block方法實(shí)現(xiàn)
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
        return 0;
 }
// block內(nèi)部結(jié)構(gòu)體
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)};
// main函數(shù)
int main(int argc, char * argv[]) {
    // block實(shí)現(xiàn)
    Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    // block調(diào)用
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

那么,我們從最下面的main函數(shù)看起梨与,分為兩個(gè)步驟堕花,分別對(duì)應(yīng)上面的代碼:

  • block實(shí)現(xiàn):調(diào)用block結(jié)構(gòu)體的構(gòu)造函數(shù)_main_block_impl_0來(lái)實(shí)現(xiàn)block,可見(jiàn)粥鞋,傳入的兩個(gè)參數(shù)缘挽,分別是函數(shù)_main_block_func_0的指針和_main_block_desc_0結(jié)構(gòu)體。
  • block調(diào)用:將block作為參數(shù)傳入block中的FuncPtr呻粹,也即_main_block_func_0方法壕曼,進(jìn)行調(diào)用。

兩個(gè)步驟相比較而言等浊,第二步比較簡(jiǎn)單腮郊,也符合一開(kāi)始的描述,將第一步中的block作為參數(shù)傳入FucPtr筹燕,就能訪問(wèn)block實(shí)現(xiàn)位置的上下文轧飞。那么,第一步中撒踪,我們需要看的就是block的結(jié)構(gòu)體过咬。看下面的注釋即可制妄。

// block結(jié)構(gòu)體
struct __main_block_impl_0 {
  // impl結(jié)構(gòu)體掸绞,即block的主結(jié)構(gòu)體
  struct __block_impl impl;
  // Desc結(jié)構(gòu)體,即block的描述結(jié)構(gòu)體
  struct __main_block_desc_0* Desc;
  // block結(jié)構(gòu)體的構(gòu)造函數(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;
  }
};

// impl結(jié)構(gòu)體
struct __block_impl {
  void *isa;  // 存儲(chǔ)位置耕捞,_NSConcreteStackBlock衔掸、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
  int Flags;  // 按位承載 block 的附加信息
  int Reserved;  // 保留變量
  void *FuncPtr;  // 函數(shù)指針砸脊,指向 Block 要執(zhí)行的函數(shù)具篇,即__main_block_func_0
};

// Desc結(jié)構(gòu)體
static struct __main_block_desc_0 {
  size_t reserved;  // 結(jié)構(gòu)體信息保留字段
  size_t Block_size;  // 結(jié)構(gòu)體大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

小結(jié):

由上文可以看出,block使用比函數(shù)多來(lái)兩個(gè)步驟凌埂,即使用構(gòu)造函數(shù)實(shí)現(xiàn)和使用FuncPtr的時(shí)候需要傳入block結(jié)構(gòu)體驱显。可以得出結(jié)論,block比函數(shù)多了一個(gè)功能埃疫,就是可以在實(shí)現(xiàn)的地方訪問(wèn)上文伏恐。

疑問(wèn):

1、好像并沒(méi)有提到block如何截獲變量栓霜。
2翠桦、isa表示的三種存儲(chǔ)位置_NSConcreteStackBlock、_NSConcreteGlobalBlock胳蛮、_NSConcreteMallocBlock之間有什么關(guān)系销凑。

二、截獲變量

在第一部分仅炊,留下了兩個(gè)疑問(wèn)斗幼,在本節(jié),我們首先看下變量的截獲問(wèn)題抚垄,這里有兩個(gè)關(guān)鍵點(diǎn)蜕窿,即截獲變量的block的結(jié)構(gòu)體分析和block截獲變量的時(shí)機(jī)。

1呆馁、截獲變量的block結(jié)構(gòu)體實(shí)現(xiàn)

// block截獲變量樣例代碼
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    int i = 0;
    Block block = ^{
        return i;
    };
    block();
    return 0;
}

// 命令行在main.m文件夾執(zhí)行命令:clang -rewrite-objc main.m
// 得到main.cpp桐经,我們看下main.cpp中block的結(jié)構(gòu)體和main函數(shù)
// block結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// main函數(shù)
int main(int argc, char * argv[]) {
    int i = 0;
    Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

可見(jiàn),與無(wú)變量截獲的樣例浙滤,有兩個(gè)區(qū)別:

  • block結(jié)構(gòu)體中多了一個(gè)變量i阴挣,這也是為什么說(shuō)block會(huì)截獲變量?至于纺腊,i的值如何初始化繼續(xù)看下面屯吊。
  • block的構(gòu)造函數(shù)__main_block_impl_0中多了一個(gè)參數(shù)i和 : i(_i),參數(shù)i好理解摹菠,為了給i賦值,而 : i(_i)則是c++語(yǔ)法結(jié)構(gòu)體中const類型變量的初始化方式骗爆,即將參數(shù)_i賦值給block結(jié)構(gòu)體中的變量i次氨。

2、block截獲變量的時(shí)機(jī)

參見(jiàn)二.1中摘投,main函數(shù)實(shí)現(xiàn):((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i))煮寡。可以發(fā)現(xiàn)block在構(gòu)造的時(shí)刻犀呼,會(huì)將參數(shù)i傳入結(jié)構(gòu)體中進(jìn)行初始化幸撕,所以,block會(huì)在實(shí)現(xiàn)的地方截獲變量外臂,而截獲的變量的值也是實(shí)現(xiàn)時(shí)刻的變量值坐儿。

三、截獲變量的類型

引用官方文檔:

1、Global variables are accessible, including static variables that exist within the enclosing lexical scope.
2貌矿、Parameters passed to the block are accessible (just like parameters to a function).
3炭菌、Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.
4、Variables local to the enclosing lexical scope declared with the _block storage modifier are provided by reference and so are mutable.Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in The _block Storage Type.
5逛漫、Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function.Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const
or by-reference variables in blocks enclosed within the block.

大致可以看出block可以中的外部變量一共有五種黑低,依次是全局變量、全局靜態(tài)變量酌毡、局部靜態(tài)變量克握、_block修飾的變量和一個(gè)局部變量(截獲后為const類型)。這里枷踏,先把_block剔除菩暗,看一下另外四種:

// block截獲變量樣例代碼
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
int a = 0;
static int b = 0;
int main(int argc, char * argv[]) {
    static int c = 0;
    int i = 0;
    NSMutableArray *arr = [NSMutableArray array];
    Block block = ^{
        a = 1;
        b = 1;
        c = 1;
        [arr addObject:@"1"];
        return i;
    };
    block();
    return 0;
}

這里有個(gè)問(wèn)題,如果繼續(xù)使用clang -rewrite-objc main.m會(huì)報(bào)錯(cuò)呕寝,因?yàn)榘薕C庫(kù)UIKit勋眯,所以使用xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m,如果想深入了解請(qǐng)看clang -rewrite-objc的使用點(diǎn)滴下梢。

// block結(jié)構(gòu)體和構(gòu)造函數(shù)
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *c;
  NSMutableArray *arr;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, NSMutableArray *_arr, int _i, int flags=0) : c(_c), arr(_arr), i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// block實(shí)現(xiàn)的方法
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *c = __cself->c; // bound by copy
  NSMutableArray *arr = __cself->arr; // bound by copy
  int i = __cself->i; // bound by copy
  a = 1;
  b = 1;
  (*c) = 1;
  ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)arr, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_k__6b9p9yt96y9dq8ds8_kvf3kh0000gn_T_main_3c9752_mi_0);
  return i;
}
// mian函數(shù)
int main(int argc, char * argv[]) {
    static int c = 0;
    int i = 0;
    NSMutableArray *arr = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
    Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &c, arr, i, 570425344));
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

上述樣例中一共有五種外部變量客蹋,這里分為三類。
1孽江、全局變量a讶坯、和靜態(tài)全局變量b為第一類,block可以直接修改岗屏,沒(méi)什么要說(shuō)的辆琅。
2、局部靜態(tài)變量c这刷,從block構(gòu)造函數(shù)可以看出此處傳入的是c的指針婉烟,所以可以更改c。
3暇屋、局部基本變量i似袁,局部對(duì)象arr,可以發(fā)現(xiàn)i不能修改咐刨,而arr因?yàn)槭菍?duì)象昙衅,可以對(duì)對(duì)象進(jìn)行操作(但不能對(duì)給arr重新指向其他地址),這時(shí)需要注意的是定鸟,block內(nèi)部的i和arr均為const類型而涉,所以均不能對(duì)其進(jìn)行修改。
本節(jié)過(guò)后联予,大家對(duì)截獲變量大概只有兩個(gè)難點(diǎn)了啼县,即為_(kāi)block修飾的變量是什么意思和對(duì)上述第三種類型的變量材原,我們?nèi)绾卧赽lock中修改。聰明的童鞋應(yīng)該想到了谭羔,兩個(gè)難點(diǎn)其實(shí)是一個(gè)問(wèn)題:_block就是為了解決block中修改const變量服務(wù)的华糖。且看下部分。

四瘟裸、_block作用

三中留下了截獲變量的一個(gè)疑點(diǎn)客叉,即如何修改const類型的變量,其實(shí)话告,我們可以從局部靜態(tài)變量和局部對(duì)象[arr addObject:@"1"]中找到一些思路兼搏,即不直接操作變量,而是通過(guò)操縱其指針沙郭,雖然我們不能將指針指向新地址佛呻,但是,我們可以對(duì)指針空間進(jìn)行操作病线。

// 使用_block的樣例
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    __block int i = 2;
    __block NSMutableArray *arr = [NSMutableArray array];
    Block block = ^{
        i = 1;
        arr = [NSMutableArray array];
        return i;
    };
    block();
    return 0;
}

// 執(zhí)行xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m后代碼
// __block為變量i創(chuàng)建的結(jié)構(gòu)體吓著,其中成員i為i的值,forwarding為指向自己的指針
struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
// __block為變量arr創(chuàng)建的結(jié)構(gòu)體送挑,其中成員arr為arr的值绑莺,forwarding為指向自己的指針
struct __Block_byref_arr_1 {
  void *__isa;
  __Block_byref_arr_1 *__forwarding;
  int __flags;
  int __size;
  void (*__Block_byref_id_object_copy)(void*, void*);
  void (*__Block_byref_id_object_dispose)(void*);
  NSMutableArray *arr;
};
// block結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __Block_byref_arr_1 *arr; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,                __Block_byref_i_0 *_i, __Block_byref_arr_1 *_arr, int flags=0) : i(_i->__forwarding), arr(_arr->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// block函數(shù)實(shí)現(xiàn)
static int __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref
  __Block_byref_arr_1 *arr = __cself->arr; // bound by ref
  (i->__forwarding->i) = 1;
  (arr->__forwarding->arr) = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
  return (i->__forwarding->i); 
}
// main函數(shù)
int main(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 2};
    __attribute__((__blocks__(byref))) __Block_byref_arr_1 arr = {(void*)0,(__Block_byref_arr_1 *)&arr, 33554432, sizeof(__Block_byref_arr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
    Block block = ((int (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, (__Block_byref_arr_1 *)&arr, 570425344));
    ((int (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

從上述代碼,可以看出惕耕,無(wú)論是基本變量i還是對(duì)象arr都被_block變成了一個(gè)結(jié)構(gòu)體纺裁,而其值為結(jié)構(gòu)體里面的成員變量,那么我們也就能明白為什么能在block中對(duì)其進(jìn)行修改了司澎。當(dāng)然欺缘,這里有一個(gè)很大的疑問(wèn),即簡(jiǎn)單的結(jié)構(gòu)體就能完成在block里面修改外部變量的要求挤安,而這里為什么會(huì)多一個(gè)指向結(jié)構(gòu)體自己的_forwarding指針呢谚殊?且看下一部分。

// 有興趣可以看下下面這個(gè)樣例蛤铜,也能完成對(duì)i的修改络凿,那么問(wèn)題來(lái)了,__block的__forwarding指針到底有什么用處(i輸出為3)
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    int i = 1;
    int *a = &i;
    Block block = ^{
        (*a)++;
        return 0;
    };
    i ++;
    block();
    NSLog(@"%d", i);
}

五昂羡、block的存儲(chǔ)類型

在二、三摔踱、四節(jié)中基本解答了一中提出的截獲變量的問(wèn)題(留下了一個(gè)_forwarding指針問(wèn)題虐先,本節(jié)解答),那么從本節(jié)開(kāi)始將解答一中留下的另外一個(gè)問(wèn)題派敷,即isa表示的三種存儲(chǔ)位置_NSConcreteStackBlock蛹批、_NSConcreteGlobalBlock撰洗、_NSConcreteMallocBlock之間有什么關(guān)系。

1腐芍、_NSConcreteGlobalBlock

// a差导、全局聲明實(shí)現(xiàn)的block
// clang編譯后可發(fā)現(xiàn)isa指針為_(kāi)NSConcreteGlobalBlock
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
Block globalBlock = ^{
    return 0;
};
int main(int argc, char * argv[]) {
    globalBlock();
    return 0;
}

// b、局部block未截取任何變量
// clang編譯后可發(fā)現(xiàn)isa指針為_(kāi)NSConcreteStackBlock猪勇,這就看出了clang的不準(zhǔn)確的地方设褐,從此以后,我們通過(guò)NSLog打印泣刹。此處NSLog打印出的為_(kāi)NSConcreteGlobalBlock助析。
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
int main(int argc, char * argv[]) {
    Block globalBlock = ^ {
        return 0;
    };
    globalBlock();
    NSLog(@"%@", globalBlock);
    return 0;
}

2、_NSConcreteStackBlock

ARC環(huán)境下椅您,大部分情況下編譯器通常會(huì)將創(chuàng)建在棧上的 block 自動(dòng)拷貝到堆上外冀,只有當(dāng)block 作為方法或函數(shù)的參數(shù)傳遞時(shí),編譯器可能不會(huì)自動(dòng)調(diào)用 copy 方法掀泳。

當(dāng) block 作為參數(shù)被傳入方法名帶有 usingBlock的 Cocoa Framework 方法或 GCD 的 API 時(shí)雪隧。這些方法會(huì)在內(nèi)部對(duì)傳遞進(jìn)來(lái)的 block 調(diào)用 copy或 _Block_copy進(jìn)行拷貝。

// 下述是獅子書(shū)上非常經(jīng)典的樣例员舵,會(huì)在Block block = [arr objectAtIndex:1];處crash脑沿,因?yàn)間etBlockArray中的block存儲(chǔ)在棧上,已經(jīng)被回收固灵。
// 這里需要注意的是一定是在參數(shù)上定義的block捅伤,若在其他地方定義,傳入?yún)?shù)中巫玻,則不會(huì)丛忆。
// 這里用的NSLog打印的block顯示_NSConcreteStackBlock。
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
id getBlockArray()
{
    int val = 10;
    NSLog(@"%@", ^{NSLog(@"blklog:%d", val);});
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d", val);},
            ^{NSLog(@"blk1:%d", val);}, nil];
}
int main(int argc, char * argv[]) {
    id arr = getBlockArray();
    Block block = [arr objectAtIndex:1];
    block();
    return 0;
}
// 解決方式仍秤,不多解釋了
#import <UIKit/UIKit.h>
typedef int (^Block)(void);
id getBlockArray()
{
    int val = 10;
    NSLog(@"%@", ^{NSLog(@"blklog:%d", val);});
    return [[NSArray alloc] initWithObjects:
            [^{NSLog(@"blk0:%d", val) ;} copy],
            [^{NSLog(@"blk1:%d", val);} copy], nil];
}
int main(int argc, char * argv[]) {
    id arr = getBlockArray();
    Block block = [arr objectAtIndex:1];
    block();
    return 0;
}

3熄诡、_NSConcreteMallocBlock

這里裝個(gè)逼:除了上述幾類block,其余的block在ARC環(huán)境下都是_NSConcreteMallocBlock類型诗力。歡迎打臉凰浮,那我就能學(xué)到更多知識(shí)了。哈哈苇本,開(kāi)個(gè)玩笑袜茧。仔細(xì)看的人可能會(huì)發(fā)現(xiàn),從一到五的clang信息中瓣窄,樣例中的block基本都是_NSConcreteStackBlock笛厦,其實(shí)不然,應(yīng)該都是_NSConcreteMallocBlock俺夕,你可以通過(guò)NSLog方式驗(yàn)證裳凸。

三種存儲(chǔ)類型不要在clang代碼中查看贱鄙,而是通過(guò)NSLog打印。

4姨谷、_block逗宁、_forwarding與堆

已知arc環(huán)境下,棧上的block會(huì)自動(dòng)copy到堆上梦湘,而block截取的_block變量也會(huì)同時(shí)copy到堆上(這塊就不細(xì)說(shuō)了瞎颗,可以去看獅子書(shū))。這就保證了践叠,即使截獲的變量出了作用域言缤,block也能訪問(wèn)到它,而在四中最后提到的方法則無(wú)法保證禁灼。如下樣例管挟。

#import <UIKit/UIKit.h>
typedef int (^Block)(void);
// 聲明一個(gè)全局block
Block globalBlock;
void test() {
    int i = 1;
    int *a = &i;
    Block block = ^{
        (*a)++;
        NSLog(@"%d", *a);
        return 0;
    };
    i++;
    block();
    // 為全局block初始化
    globalBlock = block;
}
int main(int argc, char * argv[]) {
    test();
    globalBlock();
}
// 大家心目中的輸出應(yīng)該為
**2016-12-15 17:30:21.188 block[8683:10811473] 3**
**2016-12-15 17:30:21.189 block[8683:10811473] 4**
// 實(shí)際輸出為
**2016-12-15 17:30:21.188 block[8683:10811473] 3**
**2016-12-15 17:30:21.189 block[8683:10811473] 24705**

為什么會(huì)出現(xiàn),這種結(jié)果呢弄捕,因?yàn)?a指向局部變量i僻孝,而i在出了test方法作用域后回收內(nèi)存,所以出現(xiàn)一個(gè)不知道哪來(lái)的數(shù)守谓。那么__block能處理這種情況嗎穿铆?

#import <UIKit/UIKit.h>
typedef int (^Block)(void);
Block globalBlock;
void test() {
    __block int i = 1;
    Block block = ^{
        i ++;
        NSLog(@"%d", i);
        return 0;
    };
    i ++;
    block();
    globalBlock = block;
}
int main(int argc, char * argv[]) {
    test();
    globalBlock();
}
// 輸出
**2016-12-15 17:34:07.092 block[9046:10815940] 3**
**2016-12-15 17:34:07.094 block[9046:10815940] 4**

完美。這里看一下四中_block結(jié)構(gòu)體的構(gòu)造函數(shù)

// 由下可以看出斋荞,結(jié)構(gòu)體內(nèi)的i和arr都是由結(jié)構(gòu)體內(nèi)的_forwarding指針初始化的荞雏,這保證了block外部棧變量的獨(dú)立性,也保證了block截獲變量可以copy到堆上平酿,因?yàn)槭莀block類型凤优。
// 由四中的clang代碼還可以看出,無(wú)論block內(nèi)外蜈彼,訪問(wèn)i或者arr的方式均是i->__forwarding->i或arr->__forwarding->arr筑辨。這保證了block內(nèi)外訪問(wèn)的都是同一個(gè)內(nèi)存(這里指的堆上的內(nèi)存)。
__Block_byref_i_0 *_i, __Block_byref_arr_1 *_arr, int flags=0) : i(_i->__forwarding), arr(_arr->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}

六幸逆、循環(huán)引用

文章到這也就收尾了棍辕,大家應(yīng)該也差不多知道了block會(huì)截獲變量,即會(huì)持有變量还绘,所以可能引起循環(huán)引用楚昭,關(guān)于循環(huán)引用我曾經(jīng)寫(xiě)過(guò)一篇文章,講的比較全面拍顷,參見(jiàn)循環(huán)引用抚太,看我就對(duì)了

七菇怀、參考文獻(xiàn)

1凭舶、獅子書(shū):Objective-C高級(jí)編程 iOS與OS X多線程與內(nèi)存管理
2、https://www.zybuluo.com/MicroCai/note/51116
3爱沟、http://blog.tingyun.com/web/article/detail/845
4帅霜、https://en.wikipedia.org/wiki/Blocks_(C_language_extension)

文中部分__block均寫(xiě)成了_block,不要介意呼伸。如對(duì)您有所幫助身冀,喜歡一下,關(guān)注一下括享,謝謝~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搂根,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铃辖,更是在濱河造成了極大的恐慌剩愧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娇斩,死亡現(xiàn)場(chǎng)離奇詭異仁卷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)犬第,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)锦积,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人歉嗓,你說(shuō)我怎么就攤上這事丰介。” “怎么了鉴分?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵哮幢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冠场,道長(zhǎng)家浇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任碴裙,我火速辦了婚禮钢悲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舔株。我一直安慰自己莺琳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布载慈。 她就那樣靜靜地躺著惭等,像睡著了一般。 火紅的嫁衣襯著肌膚如雪办铡。 梳的紋絲不亂的頭發(fā)上辞做,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天琳要,我揣著相機(jī)與錄音,去河邊找鬼秤茅。 笑死稚补,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的框喳。 我是一名探鬼主播课幕,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼五垮!你這毒婦竟也來(lái)了乍惊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤放仗,失蹤者是張志新(化名)和其女友劉穎润绎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匙监,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡橱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亭姥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稼钩。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖达罗,靈堂內(nèi)的尸體忽然破棺而出坝撑,到底是詐尸還是另有隱情,我是刑警寧澤粮揉,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布巡李,位于F島的核電站,受9級(jí)特大地震影響扶认,放射性物質(zhì)發(fā)生泄漏侨拦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一辐宾、第九天 我趴在偏房一處隱蔽的房頂上張望狱从。 院中可真熱鬧,春花似錦叠纹、人聲如沸季研。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)与涡。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間驼卖,已是汗流浹背氨肌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酌畜,地道東北人儒飒。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像檩奠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子附帽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

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