理解Block


title: 理解Block

block是開發(fā)中經(jīng)常用到的一個對象橄镜,或者說一個方法必指。在很多編程語言中,都有閉包的概念株茶,block就是OC對閉包的實現(xiàn)来涨。

定義block

局部變量:

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {…};

屬性:

@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

方法參數(shù):

- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

方法調(diào)用:

[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

typedef定義:

typedef returnType (^TypeName)(parameterTypes); 
TypeName blockName = ^returnType(parameters) {...}

block究竟是什么

新建一個工程,我們在main函數(shù)里面寫一個最簡單的block

int main(int argc, char * argv[]) {
    @autoreleasepool {
        ^{
            int i = 0;
            i++;
        };
        return 0;
    }
}

然后用命令行將main.m這個文件轉(zhuǎn)換成C++代碼:clang -rewrite -objc main.m启盛,我們會在目錄下會得到一個main.cpp的文件蹦掐,打開我們會發(fā)現(xiàn)其實block是這樣的:

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) {
            int i = 0;
            i++;
        }

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 argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        return 0;
    }
}
  • 因為有isa指針,所以我們可以把block看做一個類對象僵闯,isa表明該block的類型卧抗,下面會講到;
  • Flags是標(biāo)志變量鳖粟;
  • Reserved是保留變量社裆;
  • FuncPtr是block執(zhí)行時候調(diào)用的函數(shù)指針。

__block_impl是block的主體數(shù)據(jù)結(jié)構(gòu)牺弹,__main_block_impl_0可以看作是block的構(gòu)造函數(shù)浦马。在__main_block_impl_0中时呀,有__block_impl類型的impl张漂,用于存儲block的數(shù)據(jù)結(jié)構(gòu)晶默,還有個__main_block_desc_0類型的指針Desc,通過下面__main_block_desc_0的結(jié)構(gòu)體可以看出航攒,這是用于存儲block的保留字段和空間大小磺陡,另外還有個同名的初始化方法,方法含有三個參數(shù)漠畜,在main函數(shù)內(nèi)的調(diào)用過這個方法币他,對比一下我們可以看到,第一個參數(shù)是__main_block_func_0函數(shù)憔狞,正好是我們所寫block中的代碼塊蝴悉,第二個參數(shù)是__main_block_desc_0_DATA,即__main_block_desc_0的結(jié)構(gòu)體瘾敢,同時賦值為{ 0, sizeof(struct __main_block_impl_0)}拍冠,即reserved為0,Block_size字段為__main_block_impl_0結(jié)構(gòu)體的大小簇抵,第三個參數(shù)是一個缺省參數(shù)庆杜,在該函數(shù)內(nèi)部,可以看到確定了該block的類型碟摆,缺省參數(shù)flags賦值給了impl.Flags晃财,要執(zhí)行的代碼塊也賦值給了impl.FuncPtr,后期block在執(zhí)行的時候可以直接根據(jù)該指針調(diào)用典蜕。

block的類型

上文將main.m文件編譯成main.cpp中断盛,有這樣一句代碼,在__main_block_impl_0結(jié)構(gòu)體的__main_block_impl_0函數(shù)中:

impl.isa = &_NSConcreteStackBlock;

block的類型總共有三種:

  1. _NSConcreteStackBlock 棧block
  2. _NSConcreteMallocBlock 堆block
  3. _NSConcreteGlobalBlock 全局block

block的類型根據(jù)isa指針來確定的愉舔,在實際開發(fā)中如何寫這三種block呢郑临?如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        int xxx = 100;
        
        NSLog(@"globalBlock:%@",^(){
            
        });
        
        NSLog(@"stackBlock:%@",^(){
            xxx;
        });
        
        void (^mallocBlock)(void) = ^(){
            xxx;
        };
        NSLog(@"mallocBlock:%@", mallocBlock);
    }
    return 0;
}

打印結(jié)果:

globalBlock:<__NSGlobalBlock__: 0x100001060>
stackBlock:<__NSStackBlock__: 0x7fff5fbff728>
mallocBlock:<__NSMallocBlock__: 0x100400110>

可以發(fā)現(xiàn),globalBlock和stackBlock差別只是stackBlock的大括號之中一段引用了一個上下文變量a屑宠,stackBlock和mallocBlock的差別只是mallocBlock在stackBlock的基礎(chǔ)上賦值給了一個變量厢洞。

所以,一個不引用外部變量的block是_NSConcreteGlobalBlock典奉,對應(yīng)的躺翻,引用了外部變量的block則會分配在棧上成為_NSConcreteStackBlock,最后卫玖,將棧block賦值給一個變量公你,系統(tǒng)會自動拷貝到堆上,成為_NSConcreteMallocBlock假瞬。

關(guān)于__block

在實際開發(fā)中陕靠,開發(fā)者會通過block做回調(diào)傳值迂尝,也因此會修改外部的局部變量。實例代碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int xxx = 100;
        
        void (^aBlock)(void) = ^(){
            xxx = 101;
        };
    }
    return 0;
}

實際上這段代碼是編譯不過的剪芥,原因剛才提到過垄开,因為block不允許修改外部的局部變量。解決辦法很簡單税肪,在局部變量的聲明前加上__block關(guān)鍵字

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int xxx = 100;
        
        void (^aBlock)(void) = ^(){
            xxx = 101;
        };
    }
    return 0;
}

為何加上__block就可以在block內(nèi)部修改局部變量a的值溉躲?我們做如下嘗試,在多個位置打印a的地址益兄。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int xxx = 100;
        NSLog(@"before block xxx:%p", &xxx);
        
        void (^aBlock)(void) = ^(){
            xxx = 101;
            NSLog(@"in block xxx:%p", &xxx);
        };
        aBlock();
        
        NSLog(@"after block xxx:%p", &xxx);
        NSLog(@"block:%@", aBlock);
    }
    return 0;
}

打印結(jié)果如下:

before block xxx:0x7fff5fbff748
in block     xxx:0x100300388
after block  xxx:0x100300388
block:<__NSMallocBlock__: 0x1003008b0>

此時將其編譯成.cpp文件:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __Block_byref_xxx_0 {
  void *__isa;
  __Block_byref_xxx_0 *__forwarding;
  int __flags;
  int __size;
  int xxx;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_xxx_0 *xxx; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_xxx_0 *_xxx, int flags=0) : xxx(_xxx->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_xxx_0 *xxx = __cself->xxx; // bound by ref
  (xxx->__forwarding->xxx) = 101;
}
        
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->xxx, (void*)src->xxx, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->xxx, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_xxx_0 xxx = {(void*)0,(__Block_byref_xxx_0 *)&xxx, 0, sizeof(__Block_byref_xxx_0), 100};

        void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_xxx_0 *)&xxx, 570425344));
        
        ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    }
    return 0;
}

我們會發(fā)現(xiàn)锻梳,用__block修飾的變量,會被轉(zhuǎn)化成一個結(jié)構(gòu)體:__Block_byref_xxx_0净捅,同時在第47行疑枯,聲明了一個該結(jié)構(gòu)體類型的變量,重新開辟空間以及賦值蛔六。那么該位置是在哪荆永,是在棧空間還是堆空間古今?

對比剛才的打印結(jié)果屁魏,before block ****xxx****:****0x7fff5fbff748,拷貝以后in block ****xxx****:****0x100300388捉腥,二者相差太多氓拼,反而拷貝后xxx的地址與存在堆內(nèi)存的block相差1M不到,由此可以推斷出抵碟,block在拷貝的過程中桃漾,將以__block修飾的局部變量也拷貝到了堆中。**
**

簡單地數(shù)值類型比較簡單拟逮,下面我們嘗試一下對象類型撬统。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
        NSLog(@"before block:a:%p , %p", a, &a);
        void (^foo)(void) = ^{
            NSLog(@"in block:a:%p , %p", a, &a);
        };
        foo();
        NSLog(@"after block:a:%p , %p", a, &a);
    }
    return 0;
}

打印結(jié)果:

before block:a:0x1003064f0 , 0x7fff5fbff748
in block:    a:0x1003064f0 , 0x1003067a8
after block: a:0x1003064f0 , 0x1003067a8

可以發(fā)現(xiàn),a所指向堆中內(nèi)容的地址不變敦迄,變的是a本身的地址恋追。所以在block拷貝過程中,將原本在棧中的a拷貝到了堆上罚屋,a的內(nèi)容仍然是原本字符串的堆地址苦囱,因為由棧到堆,所以a本身的地址發(fā)生了變化脾猛,再結(jié)合剛才cpp文件中撕彤,__Block_byref_xxx_0結(jié)構(gòu)體中的__forwarding變量,無論是一開始還在棧的block猛拴,還是拷貝后在堆上的block羹铅,其__forwarding都是指向了堆上的block蚀狰,所以才能指向同一段字符串內(nèi)容。

__block在ARC和MRC下的區(qū)別

同樣是上面一段代碼职员,在MRC環(huán)境下麻蹋,打印出來的結(jié)果是什么呢?

before block:a:0x1003064a0 , 0x7fff5fbff748
in block:    a:0x1003064a0 , 0x7fff5fbff748
after block: a:0x1003064a0 , 0x7fff5fbff748

和ARC環(huán)境下的不一樣廉邑,a本身的地址并沒有因為__block的修飾而變化哥蔚,也就是說倒谷,a仍然在棧上蛛蒙,并沒有拷貝到堆上。同樣在MRC環(huán)境下渤愁,我們再稍作修改牵祟。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
        NSLog(@"before block:a:%p , %p", a, &a);
        void (^foo)(void) = [^{
            NSLog(@"in block:a:%p , %p", a, &a);
        } copy];
        foo();
        NSLog(@"after block:a:%p , %p", a, &a);
    }
    return 0;
}

打印結(jié)果:

before block:a:0x100302f30 , 0x7fff5fbff748
in block:    a:0x100302f30 , 0x100400308
after block: a:0x100302f30 , 0x100400308

在block賦值之前先拷貝一下,這時的效果跟ARC下就差不多了抖格。

總結(jié):MRC下block并沒有對__block修飾的局部變量進(jìn)行拷貝诺苹,只是一個弱引用;相反雹拄,ARC下block會拷貝該變量收奔,并對其retain。

block的函數(shù)性

如果從數(shù)據(jù)結(jié)構(gòu)的角度上說滓玖,因為block是結(jié)構(gòu)體指針坪哄,因為其有isa指針,那么在實際使用過程中势篡,我們更注重的是如何利用block進(jìn)行傳值和處理翩肌,其作用更像一個函數(shù)。

^(){
    NSLog(@"block is kind of function too.");
}();

該段代碼可以直接運行禁悠,加上其可以作為參數(shù)和返回值念祭,完全可以實現(xiàn)類似于swift下真正意義上的block,此時碍侦,它就是一個函數(shù)粱坤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓷产,隨后出現(xiàn)的幾起案子站玄,更是在濱河造成了極大的恐慌,老刑警劉巖拦英,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜒什,死亡現(xiàn)場離奇詭異,居然都是意外死亡疤估,警方通過查閱死者的電腦和手機(jī)灾常,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門霎冯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钞瀑,你說我怎么就攤上這事沈撞。” “怎么了雕什?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵缠俺,是天一觀的道長。 經(jīng)常有香客問我贷岸,道長壹士,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任偿警,我火速辦了婚禮躏救,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘螟蒸。我一直安慰自己盒使,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布七嫌。 她就那樣靜靜地躺著少办,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诵原。 梳的紋絲不亂的頭發(fā)上英妓,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音皮假,去河邊找鬼鞋拟。 笑死,一個胖子當(dāng)著我的面吹牛惹资,可吹牛的內(nèi)容都是我干的贺纲。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼褪测,長吁一口氣:“原來是場噩夢啊……” “哼猴誊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起侮措,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤懈叹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后分扎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澄成,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墨状。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卫漫。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肾砂,靈堂內(nèi)的尸體忽然破棺而出列赎,到底是詐尸還是另有隱情,我是刑警寧澤镐确,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布包吝,位于F島的核電站,受9級特大地震影響源葫,放射性物質(zhì)發(fā)生泄漏诗越。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一臼氨、第九天 我趴在偏房一處隱蔽的房頂上張望掺喻。 院中可真熱鬧芭届,春花似錦储矩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逃片,卻和暖如春屡拨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褥实。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工呀狼, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人损离。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓哥艇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僻澎。 傳聞我的和親對象是個殘疾皇子貌踏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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

  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動變量值 int main(){ ...
    南京小伙閱讀 913評論 1 3
  • 前言 Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,759評論 0 23
  • 本文主要根據(jù)《Objective-C高級編程》這本書中的第二章來進(jìn)行的一個總結(jié)窟勃,其中包含了查看其它文章后的總結(jié)和自...
    AnICoo1閱讀 1,024評論 0 2
  • Block基礎(chǔ)回顧 1.什么是Block祖乳? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行)秉氧,差不多就與C語言...
    Bugfix閱讀 6,749評論 5 61
  • 1.隱藏導(dǎo)航欄 ActionBar actionBar = getSupportActionBar(); acti...
    V會飛的蝸牛閱讀 419評論 0 1