《Objective-C 高級編程》學習筆記2-Blocks

獅子頭鎮樓編程

《Objective-C 高級編程》學習筆記1-內存管理
bash

《Objective-C 高級編程》學習筆記3-GCD
函數

參考資料:post

《Objective-C高級編程》Blocks學習

《Objective-C 高級編程》乾貨三部曲(二):Blocks篇ui

俗話說好記性不如爛筆頭,參考了大佬的智慧,看完獅子頭後總結的學習筆記。若有侵權請友情告知。


一、什麼是Blocks

Blocks是C語言的擴充功能:帶有局部變量的匿名函數;
spa

Blocks中將帶有局部變量的匿名函數部分稱爲「Block literal」,或簡稱「Block」;
指針

「帶有局部變量的匿名函數」這一律念並不只指Blocks,它還存在於其餘程序語言中,在計算機科學中,此概念也稱爲必包。
code

二、Blocks模式

Block語法:cdn


Block例子:

//一、可用 typedef 給Block定義別名
typedef int (^blk_t)(int);

int blockDemo() {
    blk_t blk = ^(int count) {
        return count + 1;
    };
    
    int a = blk(6);
    printf("a = %d\n", a);
    
    //二、Block會截獲所使用的局部變量值,即保存該局部變量的瞬間值;
    int b = 111;
    //三、使用附有 __block 說明符的局部變量可在Block中賦值,該變量稱爲 __block 變量
    __block int c = 222;
    
    void (^blk_b)(void) = ^ {
        printf("b1 = %d\n", b);
        printf("c1 = %d\n", c);
        c += b;
    };
    
    b = 222;
    blk_b();
    
    printf("b2 = %d\n", b);
    printf("c2 = %d\n", c);
    
    return 0;
}複製代碼

輸出結果是:


三、Blocks的實現

3.0   OC轉C的方法,可經過 clang 指令轉換源代碼


轉換後能夠在該文件夾獲得一個:block.cpp,轉換獲得的C++代碼就在其中。

3.1   Block語法轉換

OC代碼:

int mainDemo() {
    void (^blk)(void) = ^{
        printf("Block\n");
    };
  
    blk();
    return 0;
}複製代碼

轉換成C++後:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __mainDemo_block_impl_0 {
  struct __block_impl impl;
  struct __mainDemo_block_desc_0* Desc;
  __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//經過 Blocks 使用的匿名函數實際上被做爲簡單的C語言函數來處理
//參數 __cself 爲指向 Block 值的變量
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    printf("Block\n");
}

static struct __mainDemo_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0)
};

int mainDemo() {
    void (*blk)(void) = ((void (*)())&__mainDemo_block_impl_0((void *)__mainDemo_block_func_0,
                                                              &__mainDemo_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    // 去掉轉換部分:
    // struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA);
    // struct __mainDemo_block_impl_0 *blk = &tmp;
    
    return 0;
}複製代碼

3.2   Objective-C類和對象的實質

//「id」這一變量類型用於存儲OC對象,它的聲明以下:
typedef struct objc_object {
    Class isa;
} *id;
//id 爲 objc_objc 結構體的指針類型

//Class 爲 objc_calss 結構體的指針類型
typedef struct objc_class *Class;
//objc_class 的聲明以下:
struct objc_class {
    Class isa;  
};

//objc_object 結構體和 objc_class 結構體是在各個對象和類的實現中使用的最基本的結構體。複製代碼

舉個例子,定義一個OC類:MyObject

@interface MyObject : NSObject {
    int val0;
    int val1;
}
@end複製代碼

基於 objc_object 結構體,MyObject類的對象結構體以下:

struct MyObject {
    Class isa;//經過成員變量 isa 保持該類的結構體實例指針
    int val0;
    int val1;
}複製代碼

那麼MyObject的實質:


各種的結構體就是基於 objc_class 結構體的 class_t 結構體。class_t 結構體的聲明以下:

struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    uintptr_t data_NEVER_USE;
};
//該結構體實例會持有聲明的成員變量、方法的名稱、方法的實現(即函數指針)、屬性以及父類的指針複製代碼

3.3   Block的實質:爲Objective-C對象

//Block結構體
struct __mainDemo_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __mainDemo_block_desc_0* Desc;
};
// __mainDemo_block_impl_0 結構體至關於基於 objc_object 結構體的OC類對象的結構體。

// 對於其中成員變量 isa 進行初始化:
isa = &_NSConcreteStackBlock;

// 即 _NSConcreteStackBlock 至關於 class_t 結構體實例。
// 在將 Block 做爲 OC 的對象處理時,關於該類的信息放置於 _NSConcreteStackBlock 中。
複製代碼

四、截獲局部變量

 「截獲局部變量值」意味着在執行 Block 語法時,Block 語法表達式所使用的局部變量值被保存到 Block 的結構體實例中。

OC代碼:

int mainDemo() {
    int dmy = 256;
    int val = 10;
    const char *fmt = "var = %d\n";
    
    void (^blk)(void) = ^{
        printf(fmt, val);
    };
    
    val = 2;
    fmt = "These values were changed. var = %d\n";
    
    blk();
    return 0;
}
複製代碼

轉換後C++代碼核心部分:

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;
    
    //三、block中未使用的局部變量不會被截獲
    //四、block中使用的局部變量,截獲的是自動變量值
    const char *fmt;
    int val;
};

static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    // 「截獲局部變量值」意味着在執行 Block 語法時
    // Block 語法表達式所使用的局部變量值被保存到 Block 的結構體實例中
    const char *fmt = __cself->fmt;
    int val = __cself->val;
    printf(fmt, val);
}

int mainDemo() {
    int dmy = 256;
    int val = 10;
    const char *fmt = "var = %d\n";
    
    struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0, 
&__mainDemo_block_desc_0_DATA, fmt, val);
    struct __mainDemo_block_impl_0 *blk = &tmp;
    
    val = 2;
    fmt = "These values were changed. var = %d\n";

    blk->FuncPtr(blk);    
    return 0;
}   複製代碼

五、解決 Block 中不能保存值的問題

C語言的函數中可能使用的變量:

  • 局部變量
  • 函數的參數
  • 靜態變量
  • 靜態全局變量
  • 全局變量

其中,在函數的屢次調用之間可以傳遞值的變量有:

  • 靜態變量
  • 靜態全局變量
  • 全局變量

5.1   在函數的屢次調用之間可以傳遞值的變量,在 Block 中也能夠改寫值

  • 靜態全局變量全局變量 的訪問於轉換前徹底相同
  • 靜態變量 是經過把靜態變量的指針傳遞給Block結構體保存後,進行訪問

OC代碼:

int global_val = 1;
static int static_global_val = 2;

int mainDemo() {
    static int static_val = 3;

    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    
    blk();
    return 0;
}複製代碼

轉換成C++後:

int global_val = 1;
static int static_global_val = 2;

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;

    int *static_val; 

    __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, 
                            *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

//block函數部分
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    int *static_val = __cself->static_val;
    global_val *= 1;
    static_global_val *= 3;
    (*static_val) *= 3;
}

static struct __mainDemo_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0),
};

int mainDemo() {
    static int static_val = 3;

    blk = &__mainDemo_block_impl_0(__mainDemo_block_func_0, 
        &__mainDemo_block_desc_0_DATA, &static_val);

    return 0;
}複製代碼

5.2   __block 存儲域類說明符

OC代碼:

int mainDemo() {
    __block int block_val = 10;
    
    void (^blk)(void) = ^{
        block_val = 100;
    };

    blk();
    return 0;
}
複製代碼

轉換成C++後:

// __block類型結構體
struct __Block_byref_block_val_0 {
    void *__isa;
    __Block_byref_block_val_0 *__forwarding;//指向本身的指針
    int __flags;
    int __size;
    int block_val;//經過__forwarding進行訪問
};

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;

    __Block_byref_block_val_0 *block_val;    

    __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, 
                             __Block_byref_block_val_0 *_block_val, int flags=0) 
                            : block_val(_block_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    __Block_byref_block_val_0 *block_val = __cself->block_val;
    (block_val->__forwarding->block_val) = 100;
}

static void __mainDemo_block_copy_0(struct __mainDemo_block_impl_0*dst, struct __mainDemo_block_impl_0*src) {
    _Block_object_assign((void*)&dst->block_val, (void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __mainDemo_block_dispose_0(struct __mainDemo_block_impl_0*src) {
    _Block_object_dispose((void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __mainDemo_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __mainDemo_block_impl_0*, struct __mainDemo_block_impl_0*);
    void (*dispose)(struct __mainDemo_block_impl_0*);
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0),
    __mainDemo_block_copy_0,
    __mainDemo_block_dispose_0
};

int mainDemo() {
    __Block_byref_block_val_0 block_val = {
        0,
        &block_val, 
        0, 
        __Block_byref_block_val_0, 
        10
    };

    blk = __mainDemo_block_impl_0(
    __mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA, &val, 0x22000000);
    
    return 0;
}複製代碼

  • __block 變量會變成棧上生成的 __Block_byref_block_val_0 結構體實例
  • 該結構體持有原局部變量的成員變量
  • __Block_byref_block_val_0 結構體實例的成員變量 __forwarding 持有指向該實例自身的指針
  • 經過 __forwarding 成員變量訪問成員變量val
  • __block 變量的 __Block_byref_block_val_0 結構體並不在 Block 用 __mainDemo_block_impl_0 結構體中,這樣作是爲了在多個 Block 中使用 __block 變量


六、Block 存儲域

  • Block 會轉換爲 Block 的結構體類型的自動變量
  • __block 變量轉換爲 __block 變量的結構體類型的自動變量

將 Block 看成 OC對象 來看時,Block 有以下種類:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

一、Block 什麼時候爲 _NSConcreteGlobalBlock  類對象:

  1. 記述全局變量的地方使用 Block 語法時
  2. Block 語法的表達式中不實用應截獲的自動變量時

二、設置在棧上的Block,若是其所屬的變量做用域結束,該 Block 就被廢棄,__block 變量也會被廢棄:


三、 Blocks 提供了將 Block 和 __block 變量從棧上覆制到堆上的方法來解決 


四、各類 Block 調用 copy 方法的效果:


5、棧上 Block 複製到堆上的時機:

  • 調用 Block 的 copy 實例方法時
  • Block 做爲函數返回值返回時
  • 將 Block 賦值給附有 __strong 修飾符 id 類型的類或 Block 類型成員變量時
  • 在 GCD 的API中傳遞Block時

七、__block 變量存儲域


  1. 在任何一個 Block 從棧複製到堆時,__block 變量也會一併從棧複製到堆並被該 Block 所持有。當剩下的 Block 從棧複製到堆時,被複制的 Block 持有 __block 變量,並增長 __block 變量的引用計數。
  2. 若是配置在堆上的 Block 被廢棄,那麼它所使用的 __block 變量也就被釋放。此思考方式與 OC 的引用計數式內存管理相同。
  3. __block 變量用結構體成員變量 __forwarding 的緣由:


經過該功能,不管在 Block 語法位置,仍是 __block 變量配置在棧上或堆上,均可以順利訪問同一個 __block 變量。

相關文章
相關標籤/搜索