日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Objective-C中的Block

發布時間:2025/3/18 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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_func_0是由Block語法轉換的C語言函數指針;
  • __gofBlock_block_desc_0_DATA是作為靜態全局變量初始化的__gofBlock_block_desc_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]。
    typedef NSString * (^retBlock)(void);- (void)test {NSMutableArray *arr = [NSMutableArray array];retBlock block = [[self stackBlock] copy];for (int i = 0; i < 5; i++){[arr addObject:block];} }- (retBlock)stackBlock {int ret = 10;NSString * (^myBlock)(void) = ^{return [NSString stringWithFormat:@"Hello,%d",ret];};return [[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中的值。
    int base = 100;int (^myBlock)(int, int) = ^ (int a, int b) {return base + a + b; };base = 200;// base:200 myBlock:103 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
    • 2、全局變量或靜態變量。在內存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時截獲的值。
    static int base = 100;int (^myBlock)(int, int) = ^ (int a, int b) {return base + a + b; };base = 200;// base:200 myBlock:203 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
    • 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的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。