Objective-C中的Block
1.Block定義
可以用一句話來(lái)表示Block:帶有自動(dòng)變量(局部變量)的匿名函數(shù)。
在iOS中使用“^”來(lái)聲明一個(gè)Block。Block的內(nèi)容是包含在“{}”中的,并且和C語(yǔ)言一樣用“;”來(lái)表示語(yǔ)句的結(jié)束,標(biāo)準(zhǔn)語(yǔ)法如下所示:
//完整語(yǔ)法 ^ 返回值類型 參數(shù)列表 表達(dá)式//省略返回值 ^ 參數(shù)列表 表達(dá)式//省略參數(shù)列表 ^ 返回值類型 表達(dá)式//省略返回值和參數(shù)列表 ^ 表達(dá)式從上面可以看到,Block和函數(shù)很相似,具體體現(xiàn)在這些方面:
我們通常使用如下形式將Block賦值給Block類型變量,示例代碼如下:
int multiplier = 7;int (^myBlock)(int) = ^(int num){ return multiplier * num; };NSLog(@"%d",myBlock(3));采用這種方式在函數(shù)參數(shù)或返回值中使用Block類型變量時(shí),記述方式極為復(fù)雜。這時(shí),我們可以使用typedef來(lái)解決該問(wèn)題。
示例1:沒(méi)有使用typedef
- (void)loadDataFromUrl:(void(^)(NSString *))retData { }示例2:使用typedef
typedef void(^RetDataHandler)(NSString *); - (void)loadDataFromUrl:(RetDataHandler)retData {}從上面的代碼可以看到,使用typedef聲明之后,在方法中傳遞block參數(shù)時(shí),更容易理解。
Block的強(qiáng)大之處是:在聲明它的范圍里,所有變量都可以為其所捕獲。下面我們來(lái)看看自動(dòng)變量。
2.自動(dòng)變量
從上面Block語(yǔ)法的介紹中,我們可以理解“帶有自動(dòng)變量(局部變量)的匿名函數(shù)”中的匿名函數(shù)。那么“帶有自動(dòng)變量(局部變量)”是什么呢?這個(gè)在Block中表現(xiàn)為“截獲自動(dòng)變量值”。示例如下:
int iCode = 10; NSString *strName = @"Tom";void (^myBlock)(void) = ^{// 結(jié)果:My name is Tom,my code is 10NSLog(@"My name is %@,my code is %d", strName, iCode); };iCode = 20; strName = @"Jim";myBlock(); // 結(jié)果:My name is Jim,my code is 20 NSLog(@"My name is %@,my code is %d", strName, iCode);從代碼中可以看到,Block表達(dá)式截獲所使用的自動(dòng)變量iCode和strName的值,即保存該自動(dòng)變量的瞬間值。因?yàn)锽lock表達(dá)式保存了自動(dòng)變量的值,所以在執(zhí)行Block語(yǔ)法后,即使改寫B(tài)lock中所用的自動(dòng)變量的值也不會(huì)影響B(tài)lock執(zhí)行時(shí)自動(dòng)變量的值,這就是自動(dòng)變量值的截獲。
如果我們想在Block中修改截獲的自動(dòng)變量值,會(huì)有什么結(jié)果?咱們做個(gè)嘗試:?
從上面可以看到,該源代碼會(huì)產(chǎn)生編譯錯(cuò)誤。若想在Block語(yǔ)法的表達(dá)式中將值賦給在Block語(yǔ)法外聲明的自動(dòng)變量,需要在該自動(dòng)變量上附加__block說(shuō)明符,示例如下:
__block NSString *strName = @"Tom";void (^myBlock)(void) = ^{strName = @"Sky"; }; strName = @"Jim";myBlock(); // 結(jié)果:My name is Sky NSLog(@"My name is %@",strName);需要說(shuō)明的是,對(duì)于截獲的自動(dòng)變量,調(diào)用變更該對(duì)象的方法是沒(méi)有問(wèn)題的:即賦值給截獲的自動(dòng)變量會(huì)產(chǎn)生編譯錯(cuò)誤,但使用截獲的自動(dòng)變量的值卻不會(huì)有任何問(wèn)題。
3.如何在代碼中創(chuàng)建Block?
3.1不帶參數(shù)和返回值的block
- (void)testBlockOne {void (^myBlock)(void) = ^{NSLog(@"Hello Block One");};NSLog(@"%@",myBlock);myBlock(); }3.2帶參數(shù)的block
- (void)testBlockTwo{void (^myBlock)(NSString *) = ^(NSString *str){NSLog(@"Hello Block %@",str);};NSLog(@"%@",myBlock);myBlock(@"ligf"); }3.3帶參數(shù)和返回值的block
- (void)testBlockThree{int (^myBlock)(NSString *,int) = ^(NSString *str,int code){NSLog(@"Hello Block %@,code is %d", str, code);return 1;};NSLog(@"%@",myBlock);int iRet = myBlock(@"ligf",3);NSLog(@"%d",iRet); }4.block實(shí)現(xiàn)頁(yè)面?zhèn)髦?/strong>
4.1先用傳統(tǒng)的Delegate來(lái)進(jìn)行示例
WebServicesHelper類:
@class WebServicesHelper;@protocol WebServicesHelperDelegate <NSObject>- (void)networkFecherSuccess:(WebServicesHelper *)networkFetcherdidFinishWithData:(NSString *)data; - (void)networkFecherFailed:(WebServicesHelper *)networkFetchererror:(NSError *)error;@end@interface WebServicesHelper : NSObject@property (nonatomic, retain) NSURL *url; @property (nonatomic, assign) id<WebServicesHelperDelegate> delegate;- (id)initWithUrl:(NSURL *)url; - (void)startDownload;@end - (id)initWithUrl:(NSURL *)url {self = [super init];if (self){self.url = url;}return self; }- (void)startDownload {NSError *error = nil;NSString *str = [NSString stringWithContentsOfURL:self.url encoding:NSUTF8StringEncoding error:&error];if (error){[_delegate networkFecherFailed:self error:error];}else{[_delegate networkFecherSuccess:self didFinishWithData:str];} }DownloadByDelegate類:
#import "WebServicesHelper.h"@interface DownloadByDelegate : NSObject<WebServicesHelperDelegate> - (void)fetchUrlData; @end@implementation DownloadByDelegate- (void)fetchUrlData {NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"];WebServicesHelper *webServicesHelper = [[WebServicesHelper alloc] initWithUrl:url];webServicesHelper.delegate = self;[webServicesHelper startDownload]; }- (void)networkFecherSuccess:(WebServicesHelper *)networkFetcherdidFinishWithData:(NSString *)data {NSLog(@"%@",data); }- (void)networkFecherFailed:(WebServicesHelper *)networkFetchererror:(NSError *)error; {NSLog(@"%@",error); }@end調(diào)用:
DownloadByDelegate *downloadByDelegate = [[DownloadByDelegate alloc] init]; [downloadByDelegate fetchUrlData];4.2再看看用Block的實(shí)現(xiàn)
DownloadByBlock類:
typedef void(^NetworkFetcherCompletionHandler) (NSString *data, NSError *error);@interface DownloadByBlock : NSObject- (id)initWithUrl:(NSURL *)url; - (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion;@end @implementation DownloadByBlock {NSURL *_url; }- (id)initWithUrl:(NSURL *)url {self = [super self];if (self){_url = url;}return self; }- (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion {NSError *error;NSString *str = [NSString stringWithContentsOfURL:_url encoding:NSUTF8StringEncoding error:&error];completion(str,error); }@end調(diào)用:?
DownloadByBlock *downloadByBlock = [[DownloadByBlock alloc] initWithUrl:[NSURL URLWithString:@"http://www.baidu.com"]]; [downloadByBlock startWithCompletionHandler:^ (NSString *data, NSError *error){NSLog(@"%@",data); }];從上面的代碼可以明顯看到,使用Block方式,代碼的可讀性更高,使用也更加的方便。
5.Block存儲(chǔ)域
先看一個(gè)示例:
int (^myBlockOne)(int,int) = ^ (int a, int b) {return a + b;};//myBlockOne = <__NSGlobalBlock__: 0x101f1d230>NSLog(@"myBlockOne = %@", myBlockOne);int base = 100;int (^myBlockTwo)(int,int) = ^ (int a, int b) {return base + a + b;};//MRC:myBlockTwo = <__NSStackBlock__: 0x7fff5dce6520>//ARC:myBlockTwo = <__NSMallocBlock__: 0x6000002441d0>NSLog(@"myBlockTwo = %@", myBlockTwo);int (^myBlockThree)(int,int) = [[myBlockTwo copy] autorelease];//myBlockThree = <__NSMallocBlock__: 0x6080000499c0>NSLog(@"myBlockThree = %@", myBlockThree);從上面的代碼可以看到,Block在內(nèi)存中的位置可以分為三種類型:__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__。
- __NSGlobalBlock__:與全局變量一樣,該類對(duì)象存儲(chǔ)在程序的數(shù)據(jù)區(qū)域(.data區(qū))中;
- __NSStackBlock__:從名稱中可以看到含有“Stack”,即該類對(duì)象存儲(chǔ)在棧上;位于棧上的Block對(duì)象,函數(shù)返回后Block將無(wú)效,變成野指針;
- __NSMallocBlock__:該類對(duì)象由malloc函數(shù)分配的內(nèi)存塊(堆)中。
其中在全局區(qū)域和堆里面存儲(chǔ)的對(duì)象是相對(duì)安全的,但是在棧區(qū)里面的變量是危險(xiǎn)的,有可能造成程序的崩潰,因此在iOS中如果使用block的成員變量或者屬性時(shí),需要將其copy到堆內(nèi)存中。
上面的例子中,myBlockOne和myBlockTwo的區(qū)別在于:myBlockOne沒(méi)有使用Block以外的任何外部變量,Block不需要建立局部變量值的快照,這使myBlockOne與函數(shù)沒(méi)有任何區(qū)別。myBlockTwo與myBlockOne唯一不同是的使用了局部變量base,在定義(注意是定義,不是運(yùn)行)myBlockTwo時(shí),局部變量base當(dāng)前值被截獲到棧上,作為常量供Block使用。執(zhí)行下面代碼,結(jié)果是103,而不是203。
int base = 100; int (^myBlockTwo)(int,int) = ^ (int a, int b) {return base + a + b; }; base = 200; NSLog(@“%d", myBlockTwo(1, 2));我們?cè)倏匆欢未a,大家思考一下這段代碼有沒(méi)有問(wèn)題?
int base = 100;void(^myBlock)();if (YES){myBlock = ^{NSLog(@"This is ture,%d", base);};}else{myBlock = ^{NSLog(@"This is false,%d", base);};}myBlock();表面上看,和我們以前給變量賦值的語(yǔ)句沒(méi)什么太大的差異,那么是不是沒(méi)有問(wèn)題呢?答案是NO。在定義這個(gè)塊的時(shí)候,其所占的內(nèi)存區(qū)域是分配在棧中的,塊只在定義它的那個(gè)范圍內(nèi)有效,也就是說(shuō)這個(gè)塊只在對(duì)應(yīng)的if或else語(yǔ)句范圍內(nèi)有效。當(dāng)離開(kāi)了這個(gè)范圍之后,編譯器有可能把分配給塊的內(nèi)存覆寫掉。這樣運(yùn)行的時(shí)候,若編譯器未覆寫待執(zhí)行的塊,則程序照常運(yùn)行;若覆寫,則程序崩潰。
5.1__NSGlobalBlock__的實(shí)現(xiàn)
我們先寫一段生成__NSGlobalBlock__的代碼:
#include <stdio.h> void (^gofBlock)(void) = ^{printf("Hello, Gof"); }; int main(int argc, char * argv[]) {gofBlock();return 0; }對(duì)上面的代碼使用“clang -rewrite-objc main.m”進(jìn)行編譯,生成后的代碼整理之后如下:
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; };struct __gofBlock_block_impl_0 {struct __block_impl impl;struct __gofBlock_block_desc_0* Desc;__gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteGlobalBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) {printf("Hello, Gof"); }static struct __gofBlock_block_desc_0 {size_t reserved;size_t Block_size; } __gofBlock_block_desc_0_DATA = { 0, sizeof(struct __gofBlock_block_impl_0)};static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA); void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0);int main(int argc, char * argv[]) {((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);return 0; }我們將源代碼分成幾個(gè)部分來(lái)逐步理解。
第一部分:源碼中的Block語(yǔ)法
^{printf("Hello, Gof");};可以看到,變換后的代碼中也含有相同的表達(dá)式:
static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) {printf("Hello, Gof"); }從代碼可以看到,通過(guò)Blocks使用的匿名函數(shù),實(shí)際上被作為簡(jiǎn)單的C語(yǔ)言函數(shù)來(lái)處理。該函數(shù)名根據(jù)Block語(yǔ)法所屬的函數(shù)名和該Block語(yǔ)法在函數(shù)出現(xiàn)的順序值(這里為0)來(lái)命名。函數(shù)的參數(shù)__cself為指向__gofBlock_block_impl_0結(jié)構(gòu)體的指針。
第二部分:__gofBlock_block_impl_0結(jié)構(gòu)體?
struct __gofBlock_block_impl_0 {struct __block_impl impl;struct __gofBlock_block_desc_0* Desc;__gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteGlobalBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };我們先不看構(gòu)造函數(shù),__gofBlock_block_impl_0包含兩個(gè)成員變量:impl和Desc。
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; };static struct __gofBlock_block_desc_0 {size_t reserved;size_t Block_size; }從這兩個(gè)結(jié)構(gòu)體的聲明,可以知道:impl包含某些標(biāo)志、所需區(qū)域、函數(shù)指針等信息;Desc包含所需區(qū)域、Block大小信息。
接下來(lái)我們看看__gofBlock_block_impl_0構(gòu)造函數(shù)的調(diào)用:
static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA); void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0);繼續(xù)看__gofBlock_block_impl_0構(gòu)造函數(shù)的兩個(gè)參數(shù):
通過(guò)參數(shù)的配置,我們來(lái)看看__gofBlock_block_impl_0結(jié)構(gòu)體的初始化:
isa = &_NSConcreteGlobalBlock;Flags = 0;
Reserved = 0;
FuncPtr = __gofBlock_block_func_0;
Desc = &__gofBlock_block_desc_0_DATA;
關(guān)于_NSConcreteGlobalBlock,我們可以看看RunTime之類與對(duì)象。實(shí)際上,__gofBlock_block_impl_0結(jié)構(gòu)體相當(dāng)于基于objc_object結(jié)構(gòu)體的OC類對(duì)象的結(jié)構(gòu)體。對(duì)其中的isa成員變量初始化,_NSConcreteGlobalBlock相當(dāng)于objc_class結(jié)構(gòu)體實(shí)例。在將Block作為OC的對(duì)象處理時(shí),關(guān)于該類的信息放置于_NSConcreteGlobalBlock中。
第三部分:Block的調(diào)用。
gofBlock();變換后的代碼:
((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);去掉轉(zhuǎn)換部分:
(*gofBlock->impl.FuncPtr)(gofBlock);這實(shí)際上就是使用函數(shù)指針調(diào)用函數(shù)__gofBlock_block_func_0。另外,我們也可以看到,__gofBlock_block_func_0函數(shù)的參數(shù)__cself指向Block值。
總結(jié)一下:
- block 實(shí)際是一個(gè)對(duì)象,它主要由 一個(gè) impl 和 一個(gè) Desc 組成。
- impl.FuncPtr是實(shí)際的函數(shù)指針,在這里它指向 __gofBlock_block_func_0。
- Desc?用于描述當(dāng)前這個(gè) block 的附加信息的,包括結(jié)構(gòu)體的大小,需要 capture 和 dispose 的變量列表等。結(jié)構(gòu)體大小需要保存是因?yàn)?#xff0c;每個(gè) block 因?yàn)闀?huì) capture 一些變量,這些變量會(huì)加到 __gofBlock_block_impl_0 這個(gè)結(jié)構(gòu)體中,使其體積變大。
5.2__NSStackBlock__的實(shí)現(xiàn)
先看代碼:
#include <stdio.h>int main(int argc, char * argv[]) {int a = 18;void (^gofBlock)(void) = ^{printf("I have %d ages", a);};gofBlock();return 0; }對(duì)上面的代碼使用“clang -rewrite-objc main.m”進(jìn)行編譯,生成后的代碼整理之后如下:
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;int a;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int a = __cself->a; // bound by copy printf("I have %d ages", a);}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[]) {int a = 18;void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);return 0; }這和上面轉(zhuǎn)換的源代碼有一點(diǎn)點(diǎn)差異。
首先,Block語(yǔ)法表達(dá)式中的自動(dòng)變量被作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中。
其次,在調(diào)用__main_block_impl_0構(gòu)造函數(shù)初始化的時(shí)候,對(duì)由自動(dòng)變量追加的成員變量進(jìn)行了初始化。
void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));通過(guò)參數(shù)的配置,__main_block_impl_0結(jié)構(gòu)體構(gòu)造函數(shù)的初始化:
isa = &_NSConcreteStackBlock; Flags = 0; Reserved = 0; FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA; a = 18;再次,Block匿名函數(shù)的實(shí)現(xiàn):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int a = __cself->a; // bound by copy printf("I have %d ages", a);}截獲到__main_block_impl_0結(jié)構(gòu)體實(shí)例的成員變量上的自動(dòng)變量a,該變量在Block語(yǔ)法表達(dá)式之前被聲明定義。
總結(jié)一下:
- isa 指向 _NSConcreteStackBlock,說(shuō)明這是一個(gè)分配在棧上的實(shí)例。
- main_block_impl_0 中增加了一個(gè)變量 a,在 block 中引用的變量 a 實(shí)際是在申明 block 時(shí),被復(fù)制到?main_block_impl_0 結(jié)構(gòu)體中的那個(gè)變量 a。因?yàn)檫@樣,我們就能理解,在 block 內(nèi)部修改變量 a 的內(nèi)容,不會(huì)影響外部的實(shí)際變量 a。
- main_block_impl_0 中由于增加了一個(gè)變量 a,所以結(jié)構(gòu)體的大小變大了,該結(jié)構(gòu)體大小被寫在了?main_block_desc_0 中。
現(xiàn)在我們修改一下上面的代碼,在變量前面增加 __block 關(guān)鍵字:
#include <stdio.h>int main(int argc, char * argv[]) {__block int a = 18;void (^gofBlock)(void) = ^{a = 20;printf("I have %d ages", a);};gofBlock();printf("a variable is %d", a);return 0; }使用“clang -rewrite-objc main.m”進(jìn)行編譯:
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; };struct __Block_byref_a_0 {void *__isa; __Block_byref_a_0 *__forwarding;int __flags;int __size;int a; };struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_a_0 *a; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 20;printf("I have %d ages", (a->__forwarding->a));}static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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, char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);printf("a variable is %d", (a.__forwarding->a));return 0; }從編譯之后的源代碼可以看到,只加了一個(gè)__block關(guān)鍵字,源碼數(shù)量大大增加。
首先,我們看看這句:?
__block int a = 18;編譯之后:
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};去掉類型轉(zhuǎn)換:
__Block_byref_a_0 a = {0,&a,0, sizeof(__Block_byref_a_0), 18 };從源碼可以看到,這個(gè)__block變量變成了__Block_byref_a_0結(jié)構(gòu)體類型的自動(dòng)變量。
接下來(lái),我們看看Block匿名函數(shù)的實(shí)現(xiàn):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 20;printf("I have %d ages", (a->__forwarding->a));}__Block_byref_a_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有指向該實(shí)例自身的指針。通過(guò)成員變量__forwarding訪問(wèn)成員變量a。
總結(jié)一下:
- 源碼中增加一個(gè)名為 __Block_byref_a_0?的結(jié)構(gòu)體,用來(lái)保存我們要 capture 并且修改的變量 a。
- main_block_impl_0 中引用的是?__Block_byref_a_0?的結(jié)構(gòu)體指針,這樣就可以達(dá)到修改外部變量的作用。
- __Block_byref_a_0?結(jié)構(gòu)體中帶有 isa,說(shuō)明它也是一個(gè)對(duì)象。
- 我們需要負(fù)責(zé)?__Block_byref_a_0?結(jié)構(gòu)體相關(guān)的內(nèi)存管理,所以?main_block_desc_0 中增加了 copy 和 dispose 函數(shù)指針,對(duì)于在調(diào)用前后修改相應(yīng)變量的引用計(jì)數(shù)。
5.3__NSMallocBlock__的實(shí)現(xiàn)
__NSMallocBlock__類型的 block 通常不會(huì)在源碼中直接出現(xiàn),因?yàn)槟J(rèn)它是當(dāng)一個(gè) block 被 copy 的時(shí)候,才會(huì)將這個(gè) block 復(fù)制到堆中。以下是一個(gè) block 被 copy 時(shí)的示例代碼 (來(lái)自?這里),可以看到,在第 8 步,目標(biāo)的 block 類型被修改為 _NSConcreteMallocBlock。
static void *_Block_copy_internal(const void *arg, const int flags) {struct Block_layout *aBlock;const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;// 1if (!arg) return NULL;// 2aBlock = (struct Block_layout *)arg;// 3if (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on highlatching_incr_int(&aBlock->flags);return aBlock;}// 4else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;}// 5struct Block_layout *result = malloc(aBlock->descriptor->size);if (!result) return (void *)0;// 6memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first// 7result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not neededresult->flags |= BLOCK_NEEDS_FREE | 1;// 8result->isa = _NSConcreteMallocBlock;// 9if (result->flags & BLOCK_HAS_COPY_DISPOSE) {(*aBlock->descriptor->copy)(result, aBlock); // do fixup }return result; }5.4Block的copy、retain、release
和OC中的對(duì)象的copy、retain、release不同,Block對(duì)象:
- Block_copy與copy等效,Block_release與release等效;
- 對(duì)Block不管是retain、copy、release都不會(huì)改變引用計(jì)數(shù)retainCount,retainCount始終是1;
- NSGlobalBlock:retain、copy、release操作都無(wú)效,因?yàn)槿謮K絕不可能為系統(tǒng)所回收。這種塊實(shí)際上相當(dāng)于單例;
- NSStackBlock:retain、release操作無(wú)效,必須注意的是,NSStackBlock在函數(shù)返回后,Block內(nèi)存將被回收。即使retain也沒(méi)用。容易犯的錯(cuò)誤是[[mutableAarry addObject:stackBlock],在函數(shù)出棧后,從mutableAarry中取到的stackBlock已經(jīng)被回收,變成了野指針。正確的做法是先將stackBlock copy到堆上,copy之后生成新的NSMallocBlock類型對(duì)象,然后返回: [[myBlock copy] autorelease]。
- NSMallocBlock支持retain、release,雖然retainCount始終是1,但內(nèi)存管理器中仍然會(huì)增加、減少計(jì)數(shù)。copy之后不會(huì)生成新的對(duì)象,只是增加了一次引用,類似retain;
- 盡量不要對(duì)Block使用retain操作,原因是并沒(méi)有Block_retain()這樣的函數(shù),而且objc里面的retain消息發(fā)送給block對(duì)象后,其內(nèi)部實(shí)現(xiàn)是什么都不做。
5.5Block對(duì)外部變量的存取管理
5.5.1基本數(shù)據(jù)類型
- 1、局部變量。局部變量,在Block中只讀。Block定義時(shí)截獲變量的值,在Block中作為常量使用,所以即使變量的值在Block外改變,也不影響它在Block中的值。
- 2、全局變量或靜態(tài)變量。在內(nèi)存中的地址是固定的,Block在讀取該變量值的時(shí)候是直接從其所在內(nèi)存讀出,獲取到的是最新值,而不是在定義時(shí)截獲的值。
- 3、__block修飾的變量。被__block修飾的變量稱作Block變量。 基本類型的Block變量等效于全局變量、或靜態(tài)變量。
注意:Block被另一個(gè)Block使用時(shí),另一個(gè)Block被copy到堆上時(shí),被使用的Block也會(huì)被copy。但作為參數(shù)的Block是不會(huì)發(fā)生copy的。
具體看示例:
- (void)p_copyBlock {int base = 100;GofBlock blockOne = ^ (int a, int b) {return base + a + b;};// <__NSStackBlock__: 0x7fff52191248>NSLog(@"blockOne:%@", blockOne);[self p_copyBlockWithParam:blockOne]; }- (void)p_copyBlockWithParam:(GofBlock)blockParam {// <__NSStackBlock__: 0x7fff52191248>// 和上面一樣,說(shuō)明作為參數(shù)傳遞時(shí),并不會(huì)發(fā)生copyNSLog(@"blockParam:%@", blockParam);void (^blockTwo)(GofBlock) = ^ (GofBlock gofBlock) {// 第一次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 第二次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 無(wú)論blockTwo在堆上還是棧上,作為參數(shù)的Block不會(huì)發(fā)生copy。NSLog(@"gofBlock:%@", gofBlock);// 第一次:blockParam:<__NSStackBlock__: 0x7fff52191248>// 第二次:blockParam:<__NSMallocBlock__: 0x618000241500>// 當(dāng)blockTwo copy到堆上時(shí),blockParam也被copy了一分到堆上。NSLog(@"blockParam:%@", blockParam);};blockTwo(blockParam); // blockTwo在棧上// blockTwo:<__NSStackBlock__: 0x7fff521911e8>NSLog(@"blockTwo:%@", blockTwo);blockTwo = [[blockTwo copy] autorelease];blockTwo(blockParam); // blk在堆上// blockTwo after copy:<__NSMallocBlock__: 0x61000005cef0>NSLog(@"blockTwo after copy:%@",blockTwo); }5.5.2Objective-C對(duì)象
不同于基本類型,Block會(huì)引起OC對(duì)象的引用計(jì)數(shù)變化。這里對(duì)static、global、instance、block變量非arc下的情況進(jìn)行分析。還是先看示例:
.h文件:
@interface TestBlock : NSObject {NSObject *_instanceObj; }.m文件:
@implementation TestBlockNSObject *_globalObj = nil;- (id) init {self = [super init];if (self){_instanceObj = [[NSObject alloc] init];}return self; }- (void)mrcMemoryManage {static NSObject *_staticObj = nil;_globalObj = [[NSObject alloc] init];_staticObj = [[NSObject alloc] init];NSObject *localObj = [[NSObject alloc] init];__block NSObject *blockObj = [[NSObject alloc] init];typedef void (^MyBlock)(void) ;MyBlock aBlock = ^{NSLog(@"%@", _globalObj);NSLog(@"%@", _staticObj);NSLog(@"%@", _instanceObj);NSLog(@"%@", localObj);NSLog(@"%@", blockObj);};aBlock = [[aBlock copy] autorelease];aBlock();// 全局變量:1; 靜態(tài)變量:1; 實(shí)例變量:1; 臨時(shí)變量:2; block變量:1NSLog(@"全局變量:%lu; 靜態(tài)變量:%lu; 實(shí)例變量:%lu; 臨時(shí)變量:%lu; block變量:%lu", (unsigned long)[_globalObj retainCount], (unsigned long)[_staticObj retainCount], (unsigned long)[_instanceObj retainCount], (unsigned long)[localObj retainCount], (unsigned long)[blockObj retainCount]); }根據(jù)上面的結(jié)果,我們來(lái)分析一下:
- 全局變量和靜態(tài)變量在內(nèi)存中的位置是確定的,所以Block copy時(shí)不會(huì)retain對(duì)象。
- 實(shí)例變量在Block copy時(shí)也沒(méi)有直接retain實(shí)例變量對(duì)象本身,但會(huì)retain self。所以在Block中可以直接讀寫_instanceObj變量。
- 臨時(shí)變量在Block copy時(shí),系統(tǒng)自動(dòng)retain對(duì)象,增加其引用計(jì)數(shù)。
- block變量在Block copy時(shí)也不會(huì)retain。
6.參考資料
- Objective-C Blocks Quiz
- 談Objective-C block的實(shí)現(xiàn)
- A look inside blocks: Episode 3 (Block_copy)
- GCC,LLVM,Clang編譯器對(duì)比
?
轉(zhuǎn)載于:https://www.cnblogs.com/LeeGof/p/6755907.html
總結(jié)
以上是生活随笔為你收集整理的Objective-C中的Block的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 远程桌面连接错误:由于安全设置错误,客户
- 下一篇: BZOJ4855 : [Jsoi2016