對于閉包的定義以及其他定義就不多說了毛仪。
主要說一下:1膨桥、Block內(nèi)部實(shí)現(xiàn)
2州刽、Block種類
3辕狰、__block變量以及循環(huán)引用
Block實(shí)現(xiàn)
Block內(nèi)存結(jié)構(gòu)
對應(yīng)結(jié)構(gòu)體
struct __block_impl {
void *isa; ///< 對象指針
int Flags; ///< block附加信息矿酵。(bit表示)
int Reserved; ///< 保留
void *FuncPtr; ///< 函數(shù)指針
};
static struct __testBlockMethod_block_desc_0 {
size_t reserved; ///< 保留
size_t Block_size; ///< block大小
///< copy和dispose只有 使用了__block 變量才會產(chǎn)生 對截獲對象的持有or釋放
///< copy:棧上的block復(fù)制到堆上 時調(diào)用
///< dispose:堆上的block被廢棄 時調(diào)用
void (*copy)(struct __testBlockMethod_block_impl_0*, struct __testBlockMethod_block_impl_0*);
void (*dispose)(struct __testBlockMethod_block_impl_0*);
} __testBlockMethod_block_desc_0_DATA = { 0, sizeof(struct __testBlockMethod_block_impl_0), __testBlockMethod_block_copy_0, __testBlockMethod_block_dispose_0};
struct __Block_byref_testValue1_0 {
void *__isa;
__Block_byref_testValue1_0 *__forwarding; ///< 一個指向自身的指針唬复。(當(dāng)__block變量復(fù)制到堆上后,__forwarding指向復(fù)制到堆上的__block變量的結(jié)構(gòu)體的指針全肮。所以:不管__block變量配置在棧上還是堆上盅抚,都能夠正確訪問變量。)
int __flags;
int __size;
int testValue1;
};
struct __testBlockMethod_block_impl_0 {
struct __block_impl impl; ///< 函數(shù)指針倔矾,上圖的invoke
struct __testBlockMethod_block_desc_0* Desc; ///< block的附加信息
__Block_byref_testValue1_0 *testValue1; // by ref
__Block_byref_testValue2_1 *testValue2; // by ref
__testBlockMethod_block_impl_0(void *fp, struct __testBlockMethod_block_desc_0 *desc, __Block_byref_testValue1_0 *_testValue1, __Block_byref_testValue2_1 *_testValue2, int flags=0) : testValue1(_testValue1->__forwarding), testValue2(_testValue2->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
上圖表示的是block的內(nèi)存結(jié)構(gòu)妄均,其中最重要的是invoke柱锹。這個變量是一個函數(shù)指針,可以看到他的第一個參數(shù)是:void * (這個參數(shù)代表block)丰包。why禁熏?*** 因?yàn)樵趫?zhí)行block的時候,需要從內(nèi)存中把block所捕獲的變量讀出來邑彪。***
問題來了瞧毙,block捕獲的變量在內(nèi)存里? 是的寄症,block會把所有捕獲的變量copy一份宙彪,放在上圖中descriptor的后面。捕獲了多少變量有巧,就要占據(jù)多少內(nèi)存释漆。(這里copy的并不是對象本身,而是指向這些對象的指針)
eg:現(xiàn)在來使用clang -rewrite-objc -filename 來看一下block
#include <stdio.h>
void testBlockMethod() {
int testValue1 = 10;
void (^testBlock)(void) = ^ {
printf("%d\n", testValue1);
};
testBlock();
}
///< 產(chǎn)生的源代碼
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __testBlockMethod_block_impl_0 {
struct __block_impl impl;
struct __testBlockMethod_block_desc_0* Desc;
int testValue1;
__testBlockMethod_block_impl_0(void *fp, struct __testBlockMethod_block_desc_0 *desc, int _testValue1, int flags=0) : testValue1(_testValue1) {
impl.isa = &_NSConcreteStackBlock; ///< 是的篮迎,Block是個OC對象男图,(想一下OC對象結(jié)構(gòu))。 _NSConcreteStackBlock對應(yīng)_NSConcreteGlobalBlock和_NSConcreteMallocBlock甜橱。是的逊笆,Block分為棧Block、全局Block和堆Block岂傲。如果你的Block捕獲周圍變量(testValue1)难裆,那么就會在棧上。如果對棧Block執(zhí)行copy镊掖,那么就會去堆上乃戈。
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __testBlockMethod_block_func_0(struct __testBlockMethod_block_impl_0 *__cself) {
int testValue1 = __cself->testValue1; // bound by copy
printf("%d\n", testValue1);
}
static struct __testBlockMethod_block_desc_0 {
size_t reserved;
size_t Block_size;
} __testBlockMethod_block_desc_0_DATA = { 0, sizeof(struct __testBlockMethod_block_impl_0)};
void testBlockMethod() {
int testValue1 = 10;
void (*testBlock)(void) = ((void (*)())&__testBlockMethod_block_impl_0((void *)__testBlockMethod_block_func_0, &__testBlockMethod_block_desc_0_DATA, testValue1));
// struct __testBlockMethod_block_impl_0 tempTestBlock = __testBlockMethod_block_impl_0((void *)__testBlockMethod_block_func_0, &__testBlockMethod_block_desc_0_DATA, testValue1);
// struct __testBlockMethod_block_impl_0 *testBlock = &tempTestBlock;
// ///< __testBlockMethod_block_impl_0 構(gòu)造函數(shù)
// __testBlockMethod_block_impl_0(__testBlockMethod_block_func_0, __testBlockMethod_block_desc_0_DATA, testValue1, 0) {
// testValue1 = _testValue1;
//
// impl.isa = &_NSConcreteStackBlock;
// impl.Flags = 0;
// impl.FuncPtr = __testBlockMethod_block_func_0;
// impl.Desc = &__testBlockMethod_block_desc_0_DATA;
// }
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
// (testBlock->FuncPtr)(testBlock); ///< 明顯的函數(shù)指針調(diào)用
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
通過上述實(shí)例,可以看到如果將C語言數(shù)組截獲的話堰乔,那就會產(chǎn)生諸如:
int testValueA[10] = { 1 };
int testValueB[10] = testVauleA;
此類的代碼了偏化,C語言規(guī)范不允許此類賦值。
Block種類
全局Block _NSConcreteGlobalBlock (程序的數(shù)據(jù)區(qū)域.data)(不會訪問外部變量)
Block內(nèi)部沒有捕獲任何外部變量镐侯。
全局Block不會捕捉任何狀態(tài)侦讨,他的內(nèi)存區(qū)域在編譯期已經(jīng)完全確定,他聲明在全局內(nèi)存里苟翻,不需要每次用到的時候再在棧上創(chuàng)建韵卤。全局Block相當(dāng)于單例,所以不會被系統(tǒng)回收崇猫。
棧Block _NSConcreteStackBlock (棧)(返回時銷毀)
在定義Block的時候沈条,他的內(nèi)存區(qū)域是分配在棧上的。也就是說诅炉,Block只能在定義他的范圍內(nèi)有效蜡歹。出了此范圍就失效了屋厘。but,如果在此時月而,覆蓋了此內(nèi)存塊就會出問題了汗洒。
*** 可以使用copy操作,將棧block 拷貝到堆上父款。***
對于棧Block的回收溢谤,無需擔(dān)心,系統(tǒng)會自動回收憨攒。(如果棧Block被回收了世杀,此時使用棧Block就會出問題)。
1)肝集、mrc
當(dāng)函數(shù)退出的時候瞻坝,Block會被釋放,再次調(diào)用會產(chǎn)生crash包晰。
2)湿镀、arc
在arc下炕吸,生成的Block也是棧Block伐憾,但是再將Block賦值給strong類型的變量的時候,會自動執(zhí)行一次copy赫模。
堆Block _NSConcreteMallocBlock (堆)(引用計(jì)數(shù)為0時銷毀)
跟OC對象一樣树肃,擁有引用計(jì)數(shù)了。(對于堆Block的拷貝操作只是對引用計(jì)數(shù)的操作瀑罗。)在arc環(huán)境下胸嘴,堆Block和普通OC對象一樣,可以交給系統(tǒng)處理內(nèi)存斩祭。
此時~循環(huán)引用就出現(xiàn)了劣像。
1)、mrc
需要手動將棧Block拷貝到 堆上面摧玫。
2)耳奕、arc
自動執(zhí)行copy操作。
__block 變量
__block變量的轉(zhuǎn)換代碼在上文中都有看到诬像。 __block變量會跟隨Block內(nèi)存結(jié)構(gòu)的變化屋群。
當(dāng)Block從棧復(fù)制到堆上的時候,1:如果__block變量在棧上坏挠,那么__block變量將從棧復(fù)制到堆芍躏。2:如果__block變量在堆上,那么他將被Block持有降狠。
在arc和mrc下的區(qū)別:
1对竣、arc:
1)庇楞、__block修飾會引起循環(huán)引用
2)、__block修飾的變量在block代碼中會被retain
2否纬、mrc:
1)姐刁、__block修飾不會引起循環(huán)引用
2)、__block修飾的變量在block代碼中不會被retain
Block循環(huán)引用
這里就不說產(chǎn)生循環(huán)引用的原因或者是解決方法了烦味,網(wǎng)上一搜一大把聂使。
總結(jié)
Block提供了與C函數(shù)相同的功能。但是他使用起來更直觀谬俄。而且柏靶,Block可以捕獲周圍變量的值。
還要注意的是:
1溃论、Block對于外部變量的引用是將變量復(fù)制到Block數(shù)據(jù)結(jié)構(gòu)中實(shí)現(xiàn)的屎蜓。
2、Block對于__block修飾的外部變量的引用钥勋,是通過復(fù)制變量的地址來實(shí)現(xiàn)的炬转。
參考1:tutorials blocks
參考2:【Objective-C 高級編程】