Objective-C Block篇(一) : Block的實質(zhì)、存儲域

本文內(nèi)容:

  1. 什么是Blocks
  2. Block類型變量
  3. Block的實質(zhì)(帶有(截獲)自動變量值的匿名函數(shù))
  4. Block的存儲域

什么是Blocks

Blocks是C語言的擴充功能,可以用一句話表示這個功能:帶有自動變量(局部變量)的匿名函數(shù)

編譯后:就是在文件中的一個函數(shù)

# 在C中可能使用的變量

  • 自動變量(局部變量)
  • 函數(shù)的參數(shù)
  • 靜態(tài)變量(靜態(tài)局部變量)
  • 靜態(tài)全局變量
  • 全局變量
    其中,在后三種,在文件中的任何地方都能訪問到。
    研究Blocks的重點就是:在Blocks中,怎么訪問、存儲、改變前兩種數(shù)據(jù)

“帶有自動變量值的匿名函數(shù)”這一概念并不僅指Blocks,還存在與其他編程語言,也被稱為:
Block: C+ Blocks、Smalltalk、Ruby
閉包(Closure):swift
lambda計算(λ計算,lambda calculus等):LISP、Python、C++ 11
Anonymous function(匿名函數(shù)):JavaScript

Block類型變量

完整形式的Block類型變量定義語法 與 C語言函數(shù)定義 僅有兩點不同:

  • 沒有函數(shù)名:因為是匿名函數(shù)
  • 帶有 ^ :返回值類型錢帶有'^'(插入記號,caret)記號,因為OS X、iOS中大量使用Block,便于查找
    不完整形式的Block類型變量:可以省略返回值類型、參數(shù)列表

# C 函數(shù)指針 類型變量

int func (int count)
{
return count + 1;
}
int (*funcptr) (int) = &func;

# OC Block 類型變量

在Block語法下,可將Block語法賦值給 Block類型的變量。
在Blocks中的文檔中,“Block”既指源代碼中的`Block語法`,也指由Block語法`生成的值`
int (^blk) (int);  

比較:

  1. 相比C而言,僅僅是將* 改成了 ^
  2. 調(diào)用起來沒有區(qū)別

Block的實質(zhì)

# 代碼反編譯看Block

clang(LLVM 編譯器)具有轉(zhuǎn)換為我們可讀源代碼的功能,我們可以通過-rewrite-objc將含有Block語法的源代碼變換為C++的源代碼,本質(zhì)上是C語言源代碼

clang -rewrite-objc 源代碼文件名

代碼轉(zhuǎn)換:

int main(){
    void(^blk)(void)=^{ printf("Block\n");};
    blk();
    return 0;
}

源代碼通過clang可變換為以下形式:

//從其名稱可以聯(lián)想到某些標志、今后版本所需的區(qū)域以及函數(shù)指針
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    vold *Funcptr;  
}

// Block類型變量對應(yīng)的結(jié)構(gòu)體
struct __main_block_impl_0 {
    struct __block_impl imp1;   
    struct __main_block_desc_0* Desc;
    //默認構(gòu)造函數(shù),C++中的語法
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        imp1.isa = &_NSConcreteStackBlock;
        imp1.Flags = flags  //不傳默認=0,Reserved默認也是0
        imp1.FuncPtr = fp;
        Desc = desc:
    }
};

//該函數(shù)的參數(shù)__cself相當于C++實例方法中指向?qū)嵗陨淼淖兞縯his,或是Objective-C實例方法中指向?qū)ο笞陨淼淖兞縮elf
//即參數(shù)__cself為`指向Block值的變量`
static void __main_block_func_0(struct __main_block_impl_0* cself)
{
    printf("Block\n");
}

//今后版本升級所需的區(qū)域和Block的大小
static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)   //block對應(yīng)結(jié)構(gòu)體的實例大小
};

int main()
{
    //創(chuàng)建 構(gòu)造函數(shù)
      /*
        相當于(《OC 高級編程》中寫的):
        struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
        struct __main_block_impl_0 * blk = &tmp;
        相當于源碼:void(^blk)(void) = ^{printf("Block\n");};
       
        @param  __main_block_func_0  函數(shù)
        @param  &__main_block_desc_0_DATA 結(jié)構(gòu)體指針  "靜態(tài)全局變量"
      */
    void (*blk)(void) = (void(*)(void)) &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);  

    //調(diào)用
      /*
        問題:blk明明是__main_block_impl_0結(jié)構(gòu)體類型指針的,為什么變成__block_impl結(jié)構(gòu)體類型指針了?
        答:因為__block_impl作為__main_block_impl_0的首成員,所以兩個結(jié)構(gòu)體的首地址都是相同的,所以完全可以強轉(zhuǎn),只不過使用__block_impl就訪問不了__main_block_impl_0結(jié)構(gòu)體中其他的幾個成員變量,只能訪問__block_impl自己的。
      */
    ( (void (*)(struct __block_impl *)) ((struct __block_impl *)blk)->FuncPtr )((struct __block_impl *)blk);

    return 0;
}

# &_NSConcreteStackBlock是什么

在講&_NSConcreteStackBlockisa之前,先了解一下class、id這兩個關(guān)鍵字的定義:

struct objc_class {
    Class isa;
}
typedef struct objc_class * class;

struct objc_object{
    Class isa;
}
typedef struct objc_object * id;


isa:是一個Class 類型的指針. 
1. 每個實例對象有個isa的指針,他指向?qū)ο蟮念怌lass
2. Class里也有個isa的指針, 指向meteClass(元類)。
3. 元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass). 
4. 根元類的isa指針指向本身,這樣形成了一個封閉的內(nèi)循環(huán)。

元類保存了類方法的列表。當類方法被調(diào)用時,先會從本身查找類方法的實現(xiàn),如果沒有,元類會向他父類查找該方法。

綜上:

  1. 每個對象、類本質(zhì)上都是結(jié)構(gòu)體,都有isa指針
  2. 每個結(jié)構(gòu)體都持有對象、類的屬性、方法的名稱、方法的實現(xiàn)(函數(shù)指針)、以及父類的指針
  3. &_NSConcreteStackBlock相當于Block isa指向的類,在將Block作為OC對象處理時,關(guān)于該類的信息放置于_NSConcreteStackBlock中.

總結(jié):Block即為Objective-C的對象,C中的結(jié)構(gòu)體,底層實現(xiàn)是C語言中的函數(shù)(下一篇Objective-C Block篇(二) : Block捕獲自動變量機制中將通過講解Block的捕獲機制,詳細證明Block就是函數(shù))

Block的存儲域

Block存在三種不同作用域的對象:

  • _NSConcreteStackBlock設(shè)置在棧上
  • _NSConcreteGlobalBlock設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))
  • _NSConcreteMallocBlock設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(即堆)中

# _NSConcreteGlobalBlock

  1. 像聲明全局變量一樣聲明Block變量時
  2. 捕獲全局變量、靜態(tài)自動變量、靜態(tài)全局變量時(此時,如果同時滿足_NSConcreteMallocBlock的條件,那_NSConcreteMallocBlock優(yōu)先
  3. 當不捕獲任何自動變量時

# _NSConcreteMallocBlock

  1. 調(diào)用Block的copy實例方法時
  2. Block作為函數(shù)返回值返回時
  3. 將Block賦值給附有__strong修飾符id類型的類或Block類型變量時
  4. 在方法名含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API傳遞Block時

調(diào)用objc_retainBlock()方法,實際上也就是Block_copy函數(shù)

  • 對棧上的Block執(zhí)行copy方法,會從棧復制到堆
  • 對堆上的Block執(zhí)行copy方法,引用計數(shù)增加
  • 對全局的Block執(zhí)行copy方法,什么也不會發(fā)生
    所以,不管Block配置在何處,用copy方法都不會引起任何問題,不確定存儲域的時候可以直接copy

# _NSConcreteStackBlock

除了以上講述的情況,其他創(chuàng)建的Block存儲于都是在棧上

ARC 無效時,一般需要我們手動將Block從棧復制到堆,然后手動釋放。

  • -retain
  • -copy/Block_copy(block)
  • -release/Block_release(block)

對于棧上的block調(diào)用retain是無效的,只有先copy到堆上,再copy才會有效果

最后編輯于
?著作權(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)容