前情提要
1.閉包、Block是一個帶有自動變量值(可以截獲自動變量值)的匿名函數术吝。截獲的含義是保存該自動變量的瞬間值转捕。
2.OC中如果要改變Block截獲的外部自動變量的值园细,需要在該變量前加上__block
修飾符。Swift不用昔馋,系統(tǒng)會自動處理這些問題芜繁。延伸到對象,對于Swift來說系統(tǒng)也會處理但OC不同绒极。OC下,當從外部截獲一個對象(NSMutableArray* )mArray
時,在Block內調用NSMutableArray
的方法(addObject:)
沒問題蔬捷,因為Block捕獲的是NSMutableArray
類對象用的結構體實例指針垄提。但若對其進行操作,比如賦值周拐,就會產生編譯錯誤铡俐,要在變量前加上__block
。Swift下直接用就行了妥粟。
3.從C代碼的角度分析closure/block
审丘。OC可以直接用clang -rewrite-objc 原文件名
來分析,Swift下的clang ..
我沒有找到對應的命令勾给,所以只看OC的滩报。在分析之前,先介紹兩個小概念播急。
--1.OC&Swift中的self
OC Code:
- (void) method:(int)arg {
NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = [[MyObject alloc] init];
[obj method: 10];
轉換成C Code:
void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg) {
NSLog(@"%p %d\n", self, arg);
}
MyObject *obj = objc_msgSend(objc_getClass("MyObject"), sel_registerName("alloc"));
obj = objc_msgSend(obj, sel_registerName("init"));
objc_msgSend(obj, sel_registerName("method:"), 10);
最后一條執(zhí)行語句脓钾,也就是[obj method: 10]
轉換成objc_msgSend(obj, sel_registerName("method:"), 10)
。objc_msgSend(obj, sel_registerName("method:"), 10)
函數根據指定的對象和函數名桩警,從對象持有類(MyObject)的結構體中檢索[XX method:10]
對應的函數-->void _I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
的指針并調用可训。此時,objc_msgSend函數的第一個參數obj
作為_I_MyObject_method_(struct MyObject *self, SEL _cmd, int arg)
的第一個參數self
進行傳遞。即self
就是Myobject
類的對象-obj
自己捶枢。
--2.OC中的id
*id為objc_object結構體的結構體指針握截。
id在C語言中的聲明:
typedef struct objc_object {
Class isa;
} *id;
*Class為objc_class結構體的結構體指針。
Class在C語言中的聲明:
typedef struct objc_class {
Class isa;
} *Class;
這與objc_object結構體相同烂叔。然而谨胞,objc_object結構體和objc_class結構體歸根結底是在各個對象(id)和類(Class)的實現(xiàn)中使用的最基本的結構體。
例:
類长已,MyObject
@interface MyObject : NSObject {
int val0;
int val1;
}
@end
使用MyObject
創(chuàng)建的對象newObject = [MyObject new]
,就是基于objc_object
創(chuàng)建了一個該類(MyObject)的結構體實例畜眨。通過isa保持該類(MyObject)的結構體實例指針。
struct newObject {
Class isa;
int val0;
int val1;
...;
}
而各類的結構體是就是基于objc_class的 class_t結構體术瓮。
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
...;
};
class_t中持有康聂,聲明的成員變量,方法的名稱胞四,方法的實現(xiàn)(函數指針)恬汁,屬性以及父類的指針,并被OC運行時庫所使用的辜伟。
對象中的isa指針和類中的isa指針的區(qū)別在于氓侧,對象的是Class類型,只負責指向他的所屬類脊另。類的isa指針是class_t類型,包含了class_t結構體中的所有內容(比如方法约巷,父class等等)偎痛,指向它自己的元類(metaclass)。他的元類中就包含了各種方法独郎,變量等等踩麦。而他的metaclass的isa指向了NSObject,superclass指向它父類的元類。以ViewController舉例:
static void OBJC_CLASS_SETUP_$_ViewController(void ) {
OBJC_METACLASS_$_ViewController.isa = &OBJC_METACLASS_$_NSObject;
OBJC_METACLASS_$_ViewController.superclass = &OBJC_METACLASS_$_UIViewController;
OBJC_METACLASS_$_ViewController.cache = &_objc_empty_cache;
OBJC_CLASS_$_ViewController.isa = &OBJC_METACLASS_$_ViewController;
OBJC_CLASS_$_ViewController.superclass = &OBJC_CLASS_$_UIViewController;
OBJC_CLASS_$_ViewController.cache = &_objc_empty_cache;
}
給ViewController添加了一個實例方法(instanceFunc
)一個類方法(classFunc
)氓癌。
Metaclass方法列表的賦值:(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_ViewController;
--->OBJC_$_CLASS_METHODS_ViewController
--->:
_OBJC_$_CLASS_METHODS_ViewController __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"classFunc", "v16@0:8", (void *)_C_ViewController_classFunc}}
};
實例方法被保存在了_class_ro_t _OBJC_CLASS_RO_$_ViewController
這里他會賦值給_class_t OBJC_CLASS_$_ViewController
也就是本類谓谦。
即元類保存了類方法。
本類保存了實例方法贪婉。
MyObject類的實例變量val0,val1被直接聲明為對象的結構體成員反粥。OC中由類生成對象就意味著,生成一個基于該類的結構體實例疲迂,這些結構體實例(對象)通過isa保持該類的結構體實例指針才顿。舉個例子就是:有3個類,C動物->C狗->C柯基尤蒿,基于柯基生成的一個對象叫做KK娜膘,那么KK這個結構體就會有一個isa指向C柯基,而這個C柯基的isa指向自己的元類。當KK發(fā)送了一個消息(狼嚎時)因為C柯基本類的class_t結構體中的cache和vtable都沒有這個方法优质,便逐級會向上傳遞竣贪,找不到再performslector等等。
介紹完兩個小概念巩螃,開始從C代碼的角度分析Block演怎,舉兩個例子,一個是不截獲自動變量值的另一個是截獲自動變量值的:
不截獲自動變量值:
.m文件下的代碼:
int main() {
void(^blk)(void) = ^{printf("Block\n");};
blk();
return0;
}
clang -rewrite-objc 項目文件名.m之后避乏,摘取有用的內容:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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;
}
};
static void__main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}
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() {
void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));
((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return0;
}
源碼中的^{printf("Block\n");};
轉換后的代碼是
static void__main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}
通過轉換后的代碼可知爷耀,通過Block使用的匿名函數實際上被作為簡單的C語言函數來處理。另外根據Block語法所屬的函數名(此處為main)和該Block語法在該函數出現(xiàn)的順序值(此處為0)來給經clang變換的函數命名(void__main_block_func_0
)拍皮。該函數的參數*__cself
就是指向Block值的變量歹叮,__main_block_impl_0
結構體的指針。
那我們來看看__main_block_impl_0
結構體铆帽,去掉構造函數后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一個成員變量是impl
咆耿,他的結構體聲明是:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
impl
是虛函數列表,該結構體內存放了一個標志爹橱,包括今后版本升級所需的區(qū)域及函數指針萨螺。
第二個成員變量是Desc
指針,__main_block_desc_0
結構體的聲明是:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
這些也如同其成員名稱所示,其結構為今后版本升級所需的區(qū)域和Block大小慰技。
這兩個成員變量看完之后椭盏,再來看一下初始化含有這些結構體的__main_block_impl_0
結構體的構造函數,就是我們剛才忽略的那一部分代碼,這個代碼執(zhí)行完成意味著_cself
初始化完成:
__main_block_impl_0(void *fp,struct__main_block_desc_0 *desc,intflags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
在分析這個結構體之前吻商,先看一下這個構造函數的調用(在 int main() {...}
中):
void(*blk)(void) = ((void(*)(void))&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA));
去掉轉換部分 簡化成兩行代碼:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
該源代碼將__main_block_impl_0
結構體類型的自動變量(棧上生成的__main_block_impl_0
結構體實例的指針)掏颊,賦值給__main_block_impl_0
結構體指針類型的變量blk,對應未轉換前的代碼就是這個賦值操作:
void(^blk)(void) = ^{printf("Block\n");};
即,將Block語法生成的Block賦給Block類型變量blk。該源代碼中的Block就是__main_block_impl_0
結構體類型的自動變量艾帐,即棧上生成的__main_block_impl_0
結構體實例蚯舱。然后指針賦值。
接下來看看__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
結構體實例構造參數掩蛤。__main_block_func_0
和&__main_block_desc_0_DATA
。
__main_block_func_0
是一個由Block語法轉換的C語言函數指針,對應的函數就是:static void __main_block_func_0(struct__main_block_impl_0 *__cself) {printf("Block\n");}
&__main_block_desc_0_DATA
是作為靜態(tài)全局變量初始化的__main_block_desc_0
結構體實例指針陈肛。其初始化代碼如下:
__main_block_desc_0_DATA= {0,sizeof(struct__main_block_impl_0)};
由此可知揍鸟,該源代碼使用Block,即__main_block_impl_0
結構體實例的大小,進行初始化句旱。
下面綜合來看一下阳藻,棧上的Block即__main_block_impl_0
結構體實例的初始化到底是怎樣完成的(相關參數展開):
struct __main_block_impl_0 {
void *isa;
Int Flags;
int Reserved;
void &FuncPtr;
struct __main_block_desc_0* Desc;
}
該結構體會根據構造函數進行如下的初始化:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
即,向內存申請空間谈撒,并將__main_block_func_0
函數指針賦值給成員變量FunPtr
,為后續(xù)調用做好準備腥泥。
源碼中調用blk
的部分blk()
對應轉換后的代碼是:
((void(*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉轉換部分:
(*blk->impl.FuncPtr)(blk);
這就是簡單地使用函數指針調用函數。
階段總結
由Block語法轉換的__main_block_func_0
函數的指針被賦值給棧上生成的__main_block_impl_0
結構體實例的FuncPtr中,再將棧上生成的__main_block_impl_0
結構體實例賦值給Block類型的變量blk
,之后調用blk()
啃匿。
通過觀察調用函數蛔外,__main_block_func_0函數的參數__cself指向Block值blk
,調用:(*blk->impl.FuncPtr)
,參數:(blk)
。由此看出Block正是作為參數進行了傳遞溯乒。
截獲自動變量值的:
.m文件下的代碼:
#include <stdio.h>
int main() {
int unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
void(^blk)(void) = ^{printf(fmt, capturedVariable);};
blk();
return 0;
}
clang -rewrite-objc 項目文件名.m之后夹厌,摘取有用的內容:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int capturedVariable;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
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 unCapturedVariable = 100;
int capturedVariable = 60;
const char *fmt = "capturedVariable = %d/n";
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
這與前面轉換的源代碼稍有差異。下面來看看不同之處裆悄。首先我們注意到矛纹,Block語法表達式中使用的自動變量(capturedVariable
和fmt
)被作為成員變量追加到了__main_block_impl_0
中。并且類型完全相同光稼,而Block語法表達式中沒有使用的自動變量(unCapturedVariable
)并沒有被追加進去或南。
因此,Block的自動變量截獲只針對Block中使用的自動變量艾君。
再來看看初始化__main_block_impl_0
結構體的構造函數__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _capturedVariable, int flags=0) : fmt(_fmt), capturedVariable(_capturedVariable) {...}
采够,有什么不同。
在初始化結構實例時冰垄,根據傳遞給構造函數的參數對成員變量(capturedVariable和fmt)進行初始化吁恍。通過以下代碼:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
。使用執(zhí)行Block語法時的自動變量capturedVariable
和fmt
來初始化__main_block_impl_0
結構體實例(系統(tǒng)生成的Block,或者叫右邊的Block可能好理解一些)冀瓦。即在該源代碼中伴奥,__main_block_impl_0
結構體實例的初始化方法如下(相關參數展開):
struct __main_block_impl_0 {
void *isa;
Int Flags;
void &FuncPtr;
struct __main_block_desc_0* Desc;
char *fmt;
int capturedVariable;
}
賦值:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
fmt = "capturedVariable = %d/n";
capturedVariable = 60;
由此可知在__main_block_impl_0
結構體實例中,成員變量(fmt
和capturedVariable
)被外部自動變量值賦值翼闽。
下面再來看一下使用Block的匿名函數的實現(xiàn)拾徙。轉換前的代碼片段是:printf(fmt, capturedVariable);
轉換后的是:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}
在轉換后的源代碼中,截獲到__main_block_impl_0結構體實例的成員變量的自動變量(__cself->fmt
和__cself->capturedVariable
)感局, bound by copy尼啡!這些變量(fmt
和capturedVariable
)在Block語法表達式執(zhí)行之前(沒初始化__main_block_impl_0
之前)就已經被聲明定義在__main_block_impl_0
結構體里。