Block對象是C語法的, 且是一個運行時特性, 它們相似於標準C函數, 可是除了可執行代碼外它們還可能包含到自動(棧)或託管(堆)內存的變量綁定, 所以, 一個block能夠維護一組狀態(數據), 當執行Block時能夠用來影響執行行爲c++
可使用block構成能夠傳遞給API的函數表達式(block能夠作接口參數), 用於存儲數據, 能夠在多線程中使用, block作爲回調特別有用, 由於block既包含要在回調上執行的代碼,又包含執行期間所需的數據編程
OS X v10.6 Xcode開發人員工具附帶提供了GCC和Clang中的block, 能夠在OS X v10.6和更高版本以及iOS 4.0和更高版本中使用block, block運行時是開源的能夠在 LLVM’s compiler-rt subproject repository中找到. Block也做爲 N1370: Apple’s Extensions to C已提交給C標準工做組, 因爲Objective-C和C++都從C派生而來, 所以block被設計爲可用於全部三種語言(以及Objective-C++)數組
使用^
運算符聲明一個block變量並指示block文字的開頭, block的主體自己包含在{}
中安全
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
複製代碼
注意block可以使用其定義範圍內的變量bash
若是將block聲明爲變量則能夠像使用函數同樣使用它數據結構
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
複製代碼
在不少狀況下, 不須要聲明block變量, 取而代之的是隻需在須要將其做爲參數的位置編寫內聯代碼block, 示例使用qsort_b
函數, qsort_b
與標準qsort_r
函數類似可是將一個block做爲其一個參數多線程
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
複製代碼
Cocoa
框架中的幾種方法都以一個block做爲參數, 一般作對對象集合執行操做,以及在操做完成後用做回調, 如下示例顯示如何將block與NSArray
方法sortedArrayUsingComparator:
一塊兒使用閉包
NSArray *stringsArray = @[ @"string 1",
@"String 21",
@"string 12",
@"String 11",
@"String 02" ];
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
/* Output: finderSortArray: ( "string 1", "String 02", "String 11", "string 12", "String 21" ) */
複製代碼
block的強大功能是它能夠在相同的詞法範圍內修改變量, 使用__block
存儲類型修飾符表示block能夠修改變量, 沿用以前的示例, 可使用一個block變量來計算比較相等的字符串數, 在這種狀況下, 將直接使用該block, 並將currentLocale
用做該block內的只讀變量app
NSArray *stringsArray = @[ @"string 1",
@"String 21", // <-
@"string 12",
@"String 11",
@"Strîng 21", // <-
@"Striñg 21", // <-
@"String 02" ];
NSLocale *currentLocale = [NSLocale currentLocale];
__block NSUInteger orderedSameCount = 0;
NSArray *diacriticInsensitiveSortArray = [stringsArray sortedArrayUsingComparator:^(id string1, id string2) {
NSRange string1Range = NSMakeRange(0, [string1 length]);
NSComparisonResult comparisonResult = [string1 compare:string2 options:NSDiacriticInsensitiveSearch range:string1Range locale:currentLocale];
if (comparisonResult == NSOrderedSame) {
orderedSameCount++;
}
return comparisonResult;
}];
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);
NSLog(@"orderedSameCount: %d", orderedSameCount);
/* Output: diacriticInsensitiveSortArray: ( "String 02", "string 1", "String 11", "string 12", "String 21", "Str\U00eeng 21", "Stri\U00f1g 21" ) orderedSameCount: 2 */
複製代碼
block對象提供了一種方法能夠建立臨時函數體, 以C以及基於C的語言(如Objective-C和C ++)的表達式進行建立. 在其餘語言和環境中, block對象有時也稱爲"閉包(closure)"
block是匿名內聯代碼的集合
lexical scope
)中捕獲狀態lexical scope
)的狀態能夠複製一個block,並且還能夠將其傳遞給其餘線程以推遲執行(或在其本身的線程中傳遞給運行循環), 編譯器和運行時能夠確保block所引用的全部變量的生命週期, 儘管block可用於純C和C++可是block也始終是Objective-C對象
block一般表明小的獨立代碼段. 所以它們特別有用, 能夠封裝能夠同時執行的工做單元, 也能夠封裝集合中的項, 也能夠封裝其餘操做完成後的回調
block能夠做爲傳統的回調方案的替代有兩個重要緣由
block變量保存了對block對象的引用, 使用相似於聲明函數指針的語法來聲明它們, 固然不一樣處是使用^
而不是*
, block類型能夠與C類型系統的其他部分徹底相互操做
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
複製代碼
block還支持可變參數(...), 不帶參數的block必須在參數列表中指定void
block被設計爲徹底類型安全的, 經過爲編譯器提供一整套元數據以驗證block的使用, 整套數據包含傳遞給block的參數以及返回值的分配. 能夠將block引用強制轉換爲任意類型的指針, 反之亦然
可是不能經過指針引用運算符*
取消對block的引用,由於沒法在編譯時計算block的大小
能夠爲block建立類型, 這樣就能夠在多個地方使用
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
複製代碼
可使用^
運算符來指示block文字表達式的開始, 它後面能夠跟( )
中包含的參數列表, block的主體包含在{}
中
示例
float (^oneFrom)(float);
oneFrom = ^(float aFloat) {
float result = aFloat - 1.0;
return result;
};
複製代碼
若是未明確聲明block表達式的返回值, 它能夠從block的內容中自動推斷出來, 若是推斷出返回類型與參數列表都爲空void
, 那麼也能夠省略void
參數列表, 若是存在多個return語句則它們必須徹底匹配(必要時使用強制類型轉換)
在文件級別能夠將block用做全局變量
#import <stdio.h>
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
複製代碼
在block對象的代碼體內,能夠用五種不一樣方式來處理變量
能夠引用三種標準類型的變量,就像在函數中同樣
block還支持其餘兩種類型的變量
__block
變量, 它們在block(和封閉範圍)內是可變的, 若是將任何引用block複製到堆中, 則將保留它們const
常量最後,在方法實現中,block能夠引用Objective-C實例變量
在block中使用的變量的規則
全局變量是可訪問的, 包括包含在詞彙範圍內的靜態變量
傳遞給block的參數是可訪問的(就像函數的參數同樣)
處於封閉範圍內的非靜態局部變量被block捕獲爲const
不可變的常量, 不可變
用__block
存儲修飾符聲明的包圍詞法做用域的局部變量, 是可變的, 能夠被修改
在block的詞法範圍內聲明的局部變量, 行爲等同函數中的局部變量
使用局部非靜態變量
int x = 123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 123 456
複製代碼
嘗試在block內爲x分配新值將致使錯誤
int x = 123;
void (^printXAndY)(int) = ^(int y) {
x = x + y; // error
printf("%d %d\n", x, y);
};
複製代碼
經過應用__block
存儲類型修飾符, 能夠指定導入的變量是可變的(便可讀寫), __block
對於局變量相似於register
, auto
, static
這些存儲類型, 可是和它們是互斥的
__block
變量存儲在變量的詞法範圍與在該變量的詞法範圍內聲明或建立的全部block和block副本之間共享的存儲中, 所以, 在棧內聲明的block的任何拷貝都會在在棧的尾都存活(例如,經過排隊到某個地方以供之後執行), 則該存儲將避免棧的銷燬(Thus, the storage will survive the destruction of the stack frame if any copies of the blocks declared within the frame survive beyond the end of the frame (for example, by being enqueued somewhere for later execution).
這句理解有點難搞), 給定詞法範圍內的多個block能夠同時使用共享變量
做爲一個最優項, block通常都是用在棧中, 若是block使用Block_copy
拷貝(或者Objective-C 中發送了copy
消息), 拷貝的內容就在堆上了, 因此, __block
修飾的變量的地址能夠隨時間變化
__block
變量還有兩個限制
__block
示例
__block int x = 123; // x lives in block storage
void (^printXAndY)(int) = ^(int y) {
x = x + y;
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 579 456
// x is now 579
複製代碼
如下示例顯示了具備多種類型變量的block
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char localCharacter;
void (^aBlock)(void) = ^(void) {
++CounterGlobal;
++CounterStatic;
CounterGlobal = localCounter; // localCounter fixed at block creation
localCharacter = 'a'; // sets localCharacter in enclosing scope
};
++localCounter; // unseen by the block
localCharacter = 'b';
aBlock(); // execute the block
// localCharacter now 'a'
}
複製代碼
block做爲變量能夠提供對Objective-C和C++對象以及其餘塊的支持
Objective-C Objects
複製一個block時, 它將建立對該塊內使用的對象變量的強引用, 若是在實現方法內使用block
self
進行強引用如下示例說明了兩種不一樣的狀況
dispatch_async(queue, ^{
// instanceVariable is used by reference, a strong reference is made to self
doSomethingWithObject(instanceVariable);
});
id localVariable = instanceVariable;
dispatch_async(queue, ^{
/* localVariable is used by value, a strong reference is made to localVariable (and not to self). */
doSomethingWithObject(localVariable);
});
複製代碼
C++ Objects
一般能夠在一個block中使用C++對象, 在成員函數中, 對成員變量和函數的引用是經過隱式導入的this
指針進行的, 所以看起來是可變的, 若是拷貝block則有兩個注意事項
__block
存儲類型的類來處理基於棧的C++對象, 一般複製構造函數將會被使用const
複製構造函數, 而後使用該構造函數複製C++對象Blocks
當拷貝一個block
, 若有必要將複製該block中對其餘block的任何引用, 若是有一個block變量將被複制, 並且這個block還引用了其餘block, 那麼引用的那個block也會被複制
若是將塊聲明爲變量則能夠像使用函數同樣使用它, 見示例
int (^oneFrom)(int) = ^(int anInt) {
return anInt - 1;
};
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
float (^distanceTraveled)(float, float, float) =
^(float startingSpeed, float acceleration, float time) {
float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
return distance;
};
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9
複製代碼
一般將塊做爲參數傳遞給函數或方法, 在這種狀況下, 一般會建立一個內聯block
在不少狀況下, 不須要聲明block變量, 取而代之的是隻需在須要將其做爲參數的位置編寫內聯代碼block, 示例使用qsort_b
函數, qsort_b
與標準qsort_r
函數類似可是將一個block做爲其一個參數
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
複製代碼
下面的例子dispatch_apply
函數所使用的的block
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
#include <dispatch/dispatch.h>
size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
});
複製代碼
Cocoa
提供了許多使用block的方法, 與其餘任何參數同樣可將block做爲方法參數傳遞
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
test = ^(id obj, NSUInteger idx, BOOL *stop) {
if (idx < 5) {
if ([filterSet containsObject: obj]) {
return YES;
}
}
return NO;
};
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
NSLog(@"indexes: %@", indexes);
/* Output: indexes: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)] */
//============================================================================
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, found == YES
複製代碼
一般不須要複製(或保留)block, 僅當但願該block在聲明瞭該塊的做用域銷燬以後被使用時, 才須要進行復制, 複製的block會在堆上產生
可使用C函數複製和釋放block
Block_copy();
Block_release();
複製代碼
爲避免內存泄漏須始終將Block_copy()
與Block_release()
保持平衡
A block literal (that is, ^{ ... }) is the address of a stack-local data structure that represents the block. The scope of the stack-local data structure is therefore the enclosing compound statement, so you should avoid the patterns shown in the following examples:
block文字(即^ {...}
)是表示該block的堆棧本地數據結構的地址, 所以棧局部數據結構的範圍是封閉的複合聲明, 所以應該避免如下示例中顯示的模式(翻的不通順, 請看原句, 結合代碼理解)
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 block references
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop.
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause.
}
// ...
}
複製代碼
能夠設置斷點並單步執行, 並使用invoke-block
從GDB會話中調用一個block
$ invoke-block myBlock 10 20
複製代碼
若是要傳遞C字符串則必須引用它
$ invoke-block doSomethingWithString "\"this string\""
複製代碼
每一個Objective C對象都佔據着某個內存空間, 由於實例變量的個數及對象包含的關聯數據互不相同, 因此每一個對象所佔的內存區域也有大有小. block自己也是對象, 在存放快對象的內存區域中, 首個變量是指向Class
對象的指針, 該指針叫作isa
, 其他內存裏含有block對象正常運轉所需的各類信息, 上圖描述了block對象的內存佈局
invoke
變量, 這是個函數指針, 指向block的實現代碼, 函數原型至少須要接收一個void *
型的參數, 這個參數表明block自己descriptor
變量是指向結構體的指針, 每一個block裏都包含此結構體, 其中聲明瞭block對象的整體大小, 還聲明瞭copy
和dispose
這兩個輔助函數所對應的函數指針, 輔助函數在拷貝及丟棄block對象時運行, 其中會執行一些操做, 好比: copy
要保留捕獲的對象, 而dispose
則將之釋放block還會把它所捕獲的全部變量都拷貝一份, 這些拷貝會放在descriptor
變量後面, 捕獲了多少變量, 就要佔多少內存空間, 請注意, 拷貝的並非對象自己, 而是指向這些對象的指針變量, 在執行block對象的時候, 就會從內存中把這些捕獲到的變量所有讀取出來
棧block
堆block
copy
操做, 就能夠把block從棧上覆制到堆上, 拷貝的block能夠在定義它的那個範圍以外使用copy
操做都不會真的執行復制, 只是遞增block對象的引用計數release
方法, 引用計數爲0
時, 內存空間被系統回收全局block
copy
操做是個空操做, 由於全局block不會被系統回收, 至關於單例全局block示例
void (^GlobalBlock)(void) {
NSLog(@"This is a global block");
}
複製代碼
其餘程序語言中Block的名稱
程序語言 | Block名稱 |
---|---|
C + Blocks | Block |
Smalltalk | Block |
Ruby | Block |
LISP | Lambda |
Python | Lambda |
C++11 | Lambda |
Javascript | Anonymous function |
截獲自動變量(書中寫的是自動變量--就是局部變量)
截獲自動變量值的實例以下
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
// 打印 val = 10
return 0;
}
複製代碼
在上面的源碼中, Block語法的表達式使用的是它以前聲明的自動變量fmt和val
瞬間值
__block修飾符
實際上, 自動變量值截獲的只能保存執行Block語法瞬間的值, 保存後就不能改寫該值, 若是要在block內部修改捕獲的變量的值, 須要經過添加__block
修飾符
Block的實現
查看block的C++源碼實現, 可使用clang
編譯器指令
clang -rewrite-objc 源代碼文件
複製代碼
int global_val = 1;
static int static_global_val = 2;
int main() {
int val = 10;
static int static_val = 3;
__block int block_val = 4;
__block NSObject *instance_val = [[NSObject alloc] init];
void (^blk)(void) = ^{
global_val *= 1; // 全局變量
static_global_val *= 2; // 全局靜態變量
static_val *= 3; // 局部靜態變量
block_val = 1; // __block修飾變量
instance_val = [[NSObject alloc] init]; // 對象
printf("val: %d, block_val: %d\n", val, block_val);
};
val = 2;
block_val = 3;
blk();
return 0;
}
複製代碼
C++源碼
int main() {
int val = 10;
static int static_val = 3;
__attribute__((__blocks__(byref))) __Block_byref_block_val_0
block_val = {(void*)0,
(__Block_byref_block_val_0 *)&block_val,
0,
sizeof(__Block_byref_block_val_0),
4};
__attribute__((__blocks__(byref))) __Block_byref_instance_val_1
instance_val = {(void*)0,
(__Block_byref_instance_val_1 *)&instance_val,
33554432,
sizeof(__Block_byref_instance_val_1),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void*)__main_block_func_0,
&__main_block_desc_0_DATA,
&static_val,
val,
(__Block_byref_block_val_0*)&block_val,
(__Block_byref_instance_val_1 *)&instance_val,
570425344));
val = 2;
(block_val.__forwarding->block_val) = 3;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製代碼
__main_block_impl_0
結構
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 局部靜態變量 指針形式引用
int *static_val;
// 通常局部變量
int val;
// __block的變量
__Block_byref_block_val_0 *block_val; // by ref
// __block的對象
__Block_byref_instance_val_1 *instance_val; // by ref
// 初始化構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int _val, __Block_byref_block_val_0 *_block_val, __Block_byref_instance_val_1 *_instance_val, int flags=0) : static_val(_static_val), val(_val), block_val(_block_val->__forwarding), instance_val(_instance_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock; // isa指向所屬的類型, 此處代表block是對象
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__main_block_func_0函數
// block執行時候調用的函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_block_val_0 *block_val = __cself->block_val; // bound by ref
__Block_byref_instance_val_1 *instance_val = __cself->instance_val; // bound by ref
int *static_val = __cself->static_val; // bound by copy
int val = __cself->val; // bound by copy
// 全局變量
global_val *= 1;
// 全局靜態變量
static_global_val *= 2;
// 局部靜態變量
(*static_val) *= 3;
// __block變量
(block_val->__forwarding->block_val) = 1;
// __block對象
(instance_val->__forwarding->instance_val) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
printf("val: %d, block_val: %d\n", val, (block_val->__forwarding->block_val));
}
複製代碼
__main_block_desc_0_DATA
結構體
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
的變量都變成告終構體
__Block_byref_block_val_0
結構
// block_val生成的結構體
struct __Block_byref_block_val_0 {
void *__isa; // 說明也變成了對象
__Block_byref_block_val_0 *__forwarding;
int __flags;
int __size;
int block_val;
};
複製代碼
__Block_byref_instance_val_1
結構
// __block修飾的instance_val
struct __Block_byref_instance_val_1 {
void *__isa;
__Block_byref_instance_val_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *instance_val;
};
複製代碼
__block變量__Block_byref_block_val_0,__Block_byref_instance_val_1
結構體並不在Block的__main_block_impl_0
結構體中, 這樣作是爲了能夠在多個Block中使用__block
變量
截獲變量類型
局部變量
靜態局部變量
全局變量
靜態全局變量
靜態局部變量,全局變量,靜態全局變量不須要使用__block
修飾符
__block
修飾的基本數據類型的變量都會變成對象(見__Block_byref_block_val_0
,__Block_byref_instance_val_1
)
何時棧上的Block會複製到堆上
copy
方法時__strong
修飾符id
類型的類或Block類型的成員變量在多個Block中使用__block
變量時, 由於最早會將全部的Block配置在棧上,因此__block
變量也會配置在棧上, 在任何一個Block從棧複製到堆時, __block
變量也會一併從棧複製到堆並被該Block所持有, 當剩下的Block從棧複製到堆時, 被複制的Block持有__block
變量, 並增長__block
變量的引用計數
__forwarding
指針
__block
變量結構體的__forwarding
指針指向本身__block
變量結構體的__forwarding
指針指向堆Block__block
變量的結構體,堆上的block的__block
變量結構體的__forwarding
指針指向本身
__block
變量, 仍是__block
變量配置在棧上或堆上, 均可以順利地訪問到同一個__block
變量理解若有錯誤 望指正 轉載請說明出處