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 變量才會產(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 高級編程】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市算灸,隨后出現(xiàn)的幾起案子扼劈,更是在濱河造成了極大的恐慌,老刑警劉巖菲驴,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荐吵,死亡現(xiàn)場離奇詭異,居然都是意外死亡赊瞬,警方通過查閱死者的電腦和手機(jī)先煎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巧涧,“玉大人薯蝎,你說我怎么就攤上這事“” “怎么了占锯?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長闷供。 經(jīng)常有香客問我烟央,道長,這世上最難降的妖魔是什么歪脏? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任疑俭,我火速辦了婚禮,結(jié)果婚禮上婿失,老公的妹妹穿的比我還像新娘钞艇。我一直安慰自己啄寡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布哩照。 她就那樣靜靜地躺著挺物,像睡著了一般。 火紅的嫁衣襯著肌膚如雪飘弧。 梳的紋絲不亂的頭發(fā)上识藤,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音次伶,去河邊找鬼痴昧。 笑死,一個胖子當(dāng)著我的面吹牛冠王,可吹牛的內(nèi)容都是我干的赶撰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼柱彻,長吁一口氣:“原來是場噩夢啊……” “哼豪娜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哟楷,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瘤载,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吓蘑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惕虑,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坟冲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年磨镶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片健提。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡琳猫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出私痹,到底是詐尸還是另有隱情脐嫂,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布紊遵,位于F島的核電站账千,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏暗膜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望充包。 院中可真熱鬧,春花似錦论衍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘫寝,卻和暖如春蜒蕾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焕阿。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工滥搭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捣鲸。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓瑟匆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親栽惶。 傳聞我的和親對象是個殘疾皇子愁溜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容