[iOS]Block系列探究三 - Block存儲域

咱們知道應用程序的內存分配有四個區:程序員

  • 程序區域(.text區)- 存放函數體的二進制代碼。
  • 數據區域(.data區)- 主要包括靜態全局區(全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域,程序結束後有系統釋放。)和常量區(常量字符串就是放在這裏的。程序結束後由系統釋放。),若是要站在彙編角度細分的話還能夠分爲不少小的區。
  • 堆 - 通常由程序員分配釋放,若程序員不釋放,程序結束時可能由操做系統回收。注意它與數據結構中的堆是兩回事,分配方式相似於鏈表。先進先出(FIFO)。
  • 棧 - 由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。後進先出(LIFO)。

那麼block做爲OC對象是建立在哪一個區上呢?這篇文章咱們來探究一下。bash

1、block對象的種類

block是一個NSBlock對象,NSBLock的聲明以下:數據結構

@interface NSBlock : NSObject <NSCopying>

+ (id)alloc;
+ (id)allocWithZone:(struct _NSZone { }*)arg1;

- (id)copy;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)invoke;
- (void)performAfterDelay:(double)arg1;

@end
複製代碼

NSBlock有三個子類,分別是:框架

  • _NSConcreteGlobalBlock - 數據區域(.data區)
  • _NSConcreteMallocBlock - 堆
  • _NSConcreteStackBlock - 棧

接下來咱們分別研究一下這三種block。函數


1.1 _NSConcreteGlobalBlock

_NSConcreteGlobalBlock,顧名思義,全局block。如下兩種狀況初始化block時,生成的block爲_NSConcreteGlobalBlockui

  • 在生命全局變量的地方初始化Block;
  • 不捕獲非靜態局部變量。

代碼以下:atom

/** 全局變量 */
int global_count = 10;
/** 靜態全局變量 */
static int static_global_count = 10;

int main(int argc, const char * argv[]) {
    
    /** 靜態局部變量 */
    static int static_count = 10;
    
    void (^block)(void) = ^ {
        global_count = 11;
        static_global_count = 11;
        static_count = 11;
    };
    
    block();
    return 0;
}

複製代碼

咱們打斷點看一下,捕獲捕獲全局變量或者靜態局部變量時,block爲_NSConcreteGlobalBlock,斷點截圖以下:spa

捕獲全局變量或者靜態局部變量
總結來講,block實現的內容不依賴於執行時的狀態(捕獲的變量所在的內存),因此整個程序中只須要一個實例。此時將block存儲在與全局變量相同的數據區域便可。


1.2 _NSConcreteStackBlock

_NSConcreteStackBlock爲棧block,在ARC環境下,捕獲局部變量、成員變量,且沒有被強引用的block都是_NSConcreteStackBlock操作系統

1.2.1 捕獲局部變量

OC代碼以下:code

int main(int argc, const char * argv[]) {

    /** 局部變量 */
    int count = 10;
    
    /** 執行 或者使用void (^__weak block)(void)來指向block */
    ^{
        NSLog(@"%d", count);
    }();
    
    // 打印Block對象
    NSLog(@"Block對象:%@", ^{
        NSLog(@"%d", count);
    });

    return 0;
}
複製代碼

控制檯打印的信息以下:

Block對象:<__NSStackBlock__: 0x7ffeefbff568>
複製代碼

1.2.2 捕獲成員變量

OC代碼以下:

@interface ViewController ()

@property (nonatomic, assign) int count;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"Block對象:%@", ^{
        NSLog(@"%d", self.count);
    });
    ^{
        NSLog(@"%d", self.count);
    }();
}


@end
複製代碼

控制檯打印的信息以下:

Block對象:<__NSStackBlock__: 0x7ffee554b838>
複製代碼

總結來講,block實現的內容依賴於執行時的狀態,而且沒有被強引用,且做用域僅爲當前函數時,block爲_NSConcreteStackBlock,保存在棧上。


1.3 _NSConcreteMallocBlock

_NSConcreteMallocBlock爲堆block,爲廣域變量。在如下狀況會被保存在堆上:

  • 做爲函數的返回值;
  • Cocoa框架的方法且方法命中含有usingBlock等時(待證明);
  • Grand Central Dispatch的API(待證明);
  • Etc.

1.3.1 做爲函數的返回值

當block做爲函數的返回值時,ARC環境下自動將block拷貝,擴大做用域,OC代碼以下:

/** 函數建立 */
typedef void (^block)(void);
block function(int num) {
    return ^{
        NSLog(@"%d", num);
    };
}

int main(int argc, const char * argv[]) {
    
    // 函數的返回值賦值
    void (^__weak block1)(void) = function(10);
    
    // 打印block1對象
    NSLog(@"block1對象:%@", block1);
    
    return 0;
}
複製代碼

控制檯打印的結果以下:

block1對象:<__NSMallocBlock__: 0x103804d30>
複製代碼

總結一下:由於block在函數內做爲局部變量,若是不進行拷貝的話,函數返回時block就銷燬了,因此ARC下系統幫咱們自動拷貝,MRC下直接編譯報錯,須要咱們手動拷貝block。


系統對於block做爲參數時自動進行拷貝暫時沒有時間證明,以後我會補上這部份內容。抱歉。。。

1.3.2 三種block的拷貝效果

很少說,直接上表格:

Block的類 副本源的配置存儲域 賦值效果
_NSConcreteGlobalBlock 數據區 什麼也不作
_NSConcreteStackBlock 從棧拷貝到堆
_NSConcreteMallocBlock 引用計數增長

數據區中的_NSConcreteGlobalBlock執行拷貝操做什麼也不作的緣由是不須要,由於_NSConcreteGlobalBlock在進程結束後才銷燬,已是廣域變量了。

1.4 總結

判斷block是何種類型的變量分如下幾種狀況:

  • 1:若是沒有捕獲變量或者捕獲靜態/全局變量(變量存儲在數據區)則爲_NSConcreteGlobalBlock
  • 2:若是捕獲局部變量(變量存儲在棧或堆)而且未被強引用則爲_NSConcreteStackBlock
  • 3:若是是_NSConcreteStackBlock而且被強引用則爲_NSConcreteMallocBlock
相關文章
相關標籤/搜索