iOS: Block的循環引用

Block的分類

首先咱們來看一下有哪幾種Block:編程

Block的分類.png
那麼這3種Block分別在哪裏體現出來呢?接下來,咱們經過代碼來看一下:

void(^block)(void) = ^{
        NSLog(@"123456");
    };
block();
NSLog(@"%@",block);
//萬物皆對象,block也不例外
//運行代碼,能夠看到打印的內容是:
//BlockDemo[34002:5702720] 123456
//BlockDemo[34002:5702720] <__NSGlobalBlock__: 0x107c8b088>
//此時這個block就是GlobalBlock
複製代碼

咱們稍微修改下上面的代碼,讓block去截獲變量的值,bash

int a = 10;
void(^block)(void) = ^{
        NSLog(@"a=%d",a);
};
block();
NSLog(@"%@",block);
//運行代碼,能夠看到打印的內容是:
//BlockDemo[34061:5705903] a=10
//BlockDemo[34061:5705903] <__NSMallocBlock__: 0x6000021ccd50>
//此時的block就是MallocBlock
複製代碼

咱們再改變一下block的打印,函數

int a = 10;
void(^block)(void) = ^{
        NSLog(@"a=%d",a);
};
block();
NSLog(@"%@",^{
        NSLog(@"a=%d",a);
});
//運行代碼,能夠看到打印的內容是:
//BlockDemo[34108:5708687] a=10
//BlockDemo[34108:5708687] <__NSStackBlock__: 0x7ffee9f71970>
//此時打印出的就是咱們看似不經常使用,不常見到,卻可能在不知不覺中使用的StackBlock
複製代碼

存放區域.png

NSGlobalBlock:最初建立一個block的時候,他是存在於5大內存區(棧區,堆區,靜態區,常量區,代碼區(全局區))的全局區,當咱們引入外部變量,block會自動捕獲變量,當前這個變量a由棧區捕獲到堆區,由於ablock必須在同一個內存空間才能夠進行相應的操做,因此block會由全局區遷移到堆區上去,因此block就存放到棧區(NSStackBlock),可是因爲棧區空間是由系統自動分配的,自動銷燬的,因此咱們再寫void(^block)(void) =這句代碼的時候其實是作了一個copy操做,將存放在棧區上的block拷貝到了堆區(NSMallocBlock)ui

Block的循環引用

首先咱們來看下什麼是正常的釋放: this

A持有B.png
那麼B何時會釋放呢? 當A調用析構函數 dealloc給B發送 release信息的時候,A不會再持有B,而後B的 retainCount會減1,當B的 retainCount=0的時候,B就會調用本身的析構函數,從而B就能夠釋放掉。
A釋放B.png
再來看一下什麼是循環引用:
循環引用.png
下面咱們用代碼來實現看一下:

@property (nonatomic, copy) ZHBlock block;
@property (nonatomic, copy) NSString *name;

self.name = @"ZH"; 
self.block = ^{
        NSLog(@"%@",self.name);
    };
self.block();
複製代碼

在這裏 self-->block-->self 形成循環引用,其實代碼這樣寫,Xcode會在這裏給咱們一個警告,告訴咱們會有循環引用, Capturing 'self' strongly in this block is likely to lead to a retain cycle這裏,你們都會利用__weak typeof(self) weakSelf = self;來解決循環引用,這樣完成以後,你們確定會以爲這樣寫不完善,應該還有一個__strong,那麼爲何會要用這個__strong呢?對的就是防止「提早釋放」,那麼下面咱們用代碼來演示一下提早釋放:atom

self.name = @"ZH";
__weak typeof(self) weakSelf = self;
self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
self.block();
複製代碼

運行代碼就會發現,weakSelf.name打印出來的是<null>,由於當前控制器pop回去的時候就會調用析構函數dealloc,而後控制器就被釋放掉了,這個name就沒有任何意義,因此就會被置爲空。spa

那麼還有沒有其餘的方法來解決循環引用呢?code

__block ViewController *vc = self;
self.block = ^{
        NSLog(@"%@",vc.name);
        vc = nil;
    };
self.block();
複製代碼

還有沒有方法呢? 這個循環引用的實質就是在block裏面用了self,那麼不用self就能夠了啊,可是在這裏咱們要打印name屬性,這個name又是當前vc的屬性,那麼就是說咱們要在block裏面使用vc,那麼若是把vc當作參數,傳入block是否是能夠呢,咱們來試一下cdn

typedef void(^ZHBlock)(ViewController *);

self.block = ^(ViewController *vc){
        NSLog(@"%@", vc.name);
    };
self.block(self);
複製代碼

運行代碼能夠成功哦!對象

Block的一種應用(注:僅僅是我的作個記錄而已)

首先寫兩個方法,以下:

-(ViewController *)where
{
    NSLog(@"從東土大唐而來");
    return self;
}

-(void)there
{
    NSLog(@"去往西天拜佛求經");
}
複製代碼

在調用這兩個方法的時候,能夠這樣寫: [[self where] there];若是換成點語法呢,能夠這樣寫:self.where.there; 一樣能夠打印出兩個語句,這種點語法的格式就有點相似於鏈式編程。

此時若是想往-(void)there方法傳入一個參數該怎麼作呢,能夠看到這是一個getter方法,沒法傳入參數,但是又要作到通信的目的:那麼試着在there方法中返回一個block

-(ViewController *)where
{
    NSLog(@"從東土大唐而來");
    return self;
}

-(void(^)(NSString *))there
{
    NSLog(@"去往西天拜佛求經");
    
    void (^block)(NSString *) =^(NSString *name)
    {
        NSLog(@"%@",name);
    };
    return block;
}
複製代碼

此時再用點語法調用就能夠傳入參數:self.where.there(@"三藏語錄"); 打印能夠看到語句輸出正確, ---這就是面向鏈式編程最基礎的寫法。

相關文章
相關標籤/搜索