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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Block相关内容梳理

發(fā)布時(shí)間:2025/3/21 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Block相关内容梳理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

什么是block

Block是將函數(shù)及其上下文封裝起來(lái)的對(duì)象

源碼分析

編譯器是如何實(shí)現(xiàn)block的?

新建Objective-C文件命名為MyClass,在.m文件中實(shí)現(xiàn)如下代碼:

#import "MyClass.h" @implementation MyClass - (void)block{int b = 10;int (^MyBlock)(int parames);MyBlock = ^int(int a){return a*b;};MyBlock(2); } @end

使用命令行,在MyClass.m文件目錄下,運(yùn)行命令

clang -rewrite-objc MyClass.m

clang 提供一個(gè)命令,可以將 Objetive-C 的源碼改寫成 c 語(yǔ)言的

在當(dāng)前目錄下可以看到,clang輸出了一個(gè)MyClass.cpp的文件。
關(guān)鍵代碼如下:

static void _I_MyClass_block(MyClass * self, SEL _cmd) {int b = 10;int (*MyBlock)(int parames);MyBlock = ((int (*)(int))&__MyClass__block_block_impl_0((void *)__MyClass__block_block_func_0, &__MyClass__block_block_desc_0_DATA, b));((int (*)(__block_impl *, int))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock, 2); }struct __MyClass__block_block_impl_0 {struct __block_impl impl;struct __MyClass__block_block_desc_0* Desc;int b;__MyClass__block_block_impl_0(void *fp, struct __MyClass__block_block_desc_0 *desc, int _b, int flags=0) : b(_b) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr; //函數(shù)指針 };static int __MyClass__block_block_func_0(struct __MyClass__block_block_impl_0 *__cself, int a) {int b = __cself->b; // bound by copyreturn a*b; }

__MyClass__block_block_impl_0就是該block的實(shí)現(xiàn)。

什么是block的調(diào)用

block調(diào)用即是函數(shù)的調(diào)用。

截獲變量

capture過(guò)來(lái)的變量

int b = 10;int (^MyBlock)(int parames);MyBlock = ^int(int a){return a*b;};b = 100;NSLog(@"看一下結(jié)果%d",MyBlock(2));

輸出結(jié)果 20
截獲變量要看是對(duì)什么樣的變量進(jìn)行截獲
有局部變量(基本數(shù)據(jù)類型、對(duì)象類型)、全局變量、靜態(tài)局部變量、靜態(tài)全局變量。
截獲變量的特性是什么?
對(duì)于基本數(shù)據(jù)類型的局部變量截獲其值。
對(duì)于對(duì)象類型的局部變量連同所有權(quán)修飾符一起截獲。
以指針形式截獲靜態(tài)局部變量。
不截獲全局變量和靜態(tài)全局變量

源碼分析

看下面代碼

#import "MyClass.h"@implementation MyClass // 全局變量 int global_var = 4; // 靜態(tài)全局變量 static int static_global_var = 5;- (void)myBlockMethod{// 基本數(shù)據(jù)類型的局部變量int b = 10;// 靜態(tài)局部變量static int static_var = 3;// 對(duì)象類型的局部變量 所有權(quán)修飾符__unsafe_unretained __strong__unsafe_unretained id unsafe_obj = nil;__strong id strong_obj = nil;void(^MyBlock)(void);MyBlock = ^{NSLog(@"局部變量<基本數(shù)據(jù)類型> var=%d",b);NSLog(@"局部變量<靜態(tài)變量> static_var=%d",static_var);NSLog(@"局部變量<__unsafe_unretained 修飾的對(duì)象類型> var=%@",unsafe_obj);NSLog(@"局部變量 __strong 修飾的對(duì)象類型> var=%@",strong_obj);NSLog(@"全局變量 %d",global_var);NSLog(@"靜態(tài)全局變量 %d",static_global_var);};MyBlock(); }@end

運(yùn)行命令 clang -rewrite-objc -fobjc-arc MyClass.m
-fobjc-arc 支持ARC -fno-objc-arc 不支持ARC
關(guān)鍵代碼如下:

int global_var = 4; static int static_global_var = 5;struct __MyClass__myBlockMethod_block_impl_0 {struct __block_impl impl;struct __MyClass__myBlockMethod_block_desc_0* Desc;int b;int *static_var;__unsafe_unretained id unsafe_obj;__strong id strong_obj;__MyClass__myBlockMethod_block_impl_0(void *fp, struct __MyClass__myBlockMethod_block_desc_0 *desc, int _b, int *_static_var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int flags=0) : b(_b), static_var(_static_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };static void _I_MyClass_myBlockMethod(MyClass * self, SEL _cmd) {int b = 10;static int static_var = 3;__attribute__((objc_ownership(none))) id unsafe_obj = __null;__attribute__((objc_ownership(strong))) id strong_obj = __null;void(*MyBlock)(void);MyBlock = ((void (*)())&__MyClass__myBlockMethod_block_impl_0((void *)__MyClass__myBlockMethod_block_func_0, &__MyClass__myBlockMethod_block_desc_0_DATA, b, &static_var, unsafe_obj, strong_obj, 570425344));((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock); }

我們看下下面這段代碼的運(yùn)行結(jié)果是什么

int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return a*b;};b = 4;NSLog(@"結(jié)果: %d",MyBlock(7));

結(jié)果如下:結(jié)果: 14
為什么是14而不是28,因?yàn)閷?duì)于基本數(shù)據(jù)類型的局部變量截獲其值,也就是2.

然后我們?cè)?局部變量前面加上static

static int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return a*b;};b = 4;NSLog(@"結(jié)果: %d",MyBlock(7));

結(jié)果如下:結(jié)果: 28

為什么是28呢,因?yàn)閷?duì)靜態(tài)局部變量是以指針形式截獲的。int b = 4;時(shí)地址上值已經(jīng)被修改成4了

如果我們想在block內(nèi)部對(duì)b的值進(jìn)行修改,我們就要用到__block.

__block修飾符

我們什么時(shí)候要用到__block修飾符呢?
一般情況下,對(duì)被截獲變量進(jìn)行賦值操作需添加__block修飾符。

需要注意的是 賦值 ≠ 使用

下面看一些關(guān)于__block的一些筆試題

NSMutableArray *arr = [[NSMutableArray alloc] init];void (^MyBlock)(NSString *str);MyBlock = ^(NSString *str){[arr addObject:str];};MyBlock(@"客戶甲");NSLog(@"客戶列表 %@",arr);

arr里的值是

description of arr: <__NSArrayM 0x6000028de490>( 客戶甲 )

這里不需要__block,因?yàn)檫@里只是使用arr,并沒(méi)有賦值。
看下面這種情況

__block NSMutableArray *arr = nil;void (^MyBlock)(NSString *str);MyBlock = ^(NSString *str){arr = [[NSMutableArray alloc] init];[arr addObject:str];};MyBlock(@"客戶甲");

這里才是對(duì)arr的賦值,所以這時(shí)候需要使用__block。
對(duì)變量進(jìn)行賦值具體有什么特點(diǎn)?
對(duì)變量進(jìn)行賦值時(shí),局部變量,不管是基本類型的局部變量還是對(duì)象類型的局部變量,都需要使用__block修飾符。
靜態(tài)類型的局部變量本身就是以指針類型截獲的,所以不需要。
全局變量和靜態(tài)全局變量不截獲,所以也不需要。
思考一下下面的代碼結(jié)果是什么

__block int b = 2;int (^MyBlock)(int param);MyBlock = ^int(int a){return a*b;};b = 4;NSLog(@"結(jié)果: %d",MyBlock(5));

結(jié)果是10還是20呢?結(jié)果: 20
好奇怪為什么不是10呢?
__block修飾符所起的作用來(lái)回答這個(gè)問(wèn)題。
__block修飾的變量最后變成了對(duì)象。使用clang命令查看一下源碼:

struct __Block_byref_b_0 {void *__isa; __Block_byref_b_0 *__forwarding;//指向同類型的指針 __forwardingint __flags;int __size;int b; };struct __MyClass__myBlockMethod_block_impl_0 {struct __block_impl impl;struct __MyClass__myBlockMethod_block_desc_0* Desc;__Block_byref_b_0 *b; // by ref__MyClass__myBlockMethod_block_impl_0(void *fp, struct __MyClass__myBlockMethod_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} };

b變成了struct __Block_byref_b_0對(duì)象。而對(duì)b = 4;進(jìn)行賦值實(shí)際上就是(b.__forwarding->b) = 4;
該段代碼的運(yùn)行是在棧上進(jìn)行的,棧上有一個(gè)__block修飾的變量,且有一個(gè).__forwarding的指針指向它。

Block的三種類型

在Objective-C中Block一共有三種類型:

  • _NSConcreteGlobalBlock 全局的靜態(tài)block,不會(huì)訪問(wèn)任何外部變量;

  • _NSConcreteStackBlock 保存在棧中的block,當(dāng)函數(shù)返回時(shí)被銷毀;

  • _NSConcreteMallocBlock 保存在堆中的block,當(dāng)引用計(jì)數(shù)器為0時(shí)被銷毀

Block的內(nèi)存管理

內(nèi)存從高到低依次分為棧區(qū)、堆區(qū)、全局區(qū)、常量區(qū)、代碼區(qū)。


需要知道的是全局的靜態(tài)block保存在已初始化的全局靜態(tài)區(qū)。

Block的copy操作

我們?cè)诤螘r(shí)需要對(duì)block進(jìn)行copy操作?那么就要知道copy的效果是什么樣的。

Block類別源Copy結(jié)果
_NSConcreteGlobalBlock數(shù)據(jù)區(qū)什么也不做
_NSConcreteStackBlock
_NSConcreteMallocBlock增加引用計(jì)數(shù)

我們?cè)谡旧嫌幸粋€(gè)Block,在這個(gè)block當(dāng)中,有一個(gè)__block修飾的變量,當(dāng)我們對(duì)棧上的Block進(jìn)行copy的時(shí)候,會(huì)在堆上生成一個(gè)和棧上一樣對(duì)應(yīng)的Block和__block變量,分占兩塊空間,左側(cè)是在棧上,右側(cè)是在堆上,隨著變量作用域的結(jié)束,站上的Block被釋放,但是堆上對(duì)應(yīng)的Block和__block變量仍然存在。

請(qǐng)關(guān)注下面問(wèn)題:
當(dāng)我們對(duì)棧上的Block進(jìn)行copy操作之后,假如說(shuō)是在MRC環(huán)境下,是否會(huì)引起內(nèi)存泄漏;答案是肯定。解析:假如說(shuō)我們進(jìn)行了copy操作之后,同時(shí)堆上面的這個(gè)Block沒(méi)有其他成員變量指向它,那么和我們?nèi)lloc出來(lái)一個(gè)對(duì)象而沒(méi)有去調(diào)用release操作是一樣的。會(huì)產(chǎn)生內(nèi)存泄漏。

究竟對(duì)棧上的__block進(jìn)行copy操作,之后發(fā)生了什么呢?


我們?cè)跅I嫌幸粋€(gè)__block變量,__block下有一個(gè)__forwarding指針,棧上的__forwarding指針是指向它自身的;當(dāng)對(duì)棧上的__block變量進(jìn)行copy之后,實(shí)際在堆上面會(huì)產(chǎn)生一個(gè)__block變量,和棧上的是完全一致的,只不過(guò)分占兩塊內(nèi)存空間。且棧上的__forwarding指針實(shí)際上指向的是堆上面的__block變量,堆上的__forwarding指針指向的是其自身。
(b.__forwarding->b) = 4;
所以,當(dāng)我們對(duì)棧上__block修飾的變量b做出修改,假如說(shuō)我們已經(jīng)對(duì)b做了copy操作,那么我們修改的不是棧上面的__block變量b對(duì)應(yīng)的值,而是通過(guò)棧上的__forwarding指針,找到它指向的堆上的__block變量b對(duì)應(yīng)的值。

__forwarding指針是用來(lái)干什么的?
棧上的__forwarding是指向自身的,那么為什么需要這個(gè)指針呢?那么換句話說(shuō),我們沒(méi)有這個(gè)指針的情況下,可以直接通過(guò)對(duì)成員變量的訪問(wèn)來(lái)對(duì)b進(jìn)行賦值,那__forwading指針是不是多余了?從上面我們可以知道,當(dāng)對(duì)棧上的__block變量進(jìn)行copy之后,棧上的__forwading指針實(shí)際上指向堆上面的__block變量。并不是多余的。總結(jié),不論在任何內(nèi)存位置,我們都可以通過(guò)__forwading指針順利的訪問(wèn)同一個(gè)__block變量。

Block的循環(huán)引用

看代碼一

_arr = [NSMutableArray arrayWithObject:@"張三"];MyBlock = ^NSString*(NSString*str){return [NSString stringWithFormat:@"你好 %@",_arr[0]];};MyBlock(@"5");

代碼會(huì)報(bào)出警告??
Capturing ‘self’ strongly in this block is likely to lead to a retain cycle
在這個(gè)block中,有一個(gè)對(duì)self的循環(huán)引用 。MyBlock和_arr都是當(dāng)前對(duì)象self的成員變量,對(duì)_array一般用strong關(guān)鍵字來(lái)修飾,對(duì)block一般用copy關(guān)鍵字來(lái)修飾。當(dāng)前對(duì)象通過(guò)copy屬性關(guān)鍵字聲明的MyBlock,所以當(dāng)前對(duì)象對(duì)MyBlock有一個(gè)強(qiáng)引用在的。而這個(gè)Block的表達(dá)式中又使用到了當(dāng)前對(duì)象的_arr成員變量,截獲變量對(duì)于對(duì)象類型的局部變量是連同其所有權(quán)修飾符一起截獲的。_arr是通過(guò)__strong修飾的,所以在block中就有了一個(gè)strongly類型的指針指向原來(lái)的對(duì)象

如何避免這個(gè)問(wèn)題?修正代碼:

_arr = [NSMutableArray arrayWithObject:@"張三"];__weak NSArray *Weak_Array = _arr;MyBlock = ^NSString*(NSString*str){return [NSString stringWithFormat:@"你好 %@",Weak_Array[0]];};MyBlock(@"5");

采用避免產(chǎn)生循環(huán)引用的方式來(lái)解除它的循環(huán)引用。

那么為什么通過(guò)__weak修飾的成員變量就可以解除循環(huán)引用呢?因?yàn)閎lock對(duì)其所截獲的變量,如果是對(duì)象類型的,是連同其所有權(quán)修飾符一起截獲。外部是__weak修飾符,所以里面也是__weak。

看代碼二

__block MyClass* blockSelf = self;MyBlock = ^int(int num){// b = 5return num*blockSelf.b;};MyBlock(2);

在這個(gè)棧上面,有一個(gè)__block修飾的變量來(lái)指向self,即對(duì)象本身,同時(shí)當(dāng)前對(duì)象的成員變量MyBlock創(chuàng)建時(shí),表達(dá)式中有使用到self.b,這里用的是blockSelf,而不是直接使用當(dāng)前對(duì)象。
這段代碼在MRC下,不會(huì)產(chǎn)生循環(huán)引用。在ARC下,會(huì)產(chǎn)生循環(huán)引用,引起內(nèi)存泄漏。
代碼中存在一個(gè)大環(huán)引用:

self對(duì)象持有Block,Block中又使用了__block變量,而這個(gè)__block 變量又指向原有的這個(gè)對(duì)象。這種循環(huán)引用就是大環(huán)引用。
怎么解除這個(gè)循環(huán)引用呢?
我們一般使用斷環(huán)的方式來(lái)解除這種循環(huán)引用。斷開__block變量對(duì)原對(duì)象的持有,就可以規(guī)避循環(huán)引用。

修改后的代碼:

__block MyClass* blockSelf = self;MyBlock = ^int(int num){// b = 5int result = num * blockSelf.b;blockSelf = nil;return result;};MyBlock(2);

對(duì)blockSelf進(jìn)行一個(gè)nil賦值,就可以規(guī)避這個(gè)循環(huán)引用。但是這個(gè)解決方案有一個(gè)弊端,假如我們很長(zhǎng)一段時(shí)間,或者一直都沒(méi)調(diào)用這個(gè)block的話,那么這個(gè)循環(huán)引用的環(huán)就會(huì)一直存在。

小結(jié)

什么是Block?
Block是對(duì)函數(shù)及其上下文封裝起來(lái)的對(duì)象。

為什么Block會(huì)產(chǎn)生循環(huán)引用?
第一方面,自循環(huán)引用:如果說(shuō)當(dāng)前Block對(duì)當(dāng)前對(duì)象的某一成員變量進(jìn)行截獲,那么這個(gè)Block會(huì)對(duì)對(duì)應(yīng)變量有一個(gè)強(qiáng)引用,而當(dāng)前對(duì)象對(duì)Block也有一個(gè)強(qiáng)引用,就產(chǎn)生了一個(gè)自循環(huán)引用方式的循環(huán)引用問(wèn)題,我們通過(guò)聲明一個(gè)__weak修飾的變量,來(lái)消除循環(huán)引用。
第二方面,我們定義一個(gè)__block修飾的變量,也會(huì)產(chǎn)生循環(huán)引用,但是是區(qū)別場(chǎng)景的。在ARC下會(huì)產(chǎn)生循環(huán)引用,有內(nèi)存泄漏;在MRC下是沒(méi)有問(wèn)題的。我們可以在ARC下通過(guò)斷環(huán)的方式避免循環(huán)引用,但是有一個(gè)弊端,如果block一直得不到調(diào)用,那么循環(huán)引用就無(wú)法解除,環(huán)就一直存在。

怎么理解Block截獲變量的特性?
對(duì)于基本數(shù)據(jù)類型的局部變量,是對(duì)值進(jìn)行截獲。
對(duì)于對(duì)象類型的局部變量,是連同其所有權(quán)修飾符一起截獲;
對(duì)于靜態(tài)類型的局部變量,是以指針形式截獲的;
對(duì)全局變量和靜態(tài)全局變量不截獲。

你都遇到過(guò)哪些循環(huán)引用?你有事怎樣解決的?
我們會(huì)遇到Block所引起的循環(huán)引用,比如
1、Block所捕獲的成員變量,也是當(dāng)前對(duì)象的成員變量,而block也是當(dāng)前對(duì)象的成員變量,就會(huì)造成自循環(huán)的循環(huán)引用。
解決:通過(guò)加__weak修飾符開解除這種自循環(huán)引用;
2、__block在ARC下也會(huì)產(chǎn)生循環(huán)引用,采用斷環(huán)的方式來(lái)避免循環(huán)引用。

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的Block相关内容梳理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。