iOS - block原理解讀(二)

前言

在閱讀該篇文章前,推薦閱讀
ios - block原理解讀(一)

前情提要

上篇文章理清了block的實現(xiàn)的基本思路,
提到了自動變量中基礎(chǔ)類型不能在block內(nèi)部進行修改。
那么全局變量,全局靜態(tài)變量,局部靜態(tài)變量呢?

本文解決問題

  • block中引用靜態(tài)變量/全局變量/全局靜態(tài)變量
  • 被block引用的對象,引用計數(shù)為何+=2?
  • 循環(huán)引用問題、閉環(huán)開環(huán)的原因

局部靜態(tài)變量,進行可以修改原值:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        static int a = 10;

        void (^block)(void) = ^{
            a++;
            NSLog(@"%d",a);
        };
        
        block();
        
        return 0;
    }
}

這又是為什么呢?

同樣,我們看看編譯后的C++代碼

這里直接放出和前文不一樣的片段。

1.png
2.png

如果你仔細(xì)閱讀過前文,其他的我就不啰嗦了,
就是從值傳遞變成了指針傳遞,
也就是說,block內(nèi)部將靜態(tài)變量的地址存儲起來,那么用到的時候直接訪問其地址就好了。

問:老師???♂????♂?,我有個問題,如果block的作用域 > 這個靜態(tài)變量會輸出什么?

答:靜態(tài)變量存儲在靜態(tài)區(qū),程序結(jié)束后由系統(tǒng)釋放,所以不存在block的作用域大于靜態(tài)變量。

針對全局變量,一張截圖你就明白了

全局變量圖示.png

全局變量和靜態(tài)變量小科普:存儲同樣存儲在靜態(tài)區(qū),由系統(tǒng)管理

所以簡單來講,就是系統(tǒng)針對不同類型的變量的作用域和生命周期,做出了相應(yīng)的處理。

對象變量

在ARC自動引用計數(shù)下,當(dāng)引用計數(shù)為0時,對象會被釋放。
當(dāng)block內(nèi)部訪問該對象時,block對其強引用,

首先,通過兩段代碼,來看看一個問題:

typedef void (^Block)(void);
Block block;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        TestObject *object = [[TestObject alloc] init];
        
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        block = ^{
            NSLog(@"%@",object);
        };
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        return 0;
    }
}

輸出結(jié)果:
2019-02-21 20:54:37.526394+0800 BlockTest[70590:3745629] 引用數(shù) 1
2019-02-21 20:54:37.527116+0800 BlockTest[70590:3745629] 引用數(shù) 3
typedef void (^Block)(void);
Block block;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        TestObject *object = [[TestObject alloc] init];
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        {
            block = ^{
                NSLog(@"%@",object);
            };
        }

        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        return 0;
    }
}

輸出結(jié)果:
2019-02-21 20:55:50.156887+0800 BlockTest[70627:3749548] 引用數(shù) 1
2019-02-21 20:55:50.157928+0800 BlockTest[70627:3749548] 引用數(shù) 2

先列出MRC和ARC下block的一點區(qū)別
MRC時代的block:
只要block引用外部局部變量,block放在棧里面。
ARC時代的block:
只要block引用外部局部變量,block就放在堆里面。

然后,再看一下c++源碼:

image.png

image.png

可以看出:

  1. 對象類型,多出了copy和dispose函數(shù)
  2. 原有的棧上的結(jié)構(gòu)體指針被copy到了堆,
    同時,copy函數(shù)內(nèi)部會將棧對象指向堆對象。

如果你對copy函數(shù)有疑問,請查看ios - block原理解讀(三)

所以,在block初始化作用域內(nèi)引用計數(shù)+2,
在作用域外??臻g的結(jié)構(gòu)體被回收,引用計數(shù)-1,
在block消亡后,引用計數(shù)-1。

如果你理解了,看一下代碼,并說出結(jié)果:

typedef void (^Block)(void);
Block block;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        TestObject *object = [[TestObject alloc] init];
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        block = ^{
            NSLog(@"%@",object);
        };
        
        block = nil;
        
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        return 0;
    }
}

答案:1,2

循環(huán)引用問題

在ARC大前提下:

  1. block對對象變量強引用
  2. 對象引用計數(shù)不為0則不會釋放

而所謂循環(huán)引用是指,多個對象之間相互引用,產(chǎn)生了閉環(huán)。

先上代碼:

typedef void (^Block)(void);

@interface ViewController ()

@property (nonatomic,copy) Block block;

@property (nonatomic,copy) NSString *name;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.block = ^{
        NSLog(@"%@",self.name);
    };
}

說明:
viewController現(xiàn)在持有block
通過上文我們已經(jīng)知道,
block又強引用了當(dāng)前的的viewController,
那么在ARC環(huán)境下,這兩個是不會釋放的,造成內(nèi)存泄露。

解決方法

既然造成了閉環(huán),又在想在block中希望使用viewController,
只能將閉環(huán)進行斷開。

初步方案,看代碼:

__weak typeof(self) weakSelf = self;
self.block = ^{
        NSLog(@"%@",weakSelf.name);
};

繼續(xù)根據(jù)c++源碼分析

首先,__weak的作用是弱引用,不會增加引用計數(shù),
這個具體原理和__strong,__block在后續(xù)繼續(xù)講解。

image.png

然后,可以看到結(jié)構(gòu)體內(nèi)的屬性變成同樣是__weak類型的,
不會增加引用計數(shù)。
所以,下面代碼輸出結(jié)果是:1,1

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        TestObject *object = [[TestObject alloc] init];
        __unsafe_unretained typeof(object) weakObject = object;
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        block = ^{
            NSLog(@"%@",weakObject);
        };
        
        NSLog(@"引用數(shù) %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
        
        return 0;
    }
}

所以,上面的閉環(huán)狀態(tài)被我們破壞了,現(xiàn)在僅僅是viewController強引用著block。

安全性

上面的方案,如果block內(nèi)部執(zhí)行時間比較長,在執(zhí)行時,viewController突然被釋放了,而block是在堆空間上,并不會被釋放,當(dāng)block內(nèi)部繼續(xù)訪問viewController,這個時候會出現(xiàn)野指針。

經(jīng)典解決方案:

__weak typeof(self) weakSelf = self;
self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",weakSelf.name);
};

大部分博客只講到這個解決方案和所謂的短暫的閉環(huán),沒有將道理講明白。

其實,通過上篇文章和上面的解釋,我們已經(jīng)得出了結(jié)論。

首先,block引用的外部變量的是__weak修飾的weakSelf對象,
所以block初始化并copy到堆上,不會強引用self。
但是執(zhí)行block的時候,其實是執(zhí)行一個靜態(tài)函數(shù),
在執(zhí)行的過程中,生成了strongSelf對象,這個時候,產(chǎn)生了閉環(huán)。
但是這個strongSelf在??臻g上,在函數(shù)執(zhí)行結(jié)束后,strongSelf會被系統(tǒng)回收,此時閉環(huán)被打破。

注意:閉環(huán)不一定只局限于兩個對象,也可能是多個。

最后

以上均為個人研究和理解,如有問題,歡迎評論~
下篇將繼續(xù)解讀,敬請期待!

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

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

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