[iOS]Block系列探究四 - __block變量存儲域

上一篇文章研究了一下Block的存儲域,這一篇文章咱們來研究下__block變量的存儲域。bash

1、__block變量不能聲明爲全局變量

當咱們將__block聲明爲全局變量的時候,代碼以下:ui

// 聲明爲全局變量
__block int global_val = 10;

int main(int argc, const char * argv[]) {
    ...
    return 0;
}
複製代碼

會報錯,報錯信息爲__block attribute not allowed, only allowed on local variables。 爲何會報錯呢?其實也比較容易理解,__block這個屬性的出現就是爲了解決Block內部不能修改局部變量的問題。但是全局變量沒有這個問題,就不要畫蛇添足了。spa

2、__block變量的存儲域

從第一節的報錯信息__block attribute not allowed, only allowed on local variables能夠看出,__block屬性只能用來修飾局部變量,那麼下面就引出了__block變量的存儲域以及Block從棧複製到堆時對__block變量產生的影響。code

2.1 __block存儲在棧上

咱們首先來想象一種場景,__block屬性修飾的局部變量(非對象),從建立到到被棧BLock使用時,__block變量時存儲在哪一個區域呢? 先說答案,如下兩種狀況,__block存儲在棧上:對象

  • (非對象)剛初始化時;
  • 被棧BLock使用時。

2.1.1 (非對象)剛初始化時

__block變量剛初始化時的代碼以下:內存

int main(int argc, const char * argv[]) {
    
    // 聲明爲局部變量
    __block int val = 10;
    // 這個局部變量做爲地址對比
    int num = 10;
    
    NSLog(@"__block變量的地址:%p -- 局部變量的地址:%p", &val, &num);
    
    return 0;
}
複製代碼

控制檯打印語句以下:string

__block變量的地址:0x7ffeefbff578 -- 局部變量的地址:0x7ffeefbff55c
複製代碼

咱們能夠看到,__block變量的地址和普通局部變量的地址是挨着的,因此剛初始化時的__block變量存儲在棧上。it

2.1.2 被棧Block使用

__block變量被棧BLock使用的代碼以下:內存管理

int main(int argc, const char * argv[]) {
    
    // 聲明爲局部變量
    __block int val = 10;
    // 這個局部變量做爲地址對比
    int num = 10;
    
    void (^__weak block)(void) = ^{
        val = 11;
    };
    
    block();
    
    NSLog(@"__block變量的地址:%p -- 局部變量的地址:%p", &val, &num);
    
    return 0;
}
複製代碼

控制檯打印語句以下:table

__block變量的地址:0x7ffeefbff588 -- 局部變量的地址:0x7ffeefbff56c
複製代碼

咱們能夠看到,__block變量的地址和普通局部變量的地址是挨着的,因此被棧Block使用的__block變量存儲在棧上(__block變量沒有被強引用)。

2.2 __block存儲在堆上

咱們知道了存儲在棧上的__block變量被棧BLock使用時,__block變量並無拷貝到堆上,那麼__block變量被堆BLock使用時,會發生什麼呢?咱們來探究一下。 上代碼:

int main(int argc, const char * argv[]) {
    
    // 聲明爲局部變量
    __block int val = 10;
    // 這個對象做爲地址對比
    People *people = [[People alloc] init];
    
    void (^block)(void) = ^{
        val = 11;
    };
    
    block();
    
    NSLog(@"__block變量的地址:%p -- 對象的地址:%@", &val, people);
    
    return 0;
}
複製代碼

控制檯打印以下:

__block變量的地址:0x100704828 -- 對象的地址:<People: 0x100706b90>
複製代碼

咱們能夠看到,當存儲在棧上的__block變量被棧BLock使用時,__block變量被拷貝到了堆上(被堆BLock強引用)。 咱們把NSLog(@"__block變量的地址:%p -- 對象的地址:%@", &val, people);這句代碼clang一下,看看到底發生了什麼:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_0r_hkkmpct143n4wd3xxk0l1j8c0000gn_T_main_c842f2_mi_0, &(val.__forwarding->val), people);
複製代碼

重溫一下__block變量的結構體:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
複製代碼

咱們能夠看到,使用val這個__block變量時,實際上是使用了val.__forwarding->val這個值。

因此咱們能夠猜想,當__block變量初始化在棧上時,__forwarding這個成員變量一開始指向的是棧上的__block變量,可是在__block變量拷貝了一份在堆上時,__forwarding成員變量指向了堆上的__block變量。因此無論是在Block內仍是BLock外咱們訪問的都是同一個__block變量。

2.2 __block被多個堆BLock使用

其實咱們已經探討好了__block變量的存儲域,就是棧和堆。那麼一個__block變量被多個堆Block使用時會發生什麼呢? 其實__block變量本質上是一個對象,因此每被一個堆BLock使用時,就表明被強引用一次,__block變量的引用計數+1,這個和OC的引用計數式內存管理是徹底同樣的。


最後咱們用一個表格來結束今天的文章。 BLock從棧賦值到堆時對__block變量產生額影響:
__block變量的配置存儲域 BLock從棧賦值到堆時的影響
從棧拷貝到堆上並被Block持有,__forwarding指向堆上的__block對象
被Block持有,引用計數+1
相關文章
相關標籤/搜索