Objective-C中的Block
1.Block定義
可以用一句話來表示Block:帶有自動變量(局部變量)的匿名函數。
在iOS中使用“^”來聲明一個Block。Block的內容是包含在“{}”中的,并且和C語言一樣用“;”來表示語句的結束,標準語法如下所示:
//完整語法 ^ 返回值類型 參數列表 表達式//省略返回值 ^ 參數列表 表達式//省略參數列表 ^ 返回值類型 表達式//省略返回值和參數列表 ^ 表達式從上面可以看到,Block和函數很相似,具體體現在這些方面:
我們通常使用如下形式將Block賦值給Block類型變量,示例代碼如下:
int multiplier = 7;int (^myBlock)(int) = ^(int num){ return multiplier * num; };NSLog(@"%d",myBlock(3));采用這種方式在函數參數或返回值中使用Block類型變量時,記述方式極為復雜。這時,我們可以使用typedef來解決該問題。
示例1:沒有使用typedef
- (void)loadDataFromUrl:(void(^)(NSString *))retData { }示例2:使用typedef
typedef void(^RetDataHandler)(NSString *); - (void)loadDataFromUrl:(RetDataHandler)retData {}從上面的代碼可以看到,使用typedef聲明之后,在方法中傳遞block參數時,更容易理解。
Block的強大之處是:在聲明它的范圍里,所有變量都可以為其所捕獲。下面我們來看看自動變量。
2.自動變量
從上面Block語法的介紹中,我們可以理解“帶有自動變量(局部變量)的匿名函數”中的匿名函數。那么“帶有自動變量(局部變量)”是什么呢?這個在Block中表現為“截獲自動變量值”。示例如下:
int iCode = 10; NSString *strName = @"Tom";void (^myBlock)(void) = ^{// 結果:My name is Tom,my code is 10NSLog(@"My name is %@,my code is %d", strName, iCode); };iCode = 20; strName = @"Jim";myBlock(); // 結果:My name is Jim,my code is 20 NSLog(@"My name is %@,my code is %d", strName, iCode);從代碼中可以看到,Block表達式截獲所使用的自動變量iCode和strName的值,即保存該自動變量的瞬間值。因為Block表達式保存了自動變量的值,所以在執行Block語法后,即使改寫Block中所用的自動變量的值也不會影響Block執行時自動變量的值,這就是自動變量值的截獲。
如果我們想在Block中修改截獲的自動變量值,會有什么結果?咱們做個嘗試:?
從上面可以看到,該源代碼會產生編譯錯誤。若想在Block語法的表達式中將值賦給在Block語法外聲明的自動變量,需要在該自動變量上附加__block說明符,示例如下:
__block NSString *strName = @"Tom";void (^myBlock)(void) = ^{strName = @"Sky"; }; strName = @"Jim";myBlock(); // 結果:My name is Sky NSLog(@"My name is %@",strName);需要說明的是,對于截獲的自動變量,調用變更該對象的方法是沒有問題的:即賦值給截獲的自動變量會產生編譯錯誤,但使用截獲的自動變量的值卻不會有任何問題。
3.如何在代碼中創建Block?
3.1不帶參數和返回值的block
- (void)testBlockOne {void (^myBlock)(void) = ^{NSLog(@"Hello Block One");};NSLog(@"%@",myBlock);myBlock(); }3.2帶參數的block
- (void)testBlockTwo{void (^myBlock)(NSString *) = ^(NSString *str){NSLog(@"Hello Block %@",str);};NSLog(@"%@",myBlock);myBlock(@"ligf"); }3.3帶參數和返回值的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實現頁面傳值
4.1先用傳統的Delegate來進行示例
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調用:
DownloadByDelegate *downloadByDelegate = [[DownloadByDelegate alloc] init]; [downloadByDelegate fetchUrlData];4.2再看看用Block的實現
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調用:?
DownloadByBlock *downloadByBlock = [[DownloadByBlock alloc] initWithUrl:[NSURL URLWithString:@"http://www.baidu.com"]]; [downloadByBlock startWithCompletionHandler:^ (NSString *data, NSError *error){NSLog(@"%@",data); }];從上面的代碼可以明顯看到,使用Block方式,代碼的可讀性更高,使用也更加的方便。
5.Block存儲域
先看一個示例:
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在內存中的位置可以分為三種類型:__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__。
- __NSGlobalBlock__:與全局變量一樣,該類對象存儲在程序的數據區域(.data區)中;
- __NSStackBlock__:從名稱中可以看到含有“Stack”,即該類對象存儲在棧上;位于棧上的Block對象,函數返回后Block將無效,變成野指針;
- __NSMallocBlock__:該類對象由malloc函數分配的內存塊(堆)中。
其中在全局區域和堆里面存儲的對象是相對安全的,但是在棧區里面的變量是危險的,有可能造成程序的崩潰,因此在iOS中如果使用block的成員變量或者屬性時,需要將其copy到堆內存中。
上面的例子中,myBlockOne和myBlockTwo的區別在于:myBlockOne沒有使用Block以外的任何外部變量,Block不需要建立局部變量值的快照,這使myBlockOne與函數沒有任何區別。myBlockTwo與myBlockOne唯一不同是的使用了局部變量base,在定義(注意是定義,不是運行)myBlockTwo時,局部變量base當前值被截獲到棧上,作為常量供Block使用。執行下面代碼,結果是103,而不是203。
int base = 100; int (^myBlockTwo)(int,int) = ^ (int a, int b) {return base + a + b; }; base = 200; NSLog(@“%d", myBlockTwo(1, 2));我們再看一段代碼,大家思考一下這段代碼有沒有問題?
int base = 100;void(^myBlock)();if (YES){myBlock = ^{NSLog(@"This is ture,%d", base);};}else{myBlock = ^{NSLog(@"This is false,%d", base);};}myBlock();表面上看,和我們以前給變量賦值的語句沒什么太大的差異,那么是不是沒有問題呢?答案是NO。在定義這個塊的時候,其所占的內存區域是分配在棧中的,塊只在定義它的那個范圍內有效,也就是說這個塊只在對應的if或else語句范圍內有效。當離開了這個范圍之后,編譯器有可能把分配給塊的內存覆寫掉。這樣運行的時候,若編譯器未覆寫待執行的塊,則程序照常運行;若覆寫,則程序崩潰。
5.1__NSGlobalBlock__的實現
我們先寫一段生成__NSGlobalBlock__的代碼:
#include <stdio.h> void (^gofBlock)(void) = ^{printf("Hello, Gof"); }; int main(int argc, char * argv[]) {gofBlock();return 0; }對上面的代碼使用“clang -rewrite-objc main.m”進行編譯,生成后的代碼整理之后如下:
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; }我們將源代碼分成幾個部分來逐步理解。
第一部分:源碼中的Block語法
^{printf("Hello, Gof");};可以看到,變換后的代碼中也含有相同的表達式:
static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) {printf("Hello, Gof"); }從代碼可以看到,通過Blocks使用的匿名函數,實際上被作為簡單的C語言函數來處理。該函數名根據Block語法所屬的函數名和該Block語法在函數出現的順序值(這里為0)來命名。函數的參數__cself為指向__gofBlock_block_impl_0結構體的指針。
第二部分:__gofBlock_block_impl_0結構體?
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;} };我們先不看構造函數,__gofBlock_block_impl_0包含兩個成員變量: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; }從這兩個結構體的聲明,可以知道:impl包含某些標志、所需區域、函數指針等信息;Desc包含所需區域、Block大小信息。
接下來我們看看__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);繼續看__gofBlock_block_impl_0構造函數的兩個參數:
通過參數的配置,我們來看看__gofBlock_block_impl_0結構體的初始化:
isa = &_NSConcreteGlobalBlock;Flags = 0;
Reserved = 0;
FuncPtr = __gofBlock_block_func_0;
Desc = &__gofBlock_block_desc_0_DATA;
關于_NSConcreteGlobalBlock,我們可以看看RunTime之類與對象。實際上,__gofBlock_block_impl_0結構體相當于基于objc_object結構體的OC類對象的結構體。對其中的isa成員變量初始化,_NSConcreteGlobalBlock相當于objc_class結構體實例。在將Block作為OC的對象處理時,關于該類的信息放置于_NSConcreteGlobalBlock中。
第三部分:Block的調用。
gofBlock();變換后的代碼:
((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);去掉轉換部分:
(*gofBlock->impl.FuncPtr)(gofBlock);這實際上就是使用函數指針調用函數__gofBlock_block_func_0。另外,我們也可以看到,__gofBlock_block_func_0函數的參數__cself指向Block值。
總結一下:
- block 實際是一個對象,它主要由 一個 impl 和 一個 Desc 組成。
- impl.FuncPtr是實際的函數指針,在這里它指向 __gofBlock_block_func_0。
- Desc?用于描述當前這個 block 的附加信息的,包括結構體的大小,需要 capture 和 dispose 的變量列表等。結構體大小需要保存是因為,每個 block 因為會 capture 一些變量,這些變量會加到 __gofBlock_block_impl_0 這個結構體中,使其體積變大。
5.2__NSStackBlock__的實現
先看代碼:
#include <stdio.h>int main(int argc, char * argv[]) {int a = 18;void (^gofBlock)(void) = ^{printf("I have %d ages", a);};gofBlock();return 0; }對上面的代碼使用“clang -rewrite-objc main.m”進行編譯,生成后的代碼整理之后如下:
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; }這和上面轉換的源代碼有一點點差異。
首先,Block語法表達式中的自動變量被作為成員變量追加到了__main_block_impl_0結構體中。
其次,在調用__main_block_impl_0構造函數初始化的時候,對由自動變量追加的成員變量進行了初始化。
void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));通過參數的配置,__main_block_impl_0結構體構造函數的初始化:
isa = &_NSConcreteStackBlock; Flags = 0; Reserved = 0; FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA; a = 18;再次,Block匿名函數的實現:
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結構體實例的成員變量上的自動變量a,該變量在Block語法表達式之前被聲明定義。
總結一下:
- isa 指向 _NSConcreteStackBlock,說明這是一個分配在棧上的實例。
- main_block_impl_0 中增加了一個變量 a,在 block 中引用的變量 a 實際是在申明 block 時,被復制到?main_block_impl_0 結構體中的那個變量 a。因為這樣,我們就能理解,在 block 內部修改變量 a 的內容,不會影響外部的實際變量 a。
- main_block_impl_0 中由于增加了一個變量 a,所以結構體的大小變大了,該結構體大小被寫在了?main_block_desc_0 中。
現在我們修改一下上面的代碼,在變量前面增加 __block 關鍵字:
#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”進行編譯:
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; }從編譯之后的源代碼可以看到,只加了一個__block關鍵字,源碼數量大大增加。
首先,我們看看這句:?
__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};去掉類型轉換:
__Block_byref_a_0 a = {0,&a,0, sizeof(__Block_byref_a_0), 18 };從源碼可以看到,這個__block變量變成了__Block_byref_a_0結構體類型的自動變量。
接下來,我們看看Block匿名函數的實現:
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結構體實例的成員變量__forwarding持有指向該實例自身的指針。通過成員變量__forwarding訪問成員變量a。
總結一下:
- 源碼中增加一個名為 __Block_byref_a_0?的結構體,用來保存我們要 capture 并且修改的變量 a。
- main_block_impl_0 中引用的是?__Block_byref_a_0?的結構體指針,這樣就可以達到修改外部變量的作用。
- __Block_byref_a_0?結構體中帶有 isa,說明它也是一個對象。
- 我們需要負責?__Block_byref_a_0?結構體相關的內存管理,所以?main_block_desc_0 中增加了 copy 和 dispose 函數指針,對于在調用前后修改相應變量的引用計數。
5.3__NSMallocBlock__的實現
__NSMallocBlock__類型的 block 通常不會在源碼中直接出現,因為默認它是當一個 block 被 copy 的時候,才會將這個 block 復制到堆中。以下是一個 block 被 copy 時的示例代碼 (來自?這里),可以看到,在第 8 步,目標的 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中的對象的copy、retain、release不同,Block對象:
- Block_copy與copy等效,Block_release與release等效;
- 對Block不管是retain、copy、release都不會改變引用計數retainCount,retainCount始終是1;
- NSGlobalBlock:retain、copy、release操作都無效,因為全局塊絕不可能為系統所回收。這種塊實際上相當于單例;
- NSStackBlock:retain、release操作無效,必須注意的是,NSStackBlock在函數返回后,Block內存將被回收。即使retain也沒用。容易犯的錯誤是[[mutableAarry addObject:stackBlock],在函數出棧后,從mutableAarry中取到的stackBlock已經被回收,變成了野指針。正確的做法是先將stackBlock copy到堆上,copy之后生成新的NSMallocBlock類型對象,然后返回: [[myBlock copy] autorelease]。
- NSMallocBlock支持retain、release,雖然retainCount始終是1,但內存管理器中仍然會增加、減少計數。copy之后不會生成新的對象,只是增加了一次引用,類似retain;
- 盡量不要對Block使用retain操作,原因是并沒有Block_retain()這樣的函數,而且objc里面的retain消息發送給block對象后,其內部實現是什么都不做。
5.5Block對外部變量的存取管理
5.5.1基本數據類型
- 1、局部變量。局部變量,在Block中只讀。Block定義時截獲變量的值,在Block中作為常量使用,所以即使變量的值在Block外改變,也不影響它在Block中的值。
- 2、全局變量或靜態變量。在內存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時截獲的值。
- 3、__block修飾的變量。被__block修飾的變量稱作Block變量。 基本類型的Block變量等效于全局變量、或靜態變量。
注意:Block被另一個Block使用時,另一個Block被copy到堆上時,被使用的Block也會被copy。但作為參數的Block是不會發生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>// 和上面一樣,說明作為參數傳遞時,并不會發生copyNSLog(@"blockParam:%@", blockParam);void (^blockTwo)(GofBlock) = ^ (GofBlock gofBlock) {// 第一次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 第二次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 無論blockTwo在堆上還是棧上,作為參數的Block不會發生copy。NSLog(@"gofBlock:%@", gofBlock);// 第一次:blockParam:<__NSStackBlock__: 0x7fff52191248>// 第二次:blockParam:<__NSMallocBlock__: 0x618000241500>// 當blockTwo copy到堆上時,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對象
不同于基本類型,Block會引起OC對象的引用計數變化。這里對static、global、instance、block變量非arc下的情況進行分析。還是先看示例:
.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; 靜態變量:1; 實例變量:1; 臨時變量:2; block變量:1NSLog(@"全局變量:%lu; 靜態變量:%lu; 實例變量:%lu; 臨時變量:%lu; block變量:%lu", (unsigned long)[_globalObj retainCount], (unsigned long)[_staticObj retainCount], (unsigned long)[_instanceObj retainCount], (unsigned long)[localObj retainCount], (unsigned long)[blockObj retainCount]); }根據上面的結果,我們來分析一下:
- 全局變量和靜態變量在內存中的位置是確定的,所以Block copy時不會retain對象。
- 實例變量在Block copy時也沒有直接retain實例變量對象本身,但會retain self。所以在Block中可以直接讀寫_instanceObj變量。
- 臨時變量在Block copy時,系統自動retain對象,增加其引用計數。
- block變量在Block copy時也不會retain。
6.參考資料
- Objective-C Blocks Quiz
- 談Objective-C block的實現
- A look inside blocks: Episode 3 (Block_copy)
- GCC,LLVM,Clang編譯器對比
?
轉載于:https://www.cnblogs.com/LeeGof/p/6755907.html
總結
以上是生活随笔為你收集整理的Objective-C中的Block的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 远程桌面连接错误:由于安全设置错误,客户
- 下一篇: BZOJ4855 : [Jsoi2016