block


一、什么是閉包

在 wikipedia 上,閉包的定義是:

In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.

翻譯過來,閉包是一個函數(或指向函數的指針),再加上該函數執行的外部的上下文變量(有時候也稱作自由變量)。

block 實際上就是 Objective-C 語言對於閉包的實現。 block 配合上 dispatch_queue,可以方便地實現簡單的多線程編程和異步編程,《使用GCD》

本文主要介紹 Objective-C 語言的 block 在編譯器中的實現方式。主要包括:

  1. block 的內部實現數據結構介紹
  2. block 的三種類型及其相關的內存管理方式
  3. block 如何通過 capture 變量來達到訪問函數外的變量

二、實現方式

  1. block 本身也是一個 OC 對象,它里面也有 isa 指針
  2. block 是封裝了函數調用(存儲函數調用地址,函數訪問變量)和函數調用環境的 OC 對象

在 main.m 中寫入一個 block:

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

        int a = 15;
        void (^ block)(int, int) = ^ (int b, int c) {
            NSLog(@"%d", a);
        };
        block(10, 10);
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

終端進到項目 main.m 的目錄下通過反編譯成 c++ 文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mian.cpp

 得到 main.cpp,找到這個 block 對象的底層結構:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 15;
        void (* block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

實際上 block 在底層對應的就是 __main_block_impl_0:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

里面存儲着 __block_impl 的結構體 impl,以及 __main_block_desc_0 的結構體指針 Desc. 搜索對象的內容我們可以找到:

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

這里可以看到 __block_impl 包含着 isa 指針,以及 FuncPtr。FuncPtr 就是 block 的調用地址,是在聲明 block 的時候初始化傳遞進來的。以及 __main_block_desc_0 包含着的 Block_size 為 block 的內存大小。還有 int a 也封裝到了Block 內部,我們知道 OC 對象的特征就是 isa 指針,所以,block 就是封裝了函數調用、以及函數調用環境的 OC 對象。


反編譯成 C 文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.c

block 的數據結構定義如下:


對應的結構體定義如下: 

struct Block_descriptor { 
    unsigned long int reserved; 
    unsigned long int size; 
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *); 
}; 

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, …);
    struct Block_descriptor *descriptor; 
    /* Imported variables. */ 
}; 

通過該圖,我們可以知道,一個 block 實例實際上由 6 部分構成:

  1. isa 指針,所有對象都有該指針,用於實現對象相關的功能。
  2. flags,用於按 bit 位表示一些 block 的附加信息,本文后面介紹 block copy 的實現代碼可以看到對該變量的使用。
  3. reserved,保留變量。
  4. invoke,函數指針,指向具體的 block 實現的函數調用地址。
  5. descriptor, 表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針。
  6. variables,capture 過來的變量,block 能夠訪問它外部的局部變量,就是因為將這些變量(或變量的地址)復制到了結構體中。

 

三、Capture (捕獲)

對於局部變量:值傳遞,Block 只是把局部變量的值捕獲存儲在了 block 的結構體內存儲。

int a = 10;
void (^block)(void) = ^{
    NSLog(@"%d", a);
};
a = 20;
block(); // 輸出 10

對於 Static:指針傳遞,Block 把 static 的變量的指針存儲在 block 的結構體內,所以取值的話就是取對應最后的賦值。

static int a = 10;
void (^block)(void) = ^{
    NSLog(@"%d", a);
};
a = 20;
block();  // 輸出 20

全局變量:直接訪問

static int a = 20;
int b = 15;

int main(int argc, const char * argv\[\]) {
    @autoreleasepool {
        
        void (^block)(void) = ^{
            NSLog(@"%d", a);
            NSLog(@"%d", b);
        };
        a = 25;
        b = 10;
        block();  // 輸出 25 10
    }
    return 0;
}

四、block 的類型

block 分為 3 種類型,但是最終都是繼承自 NSObject。

  •  _NSConcreteGlobalBlock   全局的靜態 block,內部沒有訪問 auto 變量,不會訪問任何外部變量。
  •  _NSConcreteStackBlock     保存在棧中的 block,內部訪問了 auto 變量,當函數返回時會被銷毀。
  •  _NSConcreteMallocBlock   保存在堆中的 block,當引用計數為 0 時會被銷毀。

stack block 存放在棧內存,如果 block 存放在函數內,一旦函數作用域結束,則 block 內容則會被清除,如果存放在堆內存(調用 copy),就會變成 malloc block,則不會自動清除,這也是為什么 block 需要用 copy 修飾的原因。


4.1 NSConcreteGlobalBlock

#include <stdio.h>

int main() 
{ 
    ^{ printf("Hello, World!\\n"); } (); 
   
    return 0; 
}

在終端命令行中輸入

$ clang -rewrite-objc block.cpp(文件名) 

即可在目錄中看到 clang 輸出了一個名為 block.cpp 的文件。該文件就是 block 在 c 語言實現,將 block.cpp 中一些無關的代碼去掉,將關鍵代碼引用如下:

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

struct __main_block_impl_0 { 
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc; 
    __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; 
    } 
}; 
    
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
    printf("Hello, World!\n"); 
} 

static struct __main_block_desc_0 { 
    size_t reserved; 
    size_t Block_size; 
}__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; 

int main() 
{ 
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) (); 
    return 0; 
} 

具體看一下是如何實現的。__main_block_impl_0 就是該 block 的實現,從中可以看出:

  1. 一個 block 實際是一個對象,它主要由一個 isa 和一個 impl 和一個 descriptor 組成。
  2. 在本例中,isa 指向 _NSConcreteGlobalBlock,主要是為了實現對象的所有特性,在此我們就不展開討論了。
  3. impl 是實際的函數指針,本例中,它指向 __main_block_func_0。這里的 impl 相當於之前提到的 invoke 變量,只是clang 編譯器對變量的命名不一樣而已。
  4. descriptor 是用於描述當前這個 block 的附加信息的,包括結構體的大小,需要 capture 和 dispose 的變量列表等。結構體大小需要保存是因為,每個 block 因為會 capture 一些變量,這些變量會加到 __main_block_impl_0 這個結構體中,使其體積變大。在該例子中我們還看不到相關 capture 的代碼,后面將會看到。

4.2 NSConcreteStackBlock

#include <stdio.h> 

int main()
{ 
    int a = 100; 
    void (^block)(void) = ^{ 
        printf("%d\n", a); 
    }; 
    block(); 

    return 0; 
}

反編譯之后:

struct __main_block_impl_0 { 
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc; 
    int a; 
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { 
        impl.isa = &_NSConcreteStackBlock; 
        impl.Flags = flags; 
        impl.FuncPtr = fp; 
        Desc = desc; 
    } 
}; 
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
    int a = __cself->a; // bound by copy 
    printf("%d\n", a); 
} 

static struct __main_block_desc_0 { 
    size_t reserved; 
    size_t Block_size; 
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 

int main() 
{ 
    int a = 100; 
    void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a); 
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); 

    return 0; 
} 

在本例中,我們可以看到:

  1. 本例中,isa 指向 _NSConcreteStackBlock,說明這是一個分配在棧上的實例。
  2. main_block_impl_0 中增加了一個變量 a,在 block 中引用的變量 a 實際是在申明 block 時,被復制到 main_block_impl_0 結構體中的那個變量 a。因為這樣,我們就能理解,在 block 內部修改變量 a 的內容,不會影響外部的實際變量 a。
  3. main_block_impl_0 中由於增加了一個變量 a,所以結構體的大小變大了,該結構體大小被寫在了 main_block_desc_0 中。

4.3 NSConcreteMallocBlock

NSConcreteMallocBlock 類型的 block 通常不會在源碼中直接出現,因為默認它是當一個 block 被 copy 的時候,才會將這個 block 復制到堆中。以下是一個 block 被 copy 時的示例代碼(來自這里),可以看到,在第 8 步,目標的 block 類型被修改為 _NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    // 1
    if (!arg) return NULL;

    // 2
    aBlock = (struct Block_layout *)arg;

    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;

    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;

    // 8
    result->isa = _NSConcreteMallocBlock;

    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }

    return result;
}

五、變量的復制

block 內部默認是無法修改 auto 變量的,因為在 block 底部的話執行 block、聲明局部變量 a(main 函數)的地方分別是兩個不同的函數,並沒有辦法從一個函數去修改另一個函數的局部變量,而如果使用 static 或者使用全局變量是可以的,因為block 在底層存儲 static 變量是存儲它的指針地址,全局變量就全部都可以訪問。

如果要修改 auto 變量的話,則需要使用 __block。

對於 block 外的變量引用,block 默認是將其復制到其數據結構中來實現訪問的:


對於用 __block 修飾的外部變量引用,block 是復制其引用地址來實現訪問的:


修改上面的源碼,在變量前面增加 __block 關鍵字:

#include <stdio.h> 

int main() 
{ 
    __block int i = 1024; 
    void (^block)(void) = ^{ 
        printf("%d\n", i); 
        i = 1023; 
    }; 
    block(); 
    return 0; 
}

生成的關鍵代碼如下,可以看到,差異相當大:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            printf("%dn", (a->__forwarding->a));
            (a->__forwarding->a) = 1023;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t 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(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1024};
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

從代碼中可以看到:

  1. 源碼中增加一個名為 __Block_byref_i_0 的結構體,用來保存我們要 capture 並且修改的變量 a。
  2. main_block_impl_0 中引用的是 Block_byref_i_0 的結構體指針,這樣就可以達到修改外部變量的作用。
  3. __Block_byref_i_0 結構體中帶有 isa、a以及 __forwarding(指向自己的指針)等其他信息,它也是一個對象。
  4. 我們需要負責 Block_byref_i_0 結構體相關的內存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函數指針,對於在調用前后修改相應變量的引用計數。
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1024};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

這里聲明了一個 __Block_byref_a_0 的對象,並把 &a 傳遞給了 __forwarding,10 傳遞給了 a。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            printf("%dn", (a->__forwarding->a));
            (a->__forwarding->a) = 1023;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

這里執行 block 時,取出了 __Block_byref_a_0 所存儲的 &a(__forwarding:__Block_byref_a_0 的指針地址),再取出 a,最后進行修改/使用。

六、ARC 對 block 類型的影響

在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

原本的 NSConcreteStackBlock 的 block 會被 NSConcreteMallocBlock 類型的 block 替代。在蘋果的官方文檔中也提到,當把棧中的 block 返回時,不需要調用 copy 方法了。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int i = 1024;
        void (^block)(void) = ^{
            printf("%d\n", i);
        };
        block();
        NSLog(@"%@", block);
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

個人認為這么做的原因是,由於 ARC 已經能很好地處理對象的生命周期的管理,這樣所有對象都放到堆上管理,對於編譯器實現來說,會比較方便。

七、Block 循環引用問題

7.1 RetainCircle 的由來

當 A 對象里面強引用了 B 對象,B 對象又強引用了 A 對象,這樣兩者的 retainCount 值一直都無法為 0,於是內存始終無法釋放,導致內存泄露。所謂的內存泄露就是本應該釋放的對象,在其生命周期結束之后依舊存在。


這是 2 個對象之間的,相應的,這種循環還能存在於 3、4 ... n 個對象之間,只要相互形成環,就會導致 Retain Cicle 的問題。

當然也存在自身引用自身的。當一個對象內部的一個 obj,強引用的自身,也會導致循環引用的問題出現。常見的就是 block 里面引用的問題。


7.2 __weak、__strong 的實現原理

在 ARC 環境下,id 類型和對象類型、C 語言其他類型不同,類型前必須加上所有權的修飾符。

所有權修飾符總共有 4 種:

  1. __strong 修飾符
  2. __weak 修飾符
  3. __unsafe_unretained 修飾符
  4. __autoreleasing 修飾符

一般我們如果不寫,默認的修飾符是 __strong。

要想弄清楚 __strong、__weak 的實現原理,我們就需要研究研究 clang(LLVM編譯器)和 objc4 Objective-C runtime 庫了。

關於 clang 有一份關於ARC詳細的文檔,有興趣的可以仔細研究一下文檔里面的說明和例子,很有幫助。

以下的講解,也會來自於上述文檔中的函數說明。


  1. __strong 的實現原理

    ①、對象持有自己

    首先我們先來看看生成的對象持有自己的情況,利用 alloc/new/copy/mutableCopy 生成對象。

    當我們聲明了一個 __strong 對象

    {  
        id __strong obj = [[NSObject alloc] init];
    }

    LLVM 編譯器會把上述代碼轉換成下面的樣子

    id __attribute__((objc_ownership(strong))) obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    

    相應的會調用

    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj,selector(init));
    objc_release(obj);

    上述這些方法都好理解。在 ARC 有效的時候就會自動插入 release 代碼,在作用域結束的時候自動釋放。

    ②、對象不持有自己

    生成對象的時候不用 alloc/new/copy/mutableCopy 等方法。

    {  
        id __strong obj = [NSMutableArray array];  
    }

    LLVM 編譯器會把上述代碼轉換成下面的樣子

    id __attribute__((objc_ownership(strong))) array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
    

    查看 LLVM 文檔,其實是下述的過程,相應的會調用

    id obj = objc_msgSend(NSMutableArray, @selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);

    與之前對象會持有自己的情況不同,這里多了一個 objc_retainAutoreleasedReturnValue 函數。

    這里有 3 個函數需要說明:

    1、id objc_retainAutoreleaseReturnValue(id value);

    _Precondition:_ value is null or a pointer to a valid object.

    If value is null, this call has no effect. Otherwise, it performs a retain operation followed by the operation described in objc_autoreleaseReturnValue.

    Equivalent to the following code:

    id objc_retainAutoreleaseReturnValue(id value) {

    return objc_autoreleaseReturnValue(objc_retain(value));

    }

    Always returns value

    2、id objc_retainAutoreleasedReturnValue(id value);

    _Precondition:_ value is null or a pointer to a valid object.

    If value is null, this call has no effect. Otherwise, it attempts to accept a hand off of a retain count from a call to objc_autoreleaseReturnValue on value in a recently-called function or something it calls. If that fails, it performs a retain operation exactly like objc_retain.

    Always returns value

    3、id objc_autoreleaseReturnValue(id value);

    _Precondition:_ value is null or a pointer to a valid object.

    If value is null, this call has no effect. Otherwise, it makes a best effort to hand off ownership of a retain count on the object to a call toobjc_retainAutoreleasedReturnValue for the same object in an enclosing call frame. If this is not possible, the object is autoreleased as above.

    Always returns value

    這 3 個函數其實都是在描述一件事情:it makes a best effort to hand off ownership of a retain count on the object to a call to objc_retainAutoreleasedReturnValue for the same object in an enclosing call frame。

    這屬於 LLVM 編譯器的一個優化。objc_retainAutoreleasedReturnValue 函數是用於自己持有(retain)對象的函數,它持有的對象應為返回注冊在 autoreleasepool 中對象的方法或者是函數的返回值。

    在 ARC 中原本對象生成之后是要注冊到 autoreleasepool 中,但是調用了objc_autoreleasedReturnValue 之后,緊接着調用了 objc_retainAutoreleasedReturnValue,objc_autoreleasedReturnValue 函數會去檢查該函數方法或者函數調用方的執行命令列表,如果里面有objc_retainAutoreleasedReturnValue() 方法,那么該對象就直接返回給方法或者函數的調用方。達到了即使對象不注冊到 autoreleasepool 中,也可以返回拿到相應的對象。

  1. __weak 的實現原理

    聲明一個 __weak 對象

    {  
        id __weak obj = strongObj; // 假設這里的 strongObj 是一個已經聲明好了的對象。
    }

    LLVM 轉換成對應的代碼

    id __attribute__((objc_ownership(none))) obj1 = strongObj;
    

    相應的會調用

    id obj ;
    objc_initWeak(&obj,strongObj);
    objc_destoryWeak(&obj);

    看看文檔描述

    id objc_initWeak(id *object, id value);

    _Precondition:_ object is a valid pointer which has not been registered as a __weak object. value is null or a pointer to a valid object. If value is a null pointer or the object to which it points has begun deallocation, object is zero-initialized. Otherwise, object is registered as a __weak object pointing to value

    Equivalent to the following code:

    id objc_initWeak(id _object, id value) { _

    object = nil;

    return objc_storeWeak(object, value);

    }

    Returns the value of object after the call.Does not need to be atomic with respect to calls to objc_storeWeak on object

    objc_initWeak 的實現其實是這樣的:

    id objc_initWeak(id * object, id value) {
        *object = nil;
    return objc_storeWeak(object, value);
    }

    會把傳入的 object 變成 0 或者 nil,然后執行 objc_storeWeak 函數。

    那么 objc_destoryWeak 函數是干什么的呢?

    void objc_destroyWeak(id *object);

    _Precondition:_ object is a valid pointer which either contains a null pointer or has been registered as a __weak object. object is unregistered as a weak object, if it ever was. The current value of object is left unspecified; otherwise, equivalent to the following code:

    void objc_destroyWeak(id * object) {

    objc_storeWeak(object, nil);

    }

    Does not need to be atomic with respect to calls to objc_storeWeak on object

    objc_destoryWeak 函數的實現:

    void objc_destroyWeak(id * object) {
        objc_storeWeak(object, nil);
    }

    也是會去調用 objc_storeWeak 函數。objc_initWeak 和 objc_destroyWeak 函數都會去調用 objc_storeWeak 函數,唯一不同的是調用的入參不同,一個是 value,一個是 nil。

    那么重點就都落在 objc_storeWeak 函數上了。

    id objc_storeWeak(id *object, id value);

    _Precondition:_ object is a valid pointer which either contains a null pointer or has been registered as a __weak object. value is null or a pointer to a valid object. If value is a null pointer or the object to which it points has begun deallocation, object is assigned null and unregistered as a __weak object. Otherwise, object is registered as a __weak object or has its registration updated to point to value

    Returns the value of object after the call.

    objc_storeWeak 函數的用途就很明顯了。由於 weak 表也是用 Hash table 實現的,所以objc_storeWeak 函數就把第一個入參的變量地址注冊到 weak 表中,然后根據第二個入參來決定是否移除。如果第二個參數為 0,那么就把 __weak 變量從 weak 表中刪除記錄,並從引用計數表中刪除對應的鍵值記錄。

    所以如果 __weak 引用的原對象如果被釋放了,那么對應的 __weak 對象就會被指為 nil。原來就是通過 objc_storeWeak 函數這些函數來實現的。

    以上就是 ARC 中 __strong 和 __weak 的簡單的實現原理,更加詳細的還請大家去看看這一章開頭提到的那個 LLVM 文檔,里面說明的很詳細。


7,3 weakSelf、strongSelf 的用途

在提 weakSelf、strongSelf 之前,我們先引入一個 Retain Cicle 的例子。

假設自定義的一個 student 類

例子 1:

Student.h 文件

#import <Foundation/Foundation.h>

typedef void(^ Study)();

@interface Student : NSObject

@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) Study study;

@end

ViewController.m 文件

#import "ViewController.h"
#import "Student.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{

    [super viewDidLoad];
    
    Student * student = [[Student alloc] init];
    
    student.name = @"Hello World";
    
    student.study = ^{
        NSLog(@"my name is = %@", student.name);
    };

}

到這里,大家應該看出來了,這里肯定出現了循環引用了。student 的 study 的 Block 里面強引用了 student 自身。根據上篇文章的分析,可以知道,_NSConcreteMallocBlock 捕獲了外部的對象,會在內部持有它。retainCount 值會加一。

我們用 Instruments 來觀察一下。添加 Leak 觀察器。

當程序運行起來之后,在 Leak Checks觀察器里面應該可以看到紅色的❌,點擊它就會看到內存 leak 了。有 2 個泄露的對象。Block 和 Student 相互循環引用了。


打開 Cycles & Roots 觀察一下循環的環。


這里形成環的原因 block 里面持有 student 本身,student 本身又持有 block。

那再看一個例子 2:

#import "ViewController.h"
#import "Student.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Student * student = [[Student alloc] init];
    
    student.name  = @"Hello World";
    
    student.study = ^(NSString * name){
        NSLog(@"my name is = %@",name);
    };
    
    student.study(student.name);
    
}

我把 block 新傳入一個參數,傳入的是 student.name。這個時候會引起循環引用么?

答案肯定是不會。


如上圖,並不會出現內存泄露。原因是因為,student 是作為形參傳遞進 block 的,block 並不會捕獲形參到 block 內部進行持有。所以肯定不會造成循環引用。

再改一下。看例子 3:

#import "ViewController.h"
#import "Student.h"

@interface ViewController ()

@property (nonatomic, copy) NSString * name;
@property (nonatomic, strong) Student * stu;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Student * student = [[Student alloc] init];
    
    self.name = @"halfrost";
    self.stu  = student;
    
    student.study = ^{
        NSLog(@"my name is = %@", self.name);
    };

    student.study();
}

這樣會形成循環引用么?


答案也是會的(ARC 環境)。

vc → student → block → vc 已經成環。這里即使是 self.name 也是循環引用了,因為 block 不可能說去單獨的強持有某個實例的變量,這不符合內存管理規則(交叉管理了),但是 instruments 檢測不出來。

(原文寫着沒有循環引用,我在 ARC 環境測試時,dealloc 不會被調用,說明還被引用着。可以自行驗證)。

那遇到循環引用我們改如何處理呢??類比平時我們經常寫的 delegate,可以知道,只要有一邊是 __weak 就可以打破循環。

先說一種做法,利用 __block 解決循環的做法。例子 4:

#import "ViewController.h"
#import "Student.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    Student * student = [[Student alloc] init];
    
    __block Student * stu = student;
    
    student.name = @"Hello World";
    
    student.study = ^{
        NSLog(@"my name is = %@", stu.name);
        stu = nil;
    };
}

這樣寫會循環么?看上去應該不會。但是實際上卻是會的。


由於沒有執行 study 這個 block,現在 student 持有該 block,block 持有 __block 變量,__block 變量又持有 student 對象。3 者形成了環,導致了循環引用了。

想打破環就需要破壞掉其中一個引用。__block 不持有 student 即可。

只需要執行一下 block 即可。例子 5:

student.study();

這樣就不會循環引用了。


使用 __block 解決循環引用雖然可以控制對象持有時間,在 block 中還能動態的控制 __block 變量的值,可以賦值 nil,也可以賦值其他的值,但是有一個唯一的缺點就是需要執行一次 block 才行。否則還是會造成循環引用。

值得注意的是,在 ARC 下 __block 會導致對象被 retain,有可能導致循環引用。而在 MRC 下,則不會 retain 這個對象,也不會導致循環引用。

接下來可以正式開始講講 weakSelf 和 strongSelf 的用法了。

  1. weakSelf

    說道 weakSelf,需要先來區分幾種寫法。

    ①、__weak __typeof(self)weakSelf = self;   // 這是 AFN 里面的寫法。。

    ②、#define WEAKSELF typeof(self) __weak weakSelf = self;

    先區分 __typeof() 和 typeof()

    AFNetWorking 的庫里面的代碼都很整潔,里面各方面的代碼都可以當做代碼范本來閱讀。遇到不懂疑惑的,都要深究,肯定會有收獲。這里就是一處,平時我們的寫法是不帶 __ 的,AFN 里面用這種寫法有什么特殊的用途么?

    在 SOF 上能找到相關的答案

    __typeof__() and __typeof() are compiler-specific extensions to the C language, because standard C does not include such an operator. Standard C requires compilers to prefix language extensions with a double-underscore (which is also why you should never do so for your own functions, variables, etc.)

    typeof() is exactly the same, but throws the underscores out the window with the understanding that every modern compiler supports it. (Actually, now that I think about it, Visual C++ might not. It does support decltype() though, which generally provides the same behaviour as typeof().)

    All three mean the same thing, but none are standard C so a conforming compiler may choose to make any mean something different.

    其實兩者都是一樣的東西,只不過是 C 里面不同的標准,兼容性不同罷了。更加詳細的官方說明

    那么抽象出來就是這 2 種寫法。

    #define WEAKSELF __weak typeof(self)weakSelf  = self;  
    #define WEAKSELF typeof(self) __weak weakSelf = self;
    

    這樣子看就清楚了,兩種寫法就是完全一樣的。

    我們可以用 WEAKSELF 來解決循環引用的問題。例子 6:

    #import "ViewController.h"
    #import "Student.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad
    {
    [super viewDidLoad];
    Student * student = [[Student alloc]init];
    student.name = @"Hello World";
    __weak typeof(student) weakSelf = student;
    student.study = ^{
    NSLog(@"my name is = %@",weakSelf.name);
    };
    student.study();
    }

    這樣就解決了循環引用的問題了。

    解決循環應用的問題一定要分析清楚哪里出現了循環引用,只需要把其中一環加上 weakSelf 這類似的宏,就可以解決循環引用。如果分析不清楚,就只能無腦添加 weakSelf、strongSelf,這樣的做法不可取。

    在上面的例子 3 中,就完全不存在循環引用,要是無腦加 weakSelf、strongSelf 是不對的。在例子 6 中,也只需要加一個 weakSelf 就可以了,也不需要加 strongSelf。

    曾經在 segmentfault 也看到過這樣一個問題,問:為什么 iOS 的 Masonry 中的 self 不會循環引用?

    UIButton * testButton = [[UIButton alloc] init];
    [self.view addSubview:testButton];
    testButton.backgroundColor = [UIColor redColor];
    [testButton mas_makeConstraints:^(MASConstraintMaker * make) {
    make.width.equalTo(@100);
    make.height.equalTo(@100);
    make.left.equalTo(self.view.mas_left);
    make.top.equalTo(self.view.mas_top);
    }];
    [testButton bk_addEventHandler:^(id sender) {
    [self dismissViewControllerAnimated:YES completion:nil];
    } forControlEvents:UIControlEventTouchUpInside];

    如果我用 blocksKit 的 bk_addEventHandler方法,其中使用 strong self,該 viewController 就無法 dealloc,我理解是因為 self → self.view → testButton → self。 但是如果只用 Mansonry 的 mas_makeConstraints方法,同樣使用 strong self,該 viewController 卻能正常 dealloc,請問為什么 Masonry 沒有導致循環引用?

    看到這里,讀者應該就應該能回答這個問題了。

    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
    {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker * maker = [[MASConstraintMaker alloc] initWithView:self];
    block(maker);
    return [maker install];
    }

    關於 Masonry,它捕獲了變量 self,然后對其執行了 setTranslatesAutoresizingMaskIntoConstraints: 方法。但是,因為執行完畢后,block 會被銷毀,沒有形成環。所以,沒有引起循環依賴。

  2. strongSelf

    上面介紹完了 weakSelf,既然 weakSelf 能完美解決 Retain Circle 的問題了,那為何還需要strongSelf 呢?

    還是先從 AFN 經典說起,以下是 AFN 其中的一段代碼:

    #pragma mark - NSOperation
    - (void)setCompletionBlock:(void (^)(void))block
    {
    [self.lock lock];
    if (!block) {
    [super setCompletionBlock:nil];
    }
    else {
    __weak __typeof(self)weakSelf = self;
    [super setCompletionBlock:^ {
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
    dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
    dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
    #pragma clang diagnostic pop
    dispatch_group_async(group, queue, ^{
    block();
    });
    dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
    [strongSelf setCompletionBlock:nil];
    });
    }];
    }
    [self.lock unlock];
    }

    如果 block 里面不加 __strong __typeof(weakSelf)strongSelf = weakSelf 會如何呢?

    #import "ViewController.h"
    #import "Student.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad
    {
    [super viewDidLoad];
    Student * student = [[Student alloc]init];
    student.name = @"Hello World";
    __weak typeof(student) weakSelf = student;
    student.study = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"my name is = %@",weakSelf.name);
    });
    };
    student.study();
    }

    輸出:

    my name is = (null)
    

    為什么輸出是這樣的呢?

    重點就在 dispatch_after 這個函數里面。在 study() 的 block 結束之后,student 被自動釋放了。又由於 dispatch_after 里面捕獲的 __weak 的 student,根據第二章講過的 __weak 的實現原理,在原對象釋放之后 __weak 對象就會變成 null,防止野指針。所以就輸出了 null了。

    那么我們怎么才能在 weakSelf 之后,block里面還能繼續使用 weakSelf 之后的對象呢?

    究其根本原因就是 weakSelf 之后,無法控制什么時候會被釋放,為了保證在 block 內不會被釋放,需要添加 __strong。

    在 block 里面使用的 __strong 修飾的 weakSelf 是為了在函數生命周期中防止 self 提前釋放。strongSelf 是一個自動變量當 block 執行完畢就會釋放自動變量 strongSelf 不會對 self 進行一直進行強引用。

    #import "ViewController.h"
    #import "Student.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad
    {
    [super viewDidLoad];
    Student * student = [[Student alloc] init];
    student.name = @"Hello World";
    __weak typeof(student) weakSelf = student;
    student.study = ^{
    __strong typeof(student) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"my name is = %@",strongSelf.name);
    });
    };
    student.study();
    }

    輸出

    my name is = Hello World
    

至此,我們就明白了 weakSelf、strongSelf 的用途了。

weakSelf 是為了 block 不持有 self,避免 Retain Circle 循環引用。在 Block 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。

strongSelf 的目的是因為一旦進入 block 執行,假設不允許 self 在這個執行過程中釋放,就需要加入 strongSelf。block 執行完后這個 strongSelf 會自動釋放,沒有不會存在循環引用問題。如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。

關於 Retain Circle 最后總結一下,有 3 種方式可以解決循環引用。

結合《Effective Objective-C 2.0》(編寫高質量 iOS 與 OS X 代碼的 52 個有效方法)這本書的例子,來總結一下。

EOCNetworkFetcher.h 文件

typedef void (^ EOCNetworkFetcherCompletionHandler)(NSData * data);

@interface EOCNetworkFetcher : NSObject

@property (nonatomic, strong, readonly) NSURL * url;

- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;

@end

EOCNetworkFetcher.m 文件

@interface EOCNetworkFetcher ()

@property (nonatomic, strong, readwrite) NSURL * url;
@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic, strong) NSData * downloadData;

@end

@implementation EOCNetworkFetcher

- (id)initWithURL:(NSURL *)url
{
    if (self = [super init]) {
        _url = url;
    }
    return self;
}

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion
{
    self.completionHandler = completion;   // 開始網絡請求
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        _downloadData = [[NSData alloc] initWithContentsOfURL:_url]; 
        
        dispatch_async(dispatch_get_main_queue(), ^{  // 網絡請求完成
            [self p_requestCompleted];
        });
    });
}
    
- (void)p_requestCompleted
{
    if(_completionHandler) {
        _completionHandler(_downloadData);
    }
}

@end

EOCClass.m 文件

@implementation EOCClass
{
    NSData * _fetchedData;
    EOCNetworkFetcher * _networkFetcher;
}

- (void)downloadData
{
    NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"];
    
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData * data) {
        _fetchedData = data;
    }];
}

@end

在這個例子中,存在 3 者之間形成環

①、completion handler 的 block 因為要設置 _fetchedData 實例變量的值,所以它必須捕獲 self 變量,也就是說 handler 塊保留了 EOCClass 實例;

②、EOCClass 實例通過 strong 實例變量保留了 EOCNetworkFetcher,最后EOCNetworkFetcher 實例對象也會保留了 handler 的 block。

書上說的 3 種方法來打破循環。

  1. 手動釋放 EOCNetworkFetcher 使用之后持有的 _networkFetcher,這樣可以打破循環引用

    - (void)downloadData
    {
    NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData * data) {
    _fetchedData = data;
    _networkFetcher = nil;   // 加上此行,打破循環引用
    }];
    }
  2. 直接釋放 block。因為在使用完對象之后需要人為手動釋放,如果忘記釋放就會造成循環引用了。如果使用完 completion handler 之后直接釋放 block 即可。打破循環引用

    - (void)p_requestCompleted
    {
    if(_completionHandler) {
    _completionHandler(_downloadData);
    }
    self.completionHandler = nil;  // 加上此行,打破循環引用
    }
  3. 使用 weakSelf、strongSelf

    - (void)downloadData
    {
    __weak __typeof(self) weakSelf = self;
    NSURL * url = [NSURL URLWithString:@"http://www.baidu.com"];
    _networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData * data) {
    __typeof(&*weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
    strongSelf.fetchedData = data;
    }
    }];
    }


7.4 @weakify、@strongify 實現原理

上面講完了 weakSelf、strongSelf 之后,接下來再講講 @weakify、@strongify,這兩個關鍵字是 RAC 中避免 Block 循環引用而開發的 2 個宏,這 2 個宏的實現過程很牛,值得我們學習。

@weakify、@strongify 的作用和 weakSelf、strongSelf 對應的一樣。這里我們具體看看大神是怎么實現這 2 個宏的。

直接從源碼看起來。

#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored "-Wshadow"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")

看到這種宏定義,咋一看什么都不知道。那就只能一層層的往下看。

  1. weakify

    先從 weakify(...) 開始。

    #if DEBUG
    #define rac_keywordify autoreleasepool {}
    #else
    #define rac_keywordify try {} @catch (...) {}
    #endif

    這里在 debug 模式下使用 @autoreleasepool 是為了維持編譯器的分析能力,而使用 @try/@catch 是為了防止插入一些不必要的 autoreleasepool。rac_keywordify 實際上就是autoreleasepool {}的宏替換。因為有了 autoreleasepool {}的宏替換,所以 weakify 要加上 @,形成 @autoreleasepool {}。

    #define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
    metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
    

    __VA_ARGS__:總體來說就是將左邊宏中 ... 的內容原樣抄寫在右邊 __VA_ARGS__ 所在的位置。它是一個可變參數的宏,是新的 C99 規范中新增的,目前似乎只有 gcc支持(VC 從 VC2005 開始支持)。

    那么我們使用 @weakify(self) 傳入進去。__VA_ARGS__ 相當於 self。此時我們可以把最新開始的 weakify 套下來。於是就變成了這樣:

    rac_weakify_,, __weak, __VA_ARGS__ 整體替換 MACRO, SEP, CONTEXT, ...
    

    這里需要注意的是,源碼中就是給的兩個","逗號是連着的,所以我們也要等效替換參數,相當於 SEP 是空值。

    替換完成之后就是下面這個樣子:

    autoreleasepool {} metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)
    

    現在我們需要弄懂的就是 metamacro_concat 和 metamacro_argcount 是干什么用的。

    繼續看看 metamacro_concat 的實現

    #define metamacro_concat(A, B) \
    metamacro_concat_(A, B) #define metamacro_concat_(A, B) A ## B
    

    ## 是宏連接符。舉個例子:

    假設宏定義為 #define XNAME(n) x##n,代碼為:XNAME(4),則在預編譯時,宏發現XNAME(4) 與 XNAME(n) 匹配,則令 n 為 4,然后將右邊的 n 的內容也變為 4,然后將整個XNAME(4) 替換為 x##n,亦即 x4,故最終結果為 XNAME(4) 變為 x4。所以 A##B 就是 AB。

    metamacro_argcount 的實現

    #define metamacro_argcount(...) \
    metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) #define metamacro_at(N, ...) \
    metamacro_concat(metamacro_at, N)(__VA_ARGS__)

    metamacro_concat 是上面講過的連接符,那么 metamacro_at,N = metamacro_atN,由於 N = 20,於是 metamacro_atN = metamacro_at20。

    #define metamacro_at0(...) metamacro_head(__VA_ARGS__)
    #define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
    #define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)

    metamacro_at20 的作用就是截取前 20 個參數,剩下的參數傳入 metamacro_head。

    #define metamacro_head(...) \
    metamacro_head_(__VA_ARGS__, 0)
    #define metamacro_head_(FIRST, ...) FIRST

    metamacro_head 的作用返回第一個參數。返回到上一級 metamacro_at20,如果我們從最源頭的 @weakify(self),傳遞進來,那么 metamacro_at20(self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前 20 個參數,最后一個留給metamacro_head_(1),那么就應該返回 1。

    metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1)
    

    最終可以替換成 metamacro_foreach_cxt1。

    在源碼中繼續搜尋。

    // metamacro_foreach_cxt expansions
    #define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
    #define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
    #define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)
    #define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    SEP \
    MACRO(2, CONTEXT, _2)
    #define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    SEP \
    MACRO(3, CONTEXT, _3)
    #define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    SEP \
    MACRO(4, CONTEXT, _4)
    #define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    SEP \
    MACRO(5, CONTEXT, _5)
    #define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    SEP \
    MACRO(6, CONTEXT, _6)
    #define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    SEP \
    MACRO(7, CONTEXT, _7)
    #define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    SEP \
    MACRO(8, CONTEXT, _8)
    #define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    SEP \
    MACRO(9, CONTEXT, _9)
    #define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    SEP \
    MACRO(10, CONTEXT, _10)
    #define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    SEP \
    MACRO(11, CONTEXT, _11)
    #define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    SEP \
    MACRO(12, CONTEXT, _12)
    #define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    SEP \
    MACRO(13, CONTEXT, _13)
    #define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    SEP \
    MACRO(14, CONTEXT, _14)
    #define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    SEP \
    MACRO(15, CONTEXT, _15)
    #define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    SEP \
    MACRO(16, CONTEXT, _16)
    #define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    SEP \
    MACRO(17, CONTEXT, _17)
    #define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    SEP \
    MACRO(18, CONTEXT, _18)
    #define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
    metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    SEP \
    MACRO(19, CONTEXT, _19)

    metamacro_foreach_cxt 這個宏定義有點像遞歸,這里可以看到 N 最大就是 20,於是metamacro_foreach_cxt19 就是最大,metamacro_foreach_cxt19 會生成rac_weakify_(0,__weak,_18),然后再把前 18 個數傳入 metamacro_foreach_cxt18,並生成rac_weakify_(0,__weak,_17),依次類推,一直遞推到 metamacro_foreach_cxt0。

    #define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
    

    metamacro_foreach_cxt0 就是終止條件,不做任何操作了。

    於是最初的 @weakify 就被替換成

    autoreleasepool {}
    metamacro_foreach_cxt1(rac_weakify_, , __weak, self)
    #define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

    代入參數

    autoreleasepool {} rac_weakify_(0,__weak,self)
    

    最終需要解析的就是 rac_weakify_

    #define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
    

    把 (0,__weak,self) 的參數替換進來 (INDEX, CONTEXT, VAR)。

    INDEX = 0, CONTEXT = __weak,VAR = self,
    

    於是

    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
    

    等效替換為

    __weak __typeof__(self) self_weak_ = self;
    

    最終 @weakify(self) = __weak __typeof__(self) self_weak_ = self; 這里的 self_weak_ 就完全等價於我們之前寫的 weakSelf。

  2. strongify

    再繼續分析 strongify(...)

    rac_keywordify 還是和 weakify 一樣,是 autoreleasepool {},只為了前面能加上 @

    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored "-Wshadow"") \
    _Pragma("clang diagnostic pop")

    strongify 比 weakify 多了這些 _Pragma 語句。

    關鍵字 _Pragma 是 C99 里面引入的。_Pragma 比 #pragma(在設計上)更加合理,因而功能也有所增強。

    上面的等效替換

    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wshadow"
    #pragma clang diagnostic pop

    這里的 clang 語句的作用:忽略當一個局部變量或類型聲明遮蓋另一個變量的警告。

    最初的

    #define strongify(...) \
    rac_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored "-Wshadow"") \
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

    strongify 里面需要弄清楚的就是 metamacro_foreach 和 rac_strongify_。

    #define metamacro_foreach(MACRO, SEP, ...) \
    metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
    #define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

    我們先替換一次,SEP = 空 , MACRO = rac_strongify_ , __VA_ARGS__ ,  於是替換成這樣。

    metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)
    

    根據之前分析,metamacro_foreach_cxt 再次等效替換,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify_,self)

    根據

    #define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
    

    再次替換成 metamacro_foreach_iter(0, rac_strongify_, self)

    繼續看看 metamacro_foreach_iter 的實現

    #define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)
    

    最終替換成 rac_strongify_(0,self)

    #define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
    

    INDEX = 0, VAR = self, 於是 @strongify(self) 就等價於

    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
    

    等價於

    __strong __typeof__(self) self = self_weak_;
    

    注意 @strongify(self) 只能使用在 block 中,如果用在 block 外面,會報錯,因為這里會提示你 Redefinition of 'self'

    實例

    #ifndef weakify
    #if DEBUG   // 判斷當前代碼運行模式
    #if __has_feature(objc_arc)  // 判斷 ARC 環境
    #define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;     // ## 為連接符
    #else
    #define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
    #endif      // 結束 if _has_feature()
    #else
    #if __has_feature(objc_arc)
    #define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
    #else
    #define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
    #endif      // 結束 if _has_feature()
    #endif      // 結束 if DEBUG
    #endif      // 結束 ifndef weakify
    #ifndef strongify
    #if DEBUG
    #if __has_feature(objc_arc)
    #define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
    #else
    #define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
    #endif
    #else
    #if __has_feature(objc_arc)
    #define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
    #else
    #define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;、#endif
    #endif
    #endif
  3. 總結

    @weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
    @strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
    

    經過分析以后,其實 @weakify(self) 和 @strongify(self) 就是比我們日常寫的 weakSelf、strongSelf 多了一個 @autoreleasepool{} 而已,至於為何要用這些復雜的宏定義來做,目前我還沒有理解。如果有大神指導其中的原因,還請多多指點。

八、問題

  1. block 原理是什么?本質是什么?

    封裝了函數調用以及調用環境的 OC 對象。

  2. Block 屬性的修飾詞為什么是 copy?__block 的作用是什么?有什么注意的點?

    一旦沒有進行 copy 操作,block 就不會在堆上。__block 能夠修改自動變量的值。注意循環引用。

  3. block 修改 NSMutableArray 時,是否需要添加 __block?

    {
        NSMutableArray * mArr1 = [NSMutableArray arrayWithObjects:@"a", @"b", @"abc", nil];
    NSMutableArray * mArr2 = [NSMutableArray arrayWithCapacity:mArr1.count];
    [mArr1 enumerateObjectsUsingBlock: ^(NSString * obj, NSUInteger idx, BOOL *stop){
    [mArr2 addObject:@(obj.length)];
    }];
    NSLog(@"%@", mArr2);
    }
    2018-12-03 00:24:41.754700+0800 Demo[13081:1899280] (
    1,
    1,
    3
    )

    這里確實沒有修改 mArr2 這個局部變量。mArr2 是一個指針,指向一個可變長度的數組。在 block 里面,並沒有修改這個指針,而是修改了這個指針指向的數組。換句話說,mArr2 保存的是一塊內存區域的地址,在 block 里,並沒有改變這個地址,而是讀取出這個地址,然后去操作這塊地址空間的內容。 因為聲明 block 的時候實際上是把當時的臨時變量又復制了一份,在 block 里即使修改了這些復制的變量,也不影響外面的原始變量。即所謂的閉包。 但是當變量是一個指針的時候,block 里只是復制了一份這個指針,兩個指針指向同一個地址。所以,在 block 里面對指針指向內容做的修改,在 block 外面也一樣生效。  

九、學習文章

寧夏灼雪__ & iOS底層day6 - 探索block
http://blog.csdn.net/qq_30513483/article/details/52587551


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM