主要內(nèi)容:
1、block的認識
2、block的基本使用
3、變量的捕獲
4、循環(huán)引用問題
1、block的認識
1.1 定義
Block是一個里面存儲了指向定義block時的代碼塊的函數(shù)指針,以及block外部上下文變量信息的結(jié)構(gòu)體,簡單說就是:帶有自動變量的匿名函數(shù)。
我們通常使用block傳遞數(shù)據(jù)。
block是一個帶自動變量的匿名函數(shù),本質(zhì)是一個函數(shù)。
但是在使用上更偏向于是一個引用類型。因為block可以作為參數(shù)、可以作為返回值,還可以作為一個屬性供使用。
2 block的基本使用
block的使用有三種:1)作為參數(shù);2)作為屬性;3)作為返回值
我們在使用時既可以把他當做一個類即可,但是這個類只有一個函數(shù)。其他什么都沒有。
1.3.1 block的定義和調(diào)用
定義: //返回值類型 (^block的變量名)(參數(shù)類型)
申請空間: ^返回值類型(參數(shù)列表)
//定義一個block
typedef void (^addBlock2)(int num1,int num2);
//block的定義和調(diào)用
- (void)blockTest{
//1:定義block
//返回值類型 (^block的變量名)(參數(shù)類型)
//2:申請空間
//^返回值類型(參數(shù)列表)
void (^testBlock)(int num1,int num2) = ^void(int num1,int num2){
NSLog(@"num1+num2=%d",num1+num2);
};
addBlock2 block2= ^void(int num1,int num2){
NSLog(@"num1+num2=%d",num1+num2);
};
//3:調(diào)用
testBlock(1,2);
block2(3,4);
}
說明:
- 先定義一個block,之后實現(xiàn)block,最后調(diào)用block
- 定義block可以看做是定義一個函數(shù)指針,block實現(xiàn)就是函數(shù)的實現(xiàn)
- 之后通過函數(shù)指針直接調(diào)用block。
簡寫:
- 如果返回值類型為void,則在申請空間時,可以省略不寫
- 如果沒有參數(shù),則在申請空間時,可以省略不寫
- 在申明變量時,可以只寫參數(shù)類型,不寫參數(shù)
- 如果有返回值,在申請空間時也可以不寫返回值類型,但是要在代碼段中寫上return,系統(tǒng)會根據(jù)我們return的類型自動判斷返回值類型
1.3.2 block作為屬性
設置屬性
//定義一個block
typedef int (^myBlock)(int num1,int num2);
/*
block作為屬性有兩種,一種是先定義再設置,一種是直接定義到屬性中
*/
@interface WYBlock : NSObject
@property (nonatomic,strong) NSString *name;
//作為屬性,就和實例變量完全一樣,block要使用copy修飾(雖然不用也行)
@property (nonatomic,copy) myBlock block1;
//也可以這么寫,將block定義直接放到這里
@property (nonatomic,copy) void (^myBlock2)(int num1,int num2);
使用屬性
- (void)test{
addBlock2 block = [self blockTest3];
block(10,10);
}
//block作為屬性
- (void)blockTest4{
WYBlock *block = [[WYBlock alloc] init];
//定義
block.block1 = ^int(int num1, int num2) {
NSLog(@"num1+num2=%d",num1+num2);
return num1+num2;
};
block.myBlock2 = ^(int num1, int num2) {
NSLog(@"num1*num2=%d",num1*num2);
};
//調(diào)用
block.block1(1, 2);
block.myBlock2(3, 5);
}
說明:
- 定義屬性有兩種方式,定義和使用上也不一樣。
- 一種是先定義block,之后將block當做實例變量一樣的定義
- 還有一種是直接將block的定義作為屬性。此時的block名稱就是屬性名稱。
- 在屬性上定義的block只是一個定義,而沒有block的實現(xiàn),所以需要先實現(xiàn),才能再調(diào)用了。
1.3.3 block作為參數(shù)傳遞
block作為參數(shù)傳遞可以實現(xiàn)兩個類的數(shù)據(jù)傳遞(因為實現(xiàn)和定義在不同的類中)
方法實現(xiàn):
//block作為參數(shù),提前定義好block
- (void)sumWithblock:(myBlock) block{
NSLog(@"block的結(jié)果是%d",block(3,5));
}
//臨時定義block
- (void)sumWithblock2:(int (^)(int num1,int num2)) block{
NSLog(@"block2的結(jié)果是%d",block(3,5));
}
方法調(diào)用:
/*
1、傳遞block的實現(xiàn)
2、在被調(diào)用的方法里調(diào)用block
*/
- (void)blockTest2{
WYBlock *block = [[WYBlock alloc] init];
[block sumWithblock:^int(int num1, int num2) {
NSLog(@"num1*num2:%d",num1*num2);
return num1*num2;
}];
[block sumWithblock2:^int(int num1, int num2) {
return num1+num2;
}];
}
說明:
- 也有兩種方式,一種是直接將已定義好的block作為參數(shù)類型,一種是在作為參數(shù)類型時定義的
- 如果是第二種方式,就需要寫block名稱,反正接下來會用參數(shù)。block的名稱也沒用
- 通過這種傳參的方式就可以實現(xiàn)數(shù)據(jù)傳遞
- 我們在WYBlock類中傳入?yún)?shù),在ViewController類中使用,這樣就可以做到了數(shù)據(jù)傳遞。
1.3.4 block作為返回值
typedef void (^addBlock2)(int num1,int num2);
//block作為返回值
- (addBlock2)blockTest3{
return ^void(int num1,int num2){
NSLog(@"num1+num2=%d",num1+num2);
};
}
//調(diào)用
- (void)test{
addBlock2 block = [self blockTest3];
block(10,10);
}
說明:
- 先定義一個blockTest3方法,其返回值為addBlock2,所以在該方法return時需要返回一個block??梢袁F(xiàn)在實現(xiàn),也可以提前定義好的實現(xiàn)
- 在test中調(diào)用blockTest3,接收返回值為addBlock2類型,此時就可以直接調(diào)用了。
2 block的認識
上面我們說block其實就是一個是一個帶自動變量的匿名函數(shù),這里就進行說明。
有兩個需要考慮,一個是帶自動變量,一個是匿名函數(shù)。
2.1 block的匿名函數(shù)認識
2.1.1 先看下函數(shù)是什么樣子的
函數(shù)定義:
typedef int (*funcPtr)(int);
獲取函數(shù)指針:
//C函數(shù)實現(xiàn)
int func(int arg) {
return arg;
};
//C函數(shù)指針賦值
funcPtr ptr = *func;
函數(shù)指針調(diào)用:
int ret1 = ptr(10);
2.1.2 再看block的使用
block定義:
typedef int (^tmpBlock)(int arg);
獲取block指針:
//block指針賦值
tmpBlock block = ^(int arg){
return arg;
};
block調(diào)用
//block調(diào)用
int ret2 = block(10);
2.1.3 對比查看
經(jīng)過對比,除了函數(shù)在實現(xiàn)時有自己的名稱func,而block沒有名稱,需要直接賦給一個Block指針。這就是所謂的匿名函數(shù)。
因此block本質(zhì)就是一個匿名函數(shù)。我們定義的block其實是一個函數(shù)指針。而block的實現(xiàn)就是函數(shù)實現(xiàn)。
為了更方便放到一塊看看
//C函數(shù)實現(xiàn)
int func(int arg) {
return arg;
};
typedef int (*funcPtr)(int);
typedef int (^tmpBlock)(int arg);
/*
通過C函數(shù)和block的實現(xiàn)對比可以發(fā)現(xiàn),C函數(shù)和block的聲明定義基本一樣,只是在實現(xiàn)block時沒有名稱,而函數(shù)是有名稱的。
*/
void niminghanshu(int arg){
//C函數(shù)指針賦值
funcPtr ptr = *func;
//C函數(shù)指針調(diào)用
int ret1 = ptr(10);
//block指針賦值
tmpBlock block = ^(int arg){
return arg;
};
//block調(diào)用
int ret2 = block(10);
NSLog(@"ret1:%d---ret2:%d",ret1,ret2);
}
2.2 block的自動變量認識
上面我們看到block本質(zhì)就是一個匿名函數(shù),但是還有一個區(qū)別于函數(shù)的特性就是自動變量。
自動變量的意思是可以捕獲變量。接下來看看如何捕獲變量。
普通函數(shù)使用外界的變量,變量仍然是外界的變量,并不是自己的。而block在使用外界的變量時,會將外界變量copy自己的函數(shù)中,作為自己函數(shù)的一個變量。這就是捕獲變量。
- (void)testVariable{
__block int a = 10;
__block NSString *str = [[NSString alloc] init];
str = @"wy11";
NSLog(@"a1---%d---%p",a,&a);
NSLog(@"str1--%@--%p",str,str);
a = 100;
NSLog(@"a2---%d---%p",a,&a);
NSLog(@"str2--%@--%p",str,str);
void (^block)(void) = ^{
a = a+1;
str = @"wy22";
NSLog(@"a3--%d--%p",a,&a);
NSLog(@"str3--%@--%p",str,str);
};
block();
NSLog(@"a4--%d---%p",a,&a);
NSLog(@"str4--%@--%p",str,str);
}
運行結(jié)果:
2021-11-07 19:14:03.228605+0800 Block的學習[5696:1734206] a1---10---0x7ff7b9551f08
2021-11-07 19:14:03.228697+0800 Block的學習[5696:1734206] str1--wy11--0x1069ae2e8
2021-11-07 19:14:03.228762+0800 Block的學習[5696:1734206] a2---100---0x7ff7b9551f08
2021-11-07 19:14:03.228818+0800 Block的學習[5696:1734206] str2--wy11--0x1069ae2e8
2021-11-07 19:14:03.228876+0800 Block的學習[5696:1734206] a3--101--0x600002ffc338
2021-11-07 19:14:03.228933+0800 Block的學習[5696:1734206] str3--wy22--0x1069ae3a8
2021-11-07 19:14:03.229006+0800 Block的學習[5696:1734206] a4--101---0x600002ffc338
2021-11-07 19:14:03.229075+0800 Block的學習[5696:1734206] str4--wy22--0x1069ae3a8
說明:
- block修改值后會影響block修改的值
- 可以看到在block中對變量進行賦值后,變量的地址值發(fā)生了變化。說明block會捕獲變量。
2.3 block的類型
block根據(jù)所在的不同區(qū)域,可以分為三種類型,存儲在全局區(qū)的是全局block、存儲在棧的block是棧block、存儲在堆的block是對block。
2.3.1 全局block(NSGlobalBlock)
- (void)blockType{
//不使用任何數(shù)據(jù)
void (^globalBlock1)(void) = ^{
NSLog(@"quanju:%d",quanju);
};
NSLog(@"wy:globalBlock1--%@",globalBlock1);
//使用全局變量
void (^globalBlock2)(void) = ^{
NSLog(@"wy");
};
NSLog(@"wy:globalBlock2--%@",globalBlock2);
}
結(jié)果:
2021-11-07 19:26:20.011182+0800 Block的學習[6134:1745135] wy:globalBlock1--<__NSGlobalBlock__: 0x1085e9208>
2021-11-07 19:26:20.011280+0800 Block的學習[6134:1745135] wy:globalBlock2--<__NSGlobalBlock__: 0x1085e9228>
說明:
- 當一個block不使用任何數(shù)據(jù)時存儲在全局區(qū),是NSGlobalBlock
- 當一個block使用全局變量時,是NSGlobalBlock
2.3.2 堆block(NSMallocBlock)
void (^mallocBlock1)(void) = ^{
self->string = @"wy";
};
NSLog(@"wy:mallocBlock1--%@",mallocBlock1);
int a;
void (^mallocBlock2)(void) = ^{
NSLog(@"a=%d",a);
};
NSLog(@"wy:mallocBlock2--%@",mallocBlock2);
運行結(jié)果:
2021-11-07 19:38:45.370101+0800 Block的學習[6586:1757682] wy:mallocBlock1--<__NSMallocBlock__: 0x60000220f9f0>
2021-11-07 19:38:45.370194+0800 Block的學習[6586:1757682] wy:mallocBlock2--<__NSMallocBlock__: 0x600002208ed0>
2.3.3 棧block(NSStackBlock)
默認情況下block是堆block,我們可以通過__weak不對block進行強持有,就是棧block,
int b = 10;
void (^ __weak stackBlock1)(void) = ^{
NSLog(@"b=%d",b);
};
NSLog(@"wy:stackBlock1--%@",stackBlock1);
運行結(jié)果:
2021-11-07 19:38:45.370276+0800 Block的學習[6586:1757682] wy:stackBlock1--<__NSStackBlock__: 0x7ff7b9aede60>
- block直接存儲在全局區(qū),如果不使用任何數(shù)據(jù),或者只是用全局區(qū)的數(shù)據(jù),那么是全局block
- 如果block訪問局部變量或成員變量,并進行相應拷貝,此時的block是強引用,存儲在堆區(qū)。
- 如果block訪問局部變量或成員變量,lock通過__weak變成了弱引用,則block存儲在棧區(qū)。
3、循環(huán)引用問題
3.1 問題的出現(xiàn):
block中可能會出現(xiàn)循環(huán)引用:
- 當block持有self,就會產(chǎn)生循環(huán)引用,不是所有的block都會產(chǎn)生循環(huán)引用
- 因為block是在這個類的內(nèi)部,被self使用,而block又使用了self,就會相互引用,互相等待對方先釋放,造成循環(huán)引用
請看下這個代碼有沒有循環(huán)引用
/*
會出現(xiàn)循環(huán)引用
在block內(nèi)部使用self會出現(xiàn)循環(huán)引用,因為self和block相互引用
*/
- (void)circularTest2{
self.block1 = ^int(int num1, int num2) {
self.name = @"zhang";
return num1+num2;
};
self.block1(10,2);
}
說明:
- 有循環(huán)引用
- self持有block1,block中又持有self,所以導致了self和block的相互持有
請看下這個代碼有沒有循環(huán)引用
/*
不會出現(xiàn)循環(huán)引用
雖然block使用了self,但是這個block并沒有被self持有,所以不會出現(xiàn)
*/
- (void)circularTest{
[self sumWithblock:^int(int num1, int num2) {
self.name = @"zhang";
return num1+num2;
}];
}
說明:
- 沒有循環(huán)引用
- block并沒有被self持有,而是被sumWithblock持有,所以不構(gòu)成相互持有。
3.2 循環(huán)引用的解決
block的循環(huán)引用歸根結(jié)底就是斷開其中的一個持有,打破相互持有。共有四種方案可以實現(xiàn)
【方案一】:使用__weak
【方案二】:手動釋放一個引用
【方案三】:將self作為參數(shù)
【方案四】:使用NSProxy虛擬類
3.2.1 給self使用__weak
- 打破self對block的強引用,不再相互持有
- 持有的weakSelf是在一張弱引用表,而不是直接持有的self
- 所以就self不會計數(shù)+1,也就不會進行相互持有
/*
循環(huán)引用解決1: __weak弱引用self
將block持有self這一環(huán)斷開
*/
- (void)circularTest3{
__weak typeof(self) weakSelf = self;
self.block1 = ^int(int num1, int num2) {
weakSelf.name = @"zhang";
return num1+num2;
};
self.block1(10,2);
}
注意:
- 如果block內(nèi)部嵌套block,需要同時使用__weak和__strong
3.2.2 手動釋放對象
將self賦給一個變量,這樣就是強引用,之后我們在block執(zhí)行結(jié)束后主動設置為nil,也就是主動釋放掉,這樣就打破了block對self的引用
/*
循環(huán)引用解決2:在block內(nèi)將對象設置為nil
通過wyBlock作為中介,給self增加一個引用,之后將wyBlock設置為nil就可以給self減少一個引用計數(shù)了
*/
- (void)circularTest4{
__block WYBlock *wyBlock = self;
self.block1 = ^int(int num1, int num2) {
wyBlock.name = @"zhang";
wyBlock = nil;
return num1+num2;
};
self.block1(10,2);
}
- 對象需要被__block修飾,因為只有這樣才可以修改。
- 這里的block必須被調(diào)用,如果不調(diào)用,blcok永遠不會置空,這樣self和block都無法被釋放。
3.2.3 將self作為參數(shù)傳遞
/*
循環(huán)引用解決3:對象self作為參數(shù)
wyBlock的生命周期僅在block內(nèi)部,與self無關(guān)。block不持有self
*/
- (void)circularTest5{
self.block11 = ^(WYBlock *wyBlock){
wyBlock.name = @"wy";
NSLog(@"wy--%@",wyBlock.name);
};
self.block11(self);
}
說明:
- wyBlock的生命周期僅在block內(nèi)部,block結(jié)束后wyBlock也就銷毀了
- 沒有與self進行綁定,所以也就不會相互持有了
3.2.4 使用NSProxy虛基類實現(xiàn)
虛基類本質(zhì)上是一個定義了消息轉(zhuǎn)發(fā)功能的抽象類。也就是說他可以實現(xiàn)消息轉(zhuǎn)發(fā)功能。
因此我們在這里可以通過虛基類來調(diào)用,避免了self的強引用。
詳細的虛基類的認識可以查看博客:NSproxy虛基類實現(xiàn)代理和多繼承以及多態(tài)
/*
循環(huán)引用解決4:通過虛基類調(diào)用方法,不與self綁定
*/
- (void)circularTest6{
WYProxy *proxy = [WYProxy alloc];
[proxy transformObjc:self];
self.block1 = ^int(int num1, int num2) {
[proxy performSelector:@selector(eat)];
return num1+num2;
};
self.block1(10,2);
}
- (void)eat{
NSLog(@"eat");
}
運行結(jié)果:
2021-11-09 09:51:15.464526+0800 Block的學習[31084:410195] eat
說明:
- 此處使用虛基類來調(diào)用eat方法,沒有采用self來調(diào)用,所以沒有持有self
- 虛基類通過代理self可以實現(xiàn)eat方法,但是處于代理關(guān)系,不屬于持有關(guān)系。