iOS Block淺淺析


前言

Block真的難,筆者靜下心來讀《Objective-C 高級編程 iOS與OS X多線程和內存管理》,讀的時候順便記錄下來本身的心得,方便之後再翻回,也但願能帶給你們一些幫助。html

本文將以一個菜dog的角度,從 Block 不截獲變量、截獲變量不修改、截獲並修改變量 、 截獲對象 四個層次 淺淺探究Block的實現。編程

Block的語法就不回顧了,很差記Block語法能夠翻這篇How Do I Declare A Block in Objective-C?數組


Block實現

轉成C++ 的源代碼學習,筆者加了適當的註釋方便理解。bash

不截獲自動變量值

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

將轉爲多線程

// block中通用的成員變量 結構體
// 文章後面的代碼再也不給出,但都有用到
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 表明Block 的結構體
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成員變量
    struct __main_block_desc_0* Desc;// block 的大小
    
    // 構造函數
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 本來的代碼塊 轉到一個C函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

// 計算block大小的結構體
// 聲明的同時,初始化一個變量__main_block_desc_0_DATA
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)
};

int main()
{
    // 聲明定義block
    // 用到了構造函數方法
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    /*
        至關於如下
        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;
        
        棧上生成的結構體實例的指針,賦值給變量blk。
    */
    
    // 調用block
    // 第一個參數爲 blk_>FuncPtr,即C函數
    // 第二個參數爲 blk自己
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    /*
        至關於如下
        普通的C函數調用
        (*blk->impl.FuncPtr)(blk);
    */
    
    return 0;
}
複製代碼

即把本來的代碼塊,轉到一個C函數中。而且建立一個 表明Block 的結構體,最後一個構造函數,Block對象把函數和成員綁定起來。框架

截獲自動變量不修改的狀況

和以上區別在於,Block結構體中的成員變量多了截獲的自動變量,而且構造函數參數也是。less

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

將轉爲函數

// 跟上面同樣
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 表明Block 的結構體
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成員變量
    struct __main_block_desc_0* Desc;// block 的大小
    
    // 截獲的自動變量
    // 結構體中有名字同樣的成員變量
    const char *fmt;
    int val;
    
    // 構造函數
    // 參數多了截獲的自動變量
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 本來的代碼塊 轉到一個C函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    const char *fmt = __cself->fmt;
    int val = __cself->val;
    
    printf(fmt, val);
}

// 計算block大小的結構體
// 聲明的同時,初始化一個變量__main_block_desc_0_DATA
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)
};

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
    /*
        結構體初始化以下:
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = 0;
        impl.FuncPtr = __main_block_func_0;
        Desc = &__main_block_desc_0_DATA;
        fmt = "val = %d\n";
        val = 10;
    */
    
    return 0;
}
複製代碼

根據以上,咱們知道截獲變量後,實質上是Block結構體中有一個成員變量存了起來。調用Block時,是訪問取結構體成員變量,而不是外面的局部變量。學習

Block中修改值

Block不容許修改外部變量的值。Apple這樣設計,應該是考慮到了block的特殊性,block也屬於「函數」的範疇,變量進入block,實際就是已經改變了做用域。在幾個做用域之間進行切換時,若是不加上這樣的限制,變量的可維護性將大大下降。又好比我想在block內聲明瞭一個與外部同名的變量,此時是容許呢仍是不容許呢?只有加上了這樣的限制,這樣的情景才能實現。因而棧區變成了紅燈區,堆區變成了綠燈區。ui

iOS Block不能修改外部變量的值,指的是棧中指針的內存地址。下面舉幾個例子理解。

  • 非OC對象,修改會編譯錯誤。
int val = 0;
void (^blk)(void) = ^{
    val = 1;
};
複製代碼
  • OC對象,發送消息能夠,但改指針內存地址不行。

如下沒問題

id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        id obj = [[NSObject alloc] init];
        [array addObject:obj];
    };
複製代碼

如下編譯報錯

id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        array = [[NSMutableArray alloc] init];
    };
複製代碼
  • C 數組 截獲自動變量的方法沒有實現對C語言數組的截獲。

如下編譯錯誤

const char text[] = "hello";
    
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };
複製代碼

需改爲指針

const char *text = "hello";
    
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };
複製代碼

那麼Block 要怎麼修改變量呢?

方法一:用到靜態或全局變量

  • C 中有一個變量,容許Block改寫值。

    • 靜態變量
    • 靜態全局變量
    • 全局變量
  • 例子

int global_val = 1;// 全局變量
static int static_global_val = 2;// 靜態全局變量

int main()
{
    static int static_val = 3;// 靜態變量
    
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    }
    
    return 0;
}
複製代碼

轉換後

int global_val = 1;
static int static_global_val = 2;

// 表明Block 的結構體
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成員變量
    struct __main_block_desc_0* Desc;
    // 成員變量只多了靜態變量,緣由在後面分析
    int *static_val;
    
    // 構造函數
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staitc_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackblock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 本來的代碼塊 轉到一個C函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static__val = __cself->static_val;
    
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *=3;
}

// 計算block大小的結構體
// 聲明的同時,初始化一個變量__main_block_desc_0_DATA
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)
};

int main()
{
    static int static_val = 3;
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_val);
    
    return 0;
}
複製代碼

爲何成員變量只多了靜態變量呢?


這就要先了解 iOS 內存區域。 iOS-MRC與ARC區別以及五大內存區

  • 棧:
    • 由系統管理分配和釋放
    • 存放函數參數值,局部變量值
    • iPhone OS下的主線程的堆棧大小是1M,第二個線程開始就是512KB
    • 局部變量在程序運行期間不是一直存在,而是隻在函數執行期間存在,函數的一次調用結束後,變量就被撤銷,其所佔用的內存也被收回。
  • 堆:
    • 由程序猿管理
    • 存放程序猿建立的對象
    • C用malloc/calloc/relloc分配的區域
  • 代碼區:
    • 存放函數的二進制代碼
  • 全局區(又稱靜態區):
    • 存放全局變量和靜態變量
    • 程序運行時一直存在
    • 由編譯器管理(分配釋放),程序結束後由系統釋放

全局區又分爲 BSS段 和 數據段(data)。

BSS段:BSS段(bss segment)一般是指用來存放程序中未初始化的或者初始值爲0的全局變量的一塊內存區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態內存分配。

數據段:數據段(data segment)一般是指用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬於靜態內存分配。

但不一樣的是C++中,不區分有沒有初始化,都放到一塊去。

  • 文字常量區
    • 存放常量字符串
    • 爲了節省內存,C/C++/OC把常量字符串放到單獨的一個內存區域。當幾個指針賦值給相同的常量字符串時,它們實際上會指向相同的內存地址。


再回到剛剛的代碼上,爲何block結構體中成員變量只多了靜態變量呢?

int global_val = 1;// 全局變量
    static int static_global_val = 2;// 靜態全局變量
    static int static_val = 3;// 靜態變量
複製代碼

關於它們的區別——全局變量/靜態全局變量/局部變量/靜態局部變量的異同點

靜態局部變量雖然程序運行時一直存在,但只對定義本身的函數體始終可見。

編譯後,調用block實質上是在 一個新定義的函數 中訪問靜態局部變量,不能直接訪問,因此須要保存其指針。而全局變量能夠訪問到,因此沒有加到成員變量中。


方法二:用到__block 說明符

int main()
{
    __block int val = 10;
    
    void (^blk)(void) = ^{val = 1;};
    
    return 0;
}
複製代碼

轉換後

// 變量將會變成的結構體
// 即val不是int類型,變成此結構體實例
struct __Block_byref_val_0 {
    void *__isa;// __block變量轉化後所屬的類對象
    __Block_byref_val_0 *__forwarding;//指向__block變量自身的指針,後面解釋
    int __flags;// 版本號
    int __size;// 結構體大小
    int val;// 本來的int數值
};

// 表明Block 的結構體
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成員變量
    struct __main_block_desc_0* Desc;// block 的大小
    __Block_byref_val_0 *val;// val轉成成員變量,類型爲結構體
    
    // 構造函數
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->_forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 本來的代碼塊 轉到一個C函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    __Block_byref_val_0 *val = __cself->val;
    
    // 這裏經過__forwarding賦值?後面解釋
    (val->__forwarding->val) = 1;
}

// 當Block從棧複製到堆時
// 經過此函數把截獲的__block變量移到堆或者引用數+1
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
    _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

// 當Block從堆被廢棄時
// 經過此函數把截獲的__block變量引用數-1
// 至關於對象的delloc方法
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
    _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

// 計算block大小的結構體
// 該結構體有兩個函數
// copy 和 dispose
// 聲明的同時,初始化一個變量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};

int main()
{
    /*
        val變成了__Block_byref_val_0結構體實例
    */
    __Block_byref_val_0 val = {
        0,// isa指針
        &val,//forwarding成員,指向本身
        0,// 版本號
        sizeof(__Block_byref_val_0),
        10 //原來int val的值
    };
    
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    
    return 0;
}
複製代碼

從main函數中,咱們能夠發現,Block轉換成Block的結構體類型**__main_block_impl_0的自動變量,__block變量val轉換爲block變量的結構體類型__Block_byref_val_0**的自動變量。它們都在棧上。因此Block的isa指針指向NSConcreteStackBlock。


除了NSConcreteStackBlock,還有兩種類型 NSConcreteGlobalBlock 和 NSConcreteMallocBlock。

設置對象的存儲域
NSConcreteStackBlock
NSConcreteGlobalBlock 全局區
NSConcreteMallocBlock
  • NSConcreteGlobalBlock 在全局變量的地方生成的Block爲NSConcreteGlobalBlock,以下。在全局變量的地方不能使用自動變量,也就不存在截獲的問題。
void (^blk)(void) = ^{printf("Global Block\n");};

int main
{
    return 0;
}
複製代碼

另外只要沒有截獲自動變量,Block類型就是NSConcreteGlobalBlock。

  • NSConcreteMallocBlock 棧上的Block,在出了做用域後會被摧毀,__block變量也是。那麼若是咱們要在別的地方調用Block,就須要把它們移到堆中,手動管理它們的生命週期。這種Block類型就是NSConcreteMallocBlock。

先來理解爲何有個forwarding指向本身。

試想,Block若是截獲了自動變量,而後移到堆上,在別的做用域調用(很常見)。若是__block變量在棧上已經釋放了,Block訪問__block變量會失敗。因此係統須要在Block變成NSConcreteMallocBlock時,截獲的__block變量也複製到堆上。

Block何時會複製到堆上呢?

  • 調用Block的copy方法
  • 將Block做爲函數返回值時
  • 將Block賦值給__strong修飾的變量時
  • 向Cocoa框架含有usingBlock的方法或者GCD的API傳遞Block參數時
__block變量的配置存儲域 Block從棧賦值到堆時的影響
從棧複製到堆並被Block持有
被Block持有

當Block從棧複製到堆時,__block變量的forwarding 會從新指向其在堆中的內存地址。

這樣,不管是在Block語法中、Block語法外使用__block變量,仍是__block變量配置在棧上或對上,均可以順利地訪問同一個__block變量。

筆者在書上剛看到這句話時,有點暈,後來想了一段時間應該是如下意思,若是有誤,歡迎大神批鬥。

以下代碼,有註釋

__block int val = 0;
    
    void (^blk)(void) = [^{++val;} copy];
    
    ++val;// 轉換爲++(val.__forwarding->val);即(棧上的val).__forwarding->val,最終指向堆上的val
    
    blk();// 轉換爲++(val.__forwarding->val);即(堆上的val).__forwarding->val,最終指向堆上的val
    
    NSLog(@"%d", val);
複製代碼

截獲對象

  • __strong 修飾的對象
blk_t blk;

{
    id array = [[NSMutablArray alloc] init];
    blk = [^(id obj) {
        [array addObject:obj];
        NSLog(@"array count = %ld", [array count]);
    } copy];
}

blk([NSObject alloc] init]);
blk([NSObject alloc] init]);
blk([NSObject alloc] init]);
複製代碼

還記得上面提到的截獲變量不修改,轉爲C++,Block結構體中的成員變量多了截獲的自動變量。

這裏,變量做用域結束時,理論上array被廢棄,但執行輸出結果爲數組count123。

這意味着array超出做用域而存在。

會不會也是Block結構體中的成員變量多了截獲的自動變量呢?

轉換爲C++後

struct __main_block_impl_0 {
    struct __block_impl impl;// Block通用的成員變量
    struct __main_block_desc_0* Desc;// Block的大小
    // 指向數組的成員變量
    id __strong array;
    
    // 構造函數
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,id __strong _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 本來的代碼塊 轉到一個C函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
    id __strong array = __cself->array;
    
    [array addObject:obj];
    
    NSLog(@"array count = %ld", [array count]);
}

// 當Block從棧複製到堆時
// 經過此函數把截獲的對象引用數+1
// 至關於retain
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src)
{
    _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}

// 當Block從堆被廢棄時
// 經過此函數把截獲的對象release引用數-1
// 至關於對象的delloc方法
static void __main_block_dispose_0(struct __main_block_impl_0 *src)
{
    _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}

// 計算block大小的結構體
// 該結構體有兩個函數
// copy 和 dispose
// 聲明的同時,初始化一個變量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};
複製代碼

調用block轉換以下。

blk_t blk;

{
    id __strong array = [[NSMutableArray alloc] init];
    
    // 構造函數
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, 0x22000000);
    
    blk = [blk copy];
}

// 調用,第一個參數爲blk自己,第二個參數爲id類型對象
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
複製代碼

能夠看到,和猜想同樣,Block結構體中確實多了一個id __strong array

咱們知道,咱們寫的C語言結構體不能帶有__strong修飾符的變量。緣由是編譯器不知道什麼時候進行C語言結構體的初始化和廢棄操做。

可是OC運行時庫把握Block從棧複製到堆以及堆上的Block被廢棄的時機,所以Block用結構體中能夠管理好

那麼同時用__block 和 __strong 修飾的對象呢?

上面提到過__block int val,val將變爲一個結構體,對象也同樣。

__block id obj = [[NSObject alloc] init];
// 至關於__block id __strong obj = [[NSObject alloc] init];
複製代碼

轉換爲

// 對象將會變成的結構體
struct __Block_byref_obj_0 {
    void *__isa;// __block變量轉化後所屬的類對象
    __Block_byref_val_0 *__forwarding;//指向對象自身的指針,後面解釋
    int __flags;// 版本號
    int __size;// 結構體大小
    void (*__Block_byref_id_object_copy)(void*, void*);// retain對象
    void (*__Block_byref_id_object_dispose)(void*);// release對象
    __strong id obj;//指向對象
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

// 對象變成了__Block_byref_obj_0結構體實例
__Block_byref_obj_0 obj = {
    0,
    &obj,
    0x2000000,
    sizeof(__Block_byref_obj_0),
    __Block_byref_id_object_copy_131,
    __Block_byref_id_object_dispose_131,
    [[NSObject alloc] init]
};
複製代碼
  • __weak 修飾的對象
blk_t blk;

{
    id array = [[NSMutableArray alloc] init];
    __block id __weak array2 = array;
    
    blk =[^(id obj) {
        [array2 addObject:obj];
        NSLog(@"array count = %ld", [array2 count]);
    } copy];
}

blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
複製代碼

輸出結果爲數組數目0。

這是因爲array在做用域結束時被釋放、廢棄,nil被賦值在array2中。

結論:Block中持有weak聲明的對象,對象引用數不會增長。


總結

  • 不截獲變量 或在全局變量位置定義的Block

這種Block的類型是NSConcreteGlobalBlock。這種Block把代碼塊內容轉到一個C函數中,Block結構體較簡單。

  • 截獲但不修改變量

這種Block的結構體中截獲的變量會變成成員變量。

截獲的對象也會變成成員變量(內存語義相同),Block複製到堆上時會調用__main_block_copy_0,廢棄時調用__main_block_dispose_0,對捕獲的強引用對象引用數形成影響。

而且構造函數、調用的C函數都會用到截獲的變量。

  • 截獲並修改變量
  1. 全局和局部靜態變量

Block結構體中沒有全局變量和全局靜態變量,由於能夠直接用。但Block結構體會保存局部靜態變量的指針。

  1. 用到__block 說明符
  • 變量val

val會變成一個結構體__Block_byref_val_0,其成員變量__forwarding指向自己。

當Block從棧複製到堆時,會調用__main_block_copy_0,val會經過_Block_object_assign引用數+1。

當Block銷燬,會調用__main_block_dispose_0,val會經過_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF)引用數-1。

  • 對象

對象會變成一個結構體__Block_byref_obj_0,其成員變量__strong id obj指向對象,其成員變量__forwarding指向自己。

若是是強引用對象,Block會經過__Block_byref_id_object_copy_131,和__Block_byref_id_object_dispose_131內部引用和釋放對象。弱引用不對對象生命週期產生影響。


問題

  • Block中是否須要對弱引用的對象強引用?

到底何時才須要在ObjC的Block中使用weakSelf/strongSelf

  • Block屬性中內存語義用copy 仍是strong?

    在ARC下,這兩種效果都會把Block 從棧上壓到堆上。但事實上,copy更接近Block的本質。

block 使用 copy 是從 MRC 遺留下來的「傳統」,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 能夠把它放到堆區.在 ARC 中寫不寫都行:對於 block 使用 copy 仍是 strong 效果是同樣的,但寫上 copy 也無傷大雅,還能時刻提醒咱們:編譯器自動對 block 進行了 copy 操做。若是不寫 copy ,該類的調用者有可能會忘記或者根本不知道「編譯器會自動對 block 進行了 copy 操做」,他們有可能會在調用以前自行拷貝屬性值。這種操做多餘而低效。你也許會感受我這種作法有些怪異,不須要寫依然寫。若是你這樣想,實際上是你「日用而不知」。

  • 在這篇文章iOS-Block本質,看到許多關於Block理解的問題,對照着實現看挺有幫助。

參考

  • [1] Kazuki Sakamoto,Tomohiko Furumoto.Objective-C高級編程 iOS與OS X多線程和內存管理[M].北京:人民郵電出版社,2013:79-136.
相關文章
相關標籤/搜索