|
所有文章均為作者原創(chuàng),轉(zhuǎn)載請注明出處
最近看了一篇很有趣文章,關(guān)于block的quiz,5道題答錯了兩道。之前理解block可以在heap和stack上alloc,看了這篇文章才發(fā)現(xiàn)原來還有g(shù)lobal的block。想想也是,這種block不引用任何variable,在編譯的時候就可以確定下來了。
最開始用block的時候,感到很疑惑,原因是這種用法從編程思想上來講屬于functional programming。不符合面向?qū)ο蟮脑O計原則,但是萬物無絕對,就像在面向?qū)ο蟮氖澜缰型瑯佑袉为毚嬖诘暮瘮?shù)一樣,現(xiàn)代編程語言已經(jīng)不拘泥于某種特定的形式。而熟悉Objective-C的人應該知道,這門語言是從Lisp演進過來,而Lisp是最早的函數(shù)型編程語言,這篇文章寫的很不錯:《Functional Programming Is Hard,
That’s Why It’s Good》。關(guān)于Lisp,一直沒有精力來仔細研究一下,略感遺憾。后面的smallTalk保留了Lisp的這一特性,進而帶到了Objective-C中,所以在OC中出現(xiàn)block也并不奇怪。
matt Galloway關(guān)于block做了很詳細的分析,并給出了LLVM中關(guān)于Block的定義。但是紙上得來終覺淺,為了理解的更透徹,我們還是親自來debug一下,我們就以ExampleA為例:
Example A
為了分析方便,我們把Example A稍微改寫一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #include <stdio.h>
void exampleA() {
char a = 'A';
void(^block)() =
^{
printf("%c\n", a);
};
block();
}
int main(int argc, char *argv[]) {
exampleA();
return 0;
}
|
我們可以通過clang來rewrite這段代碼:clang -rewrite-objc test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| #line 4 "test.c"
struct __exampleA_block_impl_0 {
struct __block_impl impl;
struct __exampleA_block_desc_0* Desc;
char a;
__exampleA_block_impl_0(void *fp, struct __exampleA_block_desc_0 *desc, char _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
#line 8 "test.c"
static void __exampleA_block_func_0(struct __exampleA_block_impl_0 *__cself) {
char a = __cself->a; // bound by copy
printf("%c\n", a);
}
static struct __exampleA_block_desc_0 {
size_t reserved;
size_t Block_size;
} __exampleA_block_desc_0_DATA = { 0, sizeof(struct __exampleA_block_impl_0)};
#line 4 "test.c"
void exampleA() {
char a = 'A';
void(*block)() =
(void (*)())&__exampleA_block_impl_0((void *)__exampleA_block_func_0, &__exampleA_block_desc_0_DATA, a);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
#line 15 "test.c"
int main(int argc, char *argv[]) {
exampleA();
return 0;
}
}
|
需要指出的一點是,這個rewrite-objc的命令很詭異,我們的預期是將一個.c文件僅在語法層面將block展開,生成另一個.c文件。但是實際上這個命令生成的是一個.cpp的C++文件,這意味著它在block的結(jié)構(gòu)體中插入了構(gòu)造函數(shù):
1
2
3
4
5
6
| __exampleA_block_impl_0(void *fp, struct __exampleA_block_desc_0 *desc, char _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
|
不知道clang為什么要把它rewrite成C++的。但這并不影響我們閱讀代碼:
-
main函數(shù)執(zhí)行exampleA函數(shù)
-
在exampleA函數(shù)中,首先通過構(gòu)造函數(shù)創(chuàng)建一個block:由于這個block是在exampleA的stack中,所以block的isa類型為_NSConcreteStackBlock,那么當exampleA執(zhí)行完畢后,block就會被銷毀掉;block有自己的內(nèi)存空間來保存?zhèn)鬟M來的參數(shù)a;block的回調(diào)函數(shù)指向了__exampleA_block_func_0
-
執(zhí)行block的回調(diào)函數(shù):在__exampleA_block_func_0中,輸出的a實際上是block結(jié)構(gòu)體中的a,不是block外部的a,因此,如果在__exampleA_block_func_0內(nèi)部改變a的值,改變不是外部的a值。
__block
如果我們想改變a的值,通常要把a聲明成__block:
1
2
3
4
5
6
7
8
9
10
11
12
| void exampleA() {
__block char a = 'A';
void(^block)() =
^{
a = 'B';
printf("%c\n", a);
};
block();
}
|
這樣我們就能改變a的值了
why?
我們再次使用:clang -rewrite-objc test.c,看看有什么不同的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
char a;
};
struct __exampleA_block_impl_0 {
struct __block_impl impl;
struct __exampleA_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__exampleA_block_impl_0(void *fp, struct __exampleA_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;
}
};
#line 9 "test.c"
static void __exampleA_block_func_0(struct __exampleA_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 'B';
printf("%c\n", (a->__forwarding->a));
}
static void __exampleA_block_copy_0(struct __exampleA_block_impl_0*dst, struct __exampleA_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __exampleA_block_dispose_0(struct __exampleA_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __exampleA_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __exampleA_block_impl_0*, struct __exampleA_block_impl_0*);
void (*dispose)(struct __exampleA_block_impl_0*);
} __exampleA_block_desc_0_DATA = { 0, sizeof(struct __exampleA_block_impl_0), __exampleA_block_copy_0, __exampleA_block_dispose_0};
#line 4 "test.c"
void exampleA() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 'A'};
void(*block)() =
(void (*)())&__exampleA_block_impl_0((void *)__exampleA_block_func_0, &__exampleA_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
}
|
Closure
實際應用中,我們并不需要了解block是如何實現(xiàn)的,但是理解block后面的思想?yún)s很關(guān)鍵。前面已經(jīng)提到過Functional Programming的概念,F(xiàn)unctional Programming中一個很重要的概念是Closure,翻譯過來叫“閉包”。它有幾個最基本的feature:
(1)First-Class Value:函數(shù)也是一種變量,也可以像變量一樣被賦值,被當做參數(shù)傳遞和返回。
(2)High Order Function:高階函數(shù),即一個函數(shù)的參數(shù)也可以是另一個函數(shù),并且這個函數(shù)可以是匿名函數(shù)。
(3)Lexical Scoping:靜態(tài)作用域,也叫詞法作用域,解釋起來很麻煩,感興趣的可以看維基百科。
這三條是不是很熟悉,沒錯,Objective-C中的block這幾條都支持:
1
2
| void(^func)(int x, int y) = ^(int x,int y){ NSLog(@"%d,%d",x,y);};
func(100,100);
|
這說明block可以和普通變量一樣,進行定義和賦值
1
2
3
4
5
| __block int i = 0;
int a = newCounter( ^
i = i+1;
return i;
});
|
這說明,block可以作為匿名函數(shù)直接作為參數(shù)傳遞,同時它還可以訪問i,說明block也支持lexical scoping。
其實所謂block僅僅是語法層面的東西,任何支持closure的語言都有它獨特的一套語法和形式,尤其對于腳本語言,沒有強類型的限制,使用起來更靈活,更好玩,例如,Lua的table.sort方法中第二個參數(shù):
1
2
3
4
5
6
7
8
| network = {
{name="jason",ip = "192.168.1.11"},
{name="kate",ip = "192.168.1.12"},
{name="candy",ip = "192.168.1.13"},
{name="carl",ip = "192.168.1.14"},
}
table.sort(network,function(a,b) return(a.name > b.name) end)
print(network[1].name,network[1].ip) -->kate,192.168.1.12
|
因此,總結(jié)來說,對于block,僅僅是Objective-C實現(xiàn)Closure的一種途徑,為的是在某些場合用起來更方便,使代碼更簡潔,更緊湊而已。
|