Objective-C Block

對于閉包的定義以及其他定義就不多說了。
主要說一下:1、Block內(nèi)部實(shí)現(xiàn)
2、Block種類
3、__block變量以及循環(huán)引用

Block實(shí)現(xiàn)

Block內(nèi)存結(jié)構(gòu)

image.png

對應(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 變量才會(huì)產(chǎn)生  對截獲對象的持有or釋放
///< copy:棧上的block復(fù)制到堆上 時(shí)調(diào)用
///< dispose:堆上的block被廢棄 時(shí)調(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;    ///< 一個(gè)指向自身的指針。(當(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。這個(gè)變量是一個(gè)函數(shù)指針,可以看到他的第一個(gè)參數(shù)是:void * (這個(gè)參數(shù)代表block)。why?*** 因?yàn)樵趫?zhí)行block的時(shí)候,需要從內(nèi)存中把block所捕獲的變量讀出來。***
問題來了,block捕獲的變量在內(nèi)存里? 是的,block會(huì)把所有捕獲的變量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是個(gè)OC對象,(想一下OC對象結(jié)構(gòu))。 _NSConcreteStackBlock對應(yīng)_NSConcreteGlobalBlock和_NSConcreteMallocBlock。是的,Block分為棧Block、全局Block和堆Block。如果你的Block捕獲周圍變量(testValue1),那么就會(huì)在棧上。如果對棧Block執(zhí)行copy,那么就會(huì)去堆上。
  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ù)組截獲的話,那就會(huì)產(chǎn)生諸如:

int testValueA[10] = { 1 };
int testValueB[10] = testVauleA;

此類的代碼了,C語言規(guī)范不允許此類賦值。

Block種類

全局Block _NSConcreteGlobalBlock (程序的數(shù)據(jù)區(qū)域.data)(不會(huì)訪問外部變量)

Block內(nèi)部沒有捕獲任何外部變量。
全局Block不會(huì)捕捉任何狀態(tài),他的內(nèi)存區(qū)域在編譯期已經(jīng)完全確定,他聲明在全局內(nèi)存里,不需要每次用到的時(shí)候再在棧上創(chuàng)建。全局Block相當(dāng)于單例,所以不會(huì)被系統(tǒng)回收。

棧Block _NSConcreteStackBlock (棧)(返回時(shí)銷毀)

在定義Block的時(shí)候,他的內(nèi)存區(qū)域是分配在棧上的。也就是說,Block只能在定義他的范圍內(nèi)有效。出了此范圍就失效了。but,如果在此時(shí),覆蓋了此內(nèi)存塊就會(huì)出問題了。
*** 可以使用copy操作,將棧block 拷貝到堆上。***
對于棧Block的回收,無需擔(dān)心,系統(tǒng)會(huì)自動(dòng)回收。(如果棧Block被回收了,此時(shí)使用棧Block就會(huì)出問題)。
1)、mrc
當(dāng)函數(shù)退出的時(shí)候,Block會(huì)被釋放,再次調(diào)用會(huì)產(chǎn)生crash。
2)、arc
在arc下,生成的Block也是棧Block,但是再將Block賦值給strong類型的變量的時(shí)候,會(huì)自動(dòng)執(zhí)行一次copy。

堆Block _NSConcreteMallocBlock (堆)(引用計(jì)數(shù)為0時(shí)銷毀)

跟OC對象一樣,擁有引用計(jì)數(shù)了。(對于堆Block的拷貝操作只是對引用計(jì)數(shù)的操作。)在arc環(huán)境下,堆Block和普通OC對象一樣,可以交給系統(tǒng)處理內(nèi)存。
此時(shí)~循環(huán)引用就出現(xiàn)了。
1)、mrc
需要手動(dòng)將棧Block拷貝到 堆上面。
2)、arc
自動(dòng)執(zhí)行copy操作。

__block 變量

__block變量的轉(zhuǎn)換代碼在上文中都有看到。 __block變量會(huì)跟隨Block內(nèi)存結(jié)構(gòu)的變化。
當(dāng)Block從棧復(fù)制到堆上的時(shí)候,1:如果__block變量在棧上,那么__block變量將從棧復(fù)制到堆。2:如果__block變量在堆上,那么他將被Block持有。
在arc和mrc下的區(qū)別:
1、arc:
1)、__block修飾會(huì)引起循環(huán)引用
2)、__block修飾的變量在block代碼中會(huì)被retain
2、mrc:
1)、__block修飾不會(huì)引起循環(huán)引用
2)、__block修飾的變量在block代碼中不會(huì)被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 高級編程】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容