13 - block的認識和使用

OC底層原理探索文檔匯總

主要內(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)系。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容