談block款熬、__weak和__strong

最近在"翻新"公司的老項(xiàng)目的時(shí)候挣柬,發(fā)現(xiàn)一個(gè)奇怪的問(wèn)題:

在一個(gè) block 中钥顽,我使用了 RAC 為了避免 block 循環(huán)引用而定義的兩個(gè)宏: @weakify@strongify庇忌,但是如果在 block 內(nèi)部使用下劃線屬性(成員變量)舞箍,還是會(huì)導(dǎo)致循環(huán)引用。

很多人都知道怎么處理這個(gè)問(wèn)題皆疹,在使用了@weakify@strongify的情況下疏橄,在 block 內(nèi)部像self -> ivar這樣使用成員變量就可以避免循環(huán)引用了,但是為什么這樣用就沒(méi)問(wèn)題呢略就?使用了@weakify@strongify兩個(gè)宏之后發(fā)生了什么呢软族?帶著你在使用 block 時(shí)出現(xiàn)過(guò)的疑問(wèn),在后面的內(nèi)容中你可能會(huì)得到答案残制。

block是什么

block 是用于創(chuàng)建匿名函數(shù)的 C 語(yǔ)言擴(kuò)展立砸。用戶使用 block 指針與 block 對(duì)象進(jìn)行交互并傳輸 block 對(duì)象,block 指針表示為普通指針初茶。block 可以從局部變量中捕獲值;發(fā)生這種情況時(shí)颗祝,必須動(dòng)態(tài)分配內(nèi)存。初始分配在棧上完成恼布,但 runtime 提供了一個(gè)Block_copy函數(shù)螺戳,給定一個(gè) block 指針,將底層 block 對(duì)象復(fù)制到堆中折汞,將其引用計(jì)數(shù)設(shè)置為1并返回新的 block 指針倔幼,或者(如果 block 對(duì)象已經(jīng)在堆上)將其引用計(jì)數(shù)增加1.配對(duì)函數(shù)是Block_release,它將引用計(jì)數(shù)減少1并在計(jì)數(shù)達(dá)到零并且在堆上時(shí)銷(xiāo)毀對(duì)象爽待。翻譯自蘋(píng)果文檔

上面的翻譯來(lái)自于 谷歌翻譯~损同。我對(duì)于 block 的理解就是一個(gè)指針翩腐,指向一個(gè)帶有函數(shù)指針 (用于執(zhí)行block內(nèi)的代碼) 的結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)有許多捕獲的成員變量膏燃。在 ARC 環(huán)境下 block 會(huì)從 棧中自動(dòng)復(fù)制到堆中茂卦,方便 runtime 管理內(nèi)存生命周期;如果內(nèi)部有全局變量則復(fù)制到數(shù)據(jù)區(qū)组哩,生命周期為程序創(chuàng)建到程序結(jié)束等龙。

[站外圖片上傳中...(image-1d1a52-1561035272667)]

block的數(shù)據(jù)結(jié)構(gòu)

block 的數(shù)據(jù)結(jié)構(gòu)定義如下
[站外圖片上傳中...(image-f5b34b-1561035272667)]

結(jié)構(gòu)體定義:

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

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

通過(guò)它的數(shù)據(jù)結(jié)構(gòu),我們知道一個(gè) block 實(shí)際上是由5部分組成的

  1. isa 指針伶贰,所有對(duì)象都有該指針蛛砰,用于實(shí)現(xiàn)對(duì)象相關(guān)的功能
  2. flags,用于按 bit 位表示一些 block 的附加信息
  3. reserved黍衙,保留變量
  4. invoke暴备,函數(shù)指針,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址
  5. descriptor们豌, 表示該 block 的附加描述信息,主要是 size 大小浅妆,以及 copy 和 dispose 函數(shù)的指針

block的幾種的類(lèi)型

常見(jiàn)的 block 有下面三種望迎,不用類(lèi)型的 block 存放不同的區(qū)域,在 ARC 環(huán)境下只有_NSConcreteGlobalBlock_NSConcreteMallocBlock兩種類(lèi)型的 block

  • _NSConcreteGlobalBlock 全局的靜態(tài) block凌外,不會(huì)訪問(wèn)任何外部變量辩尊。
  • _NSConcreteStackBlock 保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀康辑。
  • _NSConcreteMallocBlock 保存在堆中的 block摄欲,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷(xiāo)毀。

下面是詳細(xì)的介紹

_NSConcreteStackBlock

該類(lèi)型的 block 僅存在在 MRC 環(huán)境中疮薇,數(shù)據(jù)存放在棧區(qū)胸墙,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀。在 ARC 環(huán)境中按咒,不存在_NSConcreteStackBlock類(lèi)型迟隅,只存在_NSConcreteGlobalBlock_NSConcreteMallocBlock 兩個(gè)類(lèi)型。在下面的例子中励七, block 的類(lèi)型的打印結(jié)果是__NSMallocBlock__智袭。原因可能是因?yàn)閏語(yǔ)言的結(jié)構(gòu)體中,編譯器不能很好地管理初始化和銷(xiāo)毀掠抬,這樣對(duì)內(nèi)存管理來(lái)說(shuō)很不方便吼野,所以就將 block 放到堆上,使用 runtime 來(lái)管理它們的生命周期两波。

int val = 1;
    
void(^textBlock)(void) = ^{
    NSLog(@"[block] val<%p>: %d", &val, val);
    NSLog(@"val: %d", val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"textBlock: %@", textBlock);

打印結(jié)果為:

val<0x16b523d1c>: 1
[block] val<0x280a9fcb0>: 1
textBlock: <__NSMallocBlock__: 0x28076a4c0>

下面使用 clang -rewrite-objc filename 將代碼轉(zhuǎn)換成 C++ 的實(shí)現(xiàn), 下面是關(guān)鍵部分的代碼

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  int val;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy

    }

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
    static int static_v = 1;
    int val = 1;

    void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, val));
    ((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);

}

  • 其中__MyObject__test_block_impl_0是 block 的結(jié)構(gòu)體類(lèi)型
  • __MyObject__test_block_func_0 是 block 實(shí)現(xiàn)的函數(shù)瞳步,在 __MyObject__test_block_impl_0內(nèi)有一個(gè)指針FuncPtr指向該函數(shù)
  • __MyObject__test_block_desc_0 是 block 附件描述信息的結(jié)構(gòu)體闷哆,包含著 block 結(jié)構(gòu)體大小, copy 和 dispose 函數(shù)指針(這兩個(gè)函數(shù)后面后講到)等的描述信息谚攒,在 __MyObject__test_block_impl_0內(nèi)有一個(gè)指針Desc指向該結(jié)構(gòu)體
  • _I_MyObject_test函數(shù)內(nèi)可以看到 block 的初始化阳准,void(*textBlock)(void)說(shuō)明 textBlock 是一個(gè)指向該 block 結(jié)構(gòu)體的指針

首先觀察這個(gè)__MyObject__test_block_impl_0的結(jié)構(gòu)體:

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  int val;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  • 使用 clang 轉(zhuǎn)換過(guò)的實(shí)現(xiàn)是 MRC 環(huán)境的,所以 isa 指針指向 _NSConcreteStackBlock 類(lèi)型
  • 在這個(gè)結(jié)構(gòu)體中可以看到一個(gè)成員變量int val;馏臭,沒(méi)錯(cuò)野蝇,它就是 block 捕獲的局部變量,從構(gòu)造函數(shù) __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int _val, int flags=0) : val(_val) 中可以看到括儒,block 僅僅捕獲了該變量的值
  • __MyObject__test_block_impl_0中由于增加了一個(gè)變量 val绕沈,所以結(jié)構(gòu)體的大小變大了,結(jié)構(gòu)體大小被寫(xiě)在了__MyObject__test_block_desc_0
  • block 捕獲外部變量?jī)H僅只 block 閉包里面會(huì)用到的值帮寻,其他用不到的值乍狐,它并不會(huì)去捕獲。

再看一下__MyObject__test_block_func_0這個(gè)函數(shù)的實(shí)現(xiàn):

static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy

    }

我們可以發(fā)現(xiàn)固逗,系統(tǒng)自動(dòng)給我們加上的注釋?zhuān)琤ound by copy浅蚪,自動(dòng)變量 val 雖然被捕獲進(jìn)來(lái)了,但是是用 __cself->val 來(lái)訪問(wèn)的烫罩。block 僅僅捕獲了 val 的值惜傲,并沒(méi)有捕獲 val 的內(nèi)存地址。所以在__MyObject__test_block_func_0 這個(gè)函數(shù)中即使我們重寫(xiě)這個(gè)自動(dòng)變量 val 的值贝攒,依舊沒(méi)法去改變block外面變量 val 的值盗誊。

小結(jié)一下:
基本數(shù)據(jù)類(lèi)型的變量是以值傳遞方式傳遞到 block 的構(gòu)造函數(shù)里面去的。block 只捕獲 block 中會(huì)用到的變量隘弊。由于只捕獲了自動(dòng)變量的值哈踱,并非內(nèi)存地址,所以 block 內(nèi)部不能改變變量的值梨熙。

_NSConcreteMallocBlock

修改一下上面的代碼:

 __block int val = 1;
void(^textBlock)(void) = ^{
    val++;
    NSLog(@"[block] val<%p>: %d", &val, val);
};
NSLog(@"val<%p>: %d", &val, val);
textBlock();
NSLog(@"val<%p>: %d", &val, val);
NSLog(@"textBlock: %@", textBlock);

打印輸出為:

val<0x282db0858>: 1
[block] val<0x282db0858>: 2
val<0x282db0858>: 2
textBlock: <__NSMallocBlock__: 0x2823d3450>

重新用 clang 生成的c++實(shí)現(xiàn)

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_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;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val)++;
    }
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
  void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
    void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}

在重新生成的代碼中开镣,我們看到新增了一個(gè)名為__Block_byref_val_0的結(jié)構(gòu)體,它是用來(lái)替代我們__block修飾的變量 val 的咽扇。

  • 它的第一個(gè)指針是 isa哑子,說(shuō)明它也是一個(gè)對(duì)象。
  • 第二個(gè)指針是指向自身類(lèi)的指針__forwarding
  • 第三個(gè)是一個(gè)標(biāo)記 flag
  • 第四個(gè)是結(jié)構(gòu)體的大小
  • 第五個(gè)是變量 val 的值

在函數(shù)static void _I_MyObject_test(MyObject * self, SEL _cmd)我們可以看到該結(jié)構(gòu)體的初始化代碼:

    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};


  • 在初始化時(shí)肌割, isa 指向了一個(gè)空指針
  • __forwarding指向了自己的地址
  • 1是變量 val 的值卧蜓。

使用 __block修飾的變量,無(wú)論是基本數(shù)據(jù)類(lèi)型還是 OC 的類(lèi)把敞,在編譯之后都是轉(zhuǎn)換成一個(gè)新的結(jié)構(gòu)體弥奸,該結(jié)構(gòu)體的__forwarding指針會(huì)指向自己的地址,而成員變量 val 則為編譯前的類(lèi)型和值奋早。至于這樣的目的是什么盛霎,可以接著看下面赠橙。


static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
  void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

__MyObject__test_block_desc_0這個(gè)結(jié)構(gòu)體中,我們發(fā)現(xiàn)比之前的代碼多了一個(gè) copydispose 的函數(shù)指針愤炸。在c語(yǔ)言的結(jié)構(gòu)體中期揪,編譯器沒(méi)有很好地進(jìn)行初始化和銷(xiāo)毀,這樣對(duì)內(nèi)存管理來(lái)說(shuō)很不方便规个,所以就在增加了這兩個(gè)函數(shù)指針凤薛,方便進(jìn)行內(nèi)存管理。copy函數(shù)把block從棧上拷貝到堆上诞仓,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷(xiāo)毀掉缤苫。

  • copydispose這兩個(gè)函數(shù)指針對(duì)應(yīng)的兩個(gè)函數(shù)實(shí)現(xiàn)
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}


  • __MyObject__test_block_copy_0函數(shù)實(shí)現(xiàn)中出現(xiàn)了方法_Block_object_assign,
  • __MyObject__test_block_dispose_0函數(shù)實(shí)現(xiàn)中出現(xiàn)了方法_Block_object_dispose

下面是這兩個(gè)方法的申明:

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

下面是這兩個(gè)方法的實(shí)現(xiàn):

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;
}

void _Block_release(void *arg) {
    // 1
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    
    // 2
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
    
    // 3
    if (newCount > 0) return;
    
    // 4
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }
    
    // 5
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }
    
    // 6
    else {
        printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
    }
}

  • _Block_copy_internalBlock_copy的一個(gè)實(shí)現(xiàn)墅拭,實(shí)現(xiàn)了從_NSConcreteStackBlock復(fù)制到_NSConcreteMallocBlock的過(guò)程活玲,有9個(gè)步驟。
    • 在第8步中我們可以看到 isa 指針指向了_NSConcreteMallocBlock
  • _Block_releaseBlock_release的一個(gè)實(shí)現(xiàn)谍婉,實(shí)現(xiàn)了一個(gè)block釋放的過(guò)程舒憾,有6個(gè)步驟

扯的有點(diǎn)遠(yuǎn)了,現(xiàn)在讓我們總結(jié)一下 __block 修飾的變量在block內(nèi)發(fā)生了什么穗熬。

  • block 會(huì)在棧中被創(chuàng)建镀迂,然后通過(guò)Block_copy函數(shù)復(fù)制到堆中。由 runtime 管理它的生命周期
  • 使用 __block 修飾的變量死陆,在編譯后會(huì)變成一個(gè)新的對(duì)象。在初始化時(shí)唧瘾,成員變量__forwarding會(huì)指向棧中該變量的地址措译,val 為該變量原本的值。當(dāng) block 的成員變量 __Block_byref_val_0從棧中復(fù)制到堆中時(shí)饰序,成員變量 __Block_byref_val_0的地址可能改變了领虹,但是 __forwarding指針指向的結(jié)構(gòu)體是不會(huì)變的,仍然在棧中求豫。
  • block 的實(shí)現(xiàn)函數(shù)__MyObject__test_block_func_0塌衰,block 通過(guò) __Block_byref_val_0 *val = __cself->val;(val->__forwarding->val)++ 變量的地址修改 val,所以在 block 內(nèi)部修改變量 val 是會(huì)影響到 block 外部的變量蝠嘉。
  • 這就是為什么 block 內(nèi)部和外部 val 的地址不同的原因(一個(gè)在棧上最疆,一個(gè)在堆上)。因?yàn)樗麄?code>__forwarding指向的結(jié)構(gòu)體是一樣的蚤告,所以在 block 內(nèi)部修改變量會(huì)影響到外部努酸,

_NSConcreteGlobalBlock

block 內(nèi)部只用到全局變量,包括全局變量杜恰,靜態(tài)全局變量获诈,靜態(tài)變量仍源,以及上述 block 的 copy 版本。數(shù)據(jù)存放在數(shù)據(jù)區(qū)舔涎,生命周期從應(yīng)用創(chuàng)建到應(yīng)用結(jié)束笼踩。

int global_v = 1;
static int static_global_v = 1;

@implementation MyObject

- (void)test
{
    static int static_v = 1;
        
    NSLog(@"val<%p>: %d", &static_v, static_v);
    NSLog(@"global_v<%p>: %d", &global_v, global_v);
    NSLog(@"static_global_v<%p>: %d", &static_global_v, static_global_v);
    
    void(^textBlock)(void) = ^{
        static_v++;
        global_v++;
        static_global_v++;
        NSLog(@"[block] val<%p>: %d", &static_v, static_v);
        NSLog(@"[block] global_v<%p>: %d", &global_v, global_v);
        NSLog(@"[block] static_global_v<%p>: %d", &static_global_v, static_global_v);
    };
    textBlock();
    NSLog(@"textBlock: %@", textBlock);
}

打印信息為:

val<0x1034b8114>: 1
global_v<0x1034b8110>: 1
static_global_v<0x1034b8118>: 1

[block] val<0x1034b8114>: 2
[block] global_v<0x1034b8110>: 2
[block] static_global_v<0x1034b8118>: 2

textBlock: <__NSGlobalBlock__: 0x10343da40>

clang 之后 C++ 實(shí)現(xiàn):

int global_v = 1;
static int static_global_v = 1;


struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  int *static_v;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, int *_static_v, int flags=0) : static_v(_static_v) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  int *static_v = __cself->static_v; // bound by copy

        (*static_v)++;
        global_v++;
        static_global_v++;        
    }

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0)};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {
    static int static_v = 1;
}

  • block 僅僅捕獲了靜態(tài)變量 static_v 的地址作為自己的成員變量,因此在內(nèi)部修改該變量可以影響到 block 外部亡嫌。block 內(nèi)部和外部該變量的地址相等
  • 全局變量 global_v 和全局靜態(tài)變量 static_global_v 并沒(méi)有被 block 捕獲嚎于,因?yàn)樗麄円呀?jīng)被保存在數(shù)據(jù)區(qū)中,可以直接使用

由于 clang 改寫(xiě)的方式跟 LLVM 不太一樣昼伴,在這里并沒(méi)有開(kāi)啟ARC匾旭,所以這里我們看到 isa 指向的還是 _NSConcreteStackBlock,但在開(kāi)啟ARC的時(shí)候圃郊,block 應(yīng)該是 _NSConcreteGlobalBlock 類(lèi)型价涝。

block 與 self

在前面的部分,我們已經(jīng)分析過(guò) 局部變量持舆,靜態(tài)變量色瘩,全局變量,全局靜態(tài)變量在 block 時(shí)的情況逸寓,那么居兆,還有一種特殊的變量 self,它在 block 內(nèi)部時(shí)又是怎么樣運(yùn)行的呢竹伸?

@interface MyObject () {
    NSString *_age;
}
@property (nonatomic, strong) NSString *name;
@end

@implementation MyObject

- (void)test
{
    
    self.name = @"n";
    _age = @"10";
    NSLog(@"self: %@", self);

    void(^textBlock)(void) = ^{
        self.name = @"a";
        _age = @"11";
        
        NSLog(@"[block] self: %@", self);
        NSLog(@"[block] name<%p>: %@", self.name, self.name);
        NSLog(@"[block] age<%p>: %@", _age, _age);
    };
    NSLog(@"name<%p>: %@", self.name, self.name);
    NSLog(@"age<%p>: %@", _age, _age);
    textBlock();
    NSLog(@"name<%p>: %@", self.name, self.name);
    NSLog(@"age<%p>: %@", _age, _age);
}

打印結(jié)果:

name<0x102804818>: n
age<0x102804838>: 10
[block] name<0x102804858>: a
[block] age<0x102804878>: 11
name<0x102804858>: a
age<0x102804878>: 11

clang之后的C++實(shí)現(xiàn)

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  MyObject *self;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *self = __cself->self; // bound by copy

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
        (*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;

    }
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
  void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_0);
    (*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_1;

    void(*textBlock)(void) = ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)textBlock)->FuncPtr)((__block_impl *)textBlock);
}

  • __MyObject__test_block_impl_0中我們可以看到self也被 block 捕獲成了成員變量
  • __MyObject__test_block_impl_0的構(gòu)造函數(shù)中我們可以看到 self 被當(dāng)做參數(shù)被傳入泥栖,而不是 self 的地址
  • 因?yàn)?block 在內(nèi)部和外部 self 指向的是相同的 MyObject 結(jié)構(gòu)體,所以在 block 內(nèi)部對(duì) self 成員變量進(jìn)行修改會(huì)影響到 block 外部
  • block 的結(jié)構(gòu)體會(huì)強(qiáng)引用 self勋篓,所以需要小心使用吧享,否則會(huì)引起循環(huán)應(yīng)用
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *self = __cself->self; // bound by copy

((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_2);
(*(NSString **)((char *)self + OBJC_IVAR_$_MyObject$_age)) = (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_2519b6_mi_3;
}

block 內(nèi)部使用屬性和成員變量是不一樣的。直接使用屬性時(shí)譬嚣,走的是 obj_msgSend 消息發(fā)送(具體可以研究這篇博客)钢颂,而在使用成員變量時(shí),應(yīng)該是先通過(guò) self 得到結(jié)構(gòu)體的首地址拜银,然后通過(guò)成員變量的偏移量然直接使用這個(gè)成員變量(其實(shí)我也沒(méi)很理解殊鞭。。尼桶。)

小結(jié)一下:

  • block 內(nèi)部使用 self 時(shí)的情況跟使用局部變量的情況是比較類(lèi)似的操灿,block 會(huì)捕獲 self 的值而不是地址當(dāng)做成員變量
  • 在 block 內(nèi)部使用屬性和成員變量的情況是不一樣的

__weak與__strong

我們都知道使用__weak和__strong修飾符可以避免在block的使用中出現(xiàn)循環(huán)引用的問(wèn)題,這是為什么呢泵督?先讓我們了解一下這兩個(gè)修飾符吧牲尺!

ARC 環(huán)境下,OC的對(duì)象面前都需要加上所有權(quán)的修飾符,所有的修飾符有以下4種

  • __strong修飾符
  • __weak修飾符
  • __unsafe_unretained修飾符
  • __autoreleasing修飾符

默認(rèn)的修飾符是__strong谤碳。

ARC下溃卡,self既不是strong也不是weak,而是unsafe_unretained的蜒简,也就是說(shuō)瘸羡,入?yún)⒌膕elf被表示為:(init系列方法的self除外)來(lái)源:博客

- (void)start {
   const __unsafe_unretained MyObject *self;
}

想要弄清__weak與__strong的實(shí)現(xiàn)原理,需要研究一下clang中關(guān)于ARC的文檔搓茬,有興趣可以點(diǎn)進(jìn)去仔細(xì)看看犹赖。

__strong

    id __strong object = [[NSObject alloc] init];

在終端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m轉(zhuǎn)換成 C++ 的實(shí)現(xiàn)

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

= 右邊的代碼意思應(yīng)該是對(duì) NSObject 這個(gè)類(lèi)發(fā)送 alloc 消息,然后再對(duì)生成的對(duì)象發(fā)送 init 消息卷仑,這兩個(gè)方法的實(shí)現(xiàn)可以在 runtime 中找到峻村,代碼我也貼到下面了
= 左邊的代碼,我不大理解objc_ownership這個(gè)函數(shù)锡凝,查了下搜不到是啥意思粘昨,看字面意思應(yīng)該是兩個(gè)對(duì)象間的持有關(guān)系,也就是自己持有自己的意思窜锯。

+ alloc
{
    return (*_zoneAlloc)((Class)self, 0, malloc_default_zone()); 
}
- init
{
    return self;
}

__weak

    id __strong object = [[NSObject alloc] init];
    id __weak weakSelf = object;

在終端使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m轉(zhuǎn)換成 C++ 實(shí)現(xiàn)

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

相應(yīng)的會(huì)調(diào)用

objc_initWeak(&weakSelf,object);
objc_destoryWeak(&weakSelf);

objc_initWeak方法的文檔說(shuō)明
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);
}

這個(gè)函數(shù)會(huì)把傳入的 object 置為nil张肾,然后執(zhí)行objc_storeWeak函數(shù)。


那么objc_storeWeak函數(shù)是干什么的呢锚扎?下面是這個(gè)方法的說(shuō)明

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函數(shù)的用途就很明顯了吞瞪。由于weak表也是用Hash table實(shí)現(xiàn)的,所以objc_storeWeak函數(shù)就把第一個(gè)入?yún)⒌淖兞康刂纷?cè)到weak表中驾孔,然后根據(jù)第二個(gè)入?yún)?lái)決定是否移除芍秆。如果第二個(gè)參數(shù)為0,那么就把__weak變量從weak表中刪除記錄翠勉,并從引用計(jì)數(shù)表中刪除對(duì)應(yīng)的鍵值記錄

所以如果__weak引用的原對(duì)象如果被釋放了妖啥,那么對(duì)應(yīng)的__weak對(duì)象就會(huì)被指為nil。原來(lái)就是通過(guò)objc_storeWeak函數(shù)這些函數(shù)來(lái)實(shí)現(xiàn)的眉菱。


接下來(lái)是 objc_destoryWeak 函數(shù)的實(shí)現(xiàn)

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

還是調(diào)用上面的objc_storeWeak函數(shù)迹栓,因?yàn)閭魅氲膙alue為nil掉分,所以object將從weak表中刪除并且置為nil

__weak與__strong的作用

終于講到這兩個(gè)所有權(quán)修飾符的作用了俭缓。


首先是不使用這兩個(gè)修飾符時(shí)的情況。在上面我們已經(jīng)講到過(guò) block 存在 self 的一種情況了酥郭,下面我們要講一下 block 存在 self 并且 self 強(qiáng)應(yīng)用 block 時(shí)的情況

@interface MyObject ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject
- (void)test
{
    self.textBlock = ^{
        self.name = @"n";
    }
}

@end

@implementation OneViewController 

- (void)viewDidLoad {
    [super viewDidLoad];

    self.object = [[MyObject alloc] init];
    [self.object test]; 
}


對(duì)于 MyObject 來(lái)說(shuō)是造成了循環(huán)引用的华坦,因?yàn)樗鼜?qiáng)引用了 block,而 block 內(nèi)部也強(qiáng)引用著 self不从,所以 MyObject 是不能被dealloc的惜姐,但奇怪的是,將 MyObject 當(dāng)做屬性的 OneViewController 竟然可以dealloc,這估計(jì)是另一個(gè)問(wèn)題了歹袁,等我有空再去研究一下這個(gè)坷衍。。条舔。

使用 clang 得到的C++實(shí)現(xiàn)枫耳,這邊只截取了block結(jié)構(gòu)體和初始化block部分

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  MyObject *self;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 初始化
((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, self, 570425344)

在這個(gè)部分中可以看到 block 將 self(MyObject *指針)捕獲成了自己的成員變量了(強(qiáng)引用), 而self指針的成員變量又包含block,造成循環(huán)引用孟抗。


僅僅使用__weak

@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject

- (void)test
{
    __weak typeof(self) weakSelf = self;
    self.textBlock = ^{
        weakSelf.name = @"n";
        NSLog(@"hh");
    };
    self.textBlock();
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m得到C++實(shí)現(xiàn)

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  MyObject *const __weak weakSelf;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy

        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)weakSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_970d18_mi_0);
    }
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
  void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

    __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}

蘋(píng)果使用一個(gè)全局的 weak 表來(lái)保存所有的 weak 引用迁杨。并將對(duì)象作為鍵,weak_entry_t 作為值凄硼。weak_entry_t 中保存了所有指向該對(duì)象的 weak 指針铅协。當(dāng)被指向的對(duì)象執(zhí)行 dealloc 時(shí)候,將所有指向該對(duì)象的 weak 指針的設(shè)置為nil摊沉。

  • block 將 __weak 修飾的 self 捕獲為成員變量
  • 當(dāng) self 執(zhí)行dealloc時(shí)狐史,block 內(nèi)的 self 置為nil,從而打破循環(huán)引用
  • 當(dāng) self delloac 之后坯钦,在調(diào)用 block 的函數(shù)指針预皇,block 內(nèi)部的self置為nil。

同時(shí)使用__weak與__strong

@interface MyObject ()
//{
//    NSString *_age;
//}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) void(^textBlock)(void);
@end

@implementation MyObject

- (void)test
{
    __weak typeof(self) weakSelf = self;
    self.textBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        strongSelf.name = @"n";
        NSLog(@"hh");
    };
    self.textBlock();
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 MyObject.m得到C++實(shí)現(xiàn)

struct __MyObject__test_block_impl_0 {
  struct __block_impl impl;
  struct __MyObject__test_block_desc_0* Desc;
  MyObject *const __weak weakSelf;
  __MyObject__test_block_impl_0(void *fp, struct __MyObject__test_block_desc_0 *desc, MyObject *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MyObject__test_block_func_0(struct __MyObject__test_block_impl_0 *__cself) {
  MyObject *const __weak weakSelf = __cself->weakSelf; // bound by copy

        __attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)strongSelf, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_xy_nncr8hn96cd0_rdymw2f0qcw0000gn_T_MyObject_0010b9_mi_0);
    }
static void __MyObject__test_block_copy_0(struct __MyObject__test_block_impl_0*dst, struct __MyObject__test_block_impl_0*src) {_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __MyObject__test_block_dispose_0(struct __MyObject__test_block_impl_0*src) {_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __MyObject__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MyObject__test_block_impl_0*, struct __MyObject__test_block_impl_0*);
  void (*dispose)(struct __MyObject__test_block_impl_0*);
} __MyObject__test_block_desc_0_DATA = { 0, sizeof(struct __MyObject__test_block_impl_0), __MyObject__test_block_copy_0, __MyObject__test_block_dispose_0};

static void _I_MyObject_test(MyObject * self, SEL _cmd) {

    __attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setTextBlock:"), ((void (*)())&__MyObject__test_block_impl_0((void *)__MyObject__test_block_func_0, &__MyObject__test_block_desc_0_DATA, weakSelf, 570425344)));
    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("textBlock"))();
}

  • __MyObject__test_block_impl_0 block 仍然是將 __weak 修飾的 self 捕獲為成員變量
  • 當(dāng) self 執(zhí)行dealloc時(shí)婉刀,block 內(nèi)的 self 會(huì)被置為nil吟温,從而打破循環(huán)引用
  • block 內(nèi)的代碼在__MyObject__test_block_func_0函數(shù)內(nèi),當(dāng)使用strongSelf時(shí)突颊,會(huì)先取出__weak修飾的成員變量self:MyObject *const __weak weakSelf = __cself->weakSelf;, 然后再生成一個(gè)__strong修飾的局部變量__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;鲁豪,self 的引用計(jì)數(shù) +1。這樣的目的是在 block 內(nèi)的代碼塊執(zhí)行完之前避免 self 被dealloc掉律秃。當(dāng) block 執(zhí)行完畢之后爬橡,局部變量 strongSelf 被釋放,self 的引用計(jì)數(shù) -1棒动。

@weakify 和 @strongify

這兩個(gè)是RAC中避免Block循環(huán)引用而開(kāi)發(fā)的2個(gè)宏糙申,實(shí)現(xiàn)過(guò)程很牛,值得我們學(xué)習(xí)船惨。限于篇幅柜裸,我就不分析了,想了解可以點(diǎn)開(kāi)這篇博客粱锐。
這兩個(gè)宏展開(kāi)下來(lái)就相當(dāng)于:

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


回到開(kāi)頭

好了疙挺,不知道你看了這么多頭暈了沒(méi)有。怜浅。铐然。下面讓我們回到開(kāi)頭我碰到的那個(gè)問(wèn)題蔬崩,為什么我使用了 @weakify 和 @strongify,然后直接使用下劃線的成員變量還是會(huì)造成循環(huán)引用搀暑。原因就是_ivar直接使用成員變量沥阳,self 跟 weakSelf會(huì)同時(shí)被 block 捕獲成 block 的成員變量,注意:self 還是會(huì)被 block 捕獲的(前面好像沒(méi)寫(xiě)例子自点,不過(guò)你可以自己寫(xiě)寫(xiě)看)沪袭,導(dǎo)致 block 還是強(qiáng)引用著 self,導(dǎo)致循環(huán)引用樟氢。解決辦法就是 strongSelf -> ivar這樣使用成員變量

總結(jié)

  • block 會(huì)捕捉 block 內(nèi)部的變量

    • 當(dāng)變量類(lèi)型是局部變量(基本數(shù)據(jù)類(lèi)型時(shí)或 oc 類(lèi))冈绊,僅捕獲該變量的值,所以 block 內(nèi)部和外部這兩個(gè)變量的地址是不一樣的埠啃,在block 內(nèi)部修改變量的值也不會(huì)影響 block 外部的變量
    • 當(dāng)變量是 self 時(shí)的情況跟 局部變量時(shí)是差不多的
    • 當(dāng)變量類(lèi)型是__block修飾的布局變量(基本數(shù)據(jù)類(lèi)型或者 oc 類(lèi))死宣,會(huì)新構(gòu)建一個(gè)結(jié)構(gòu)體,其中成員變量__forwarding會(huì)指向棧中該變量的地址碴开,因此在 block 內(nèi)部修改該變量會(huì)影響 block 外部的變量
    • 當(dāng)變量是全局變量或者全局靜態(tài)變量時(shí)毅该,block 不會(huì)捕獲該變量,因?yàn)樽兞恳呀?jīng)存在在數(shù)據(jù)區(qū)潦牛,可以直接調(diào)用眶掌。此時(shí) block 也保存在數(shù)據(jù)區(qū)
    • 當(dāng)變量是靜態(tài)變量時(shí),block 會(huì)捕獲該變量的地址巴碗,因此在 block 內(nèi)部修改該變量會(huì)影響 block 外部的變量
  • block 結(jié)構(gòu)體中的成員變量 descriptor 包含著 copydispose 兩個(gè)函數(shù)指針朴爬。copy 函數(shù)把 block 從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時(shí)候銷(xiāo)毀掉橡淆。

  • 蘋(píng)果使用一個(gè)全局的 weak 表來(lái)保存所有的 weak 引用召噩。并將對(duì)象作為鍵,weak_entry_t 作為值逸爵。weak_entry_t 中保存了所有指向該對(duì)象的 weak 指針找筝。當(dāng)被指向的對(duì)象執(zhí)行 dealloc 時(shí)候赛糟,將所有指向該對(duì)象的 weak 指針的設(shè)置為nil。

  • 在 block 外部使用 __weak 的原因是事扭,讓 block 將這個(gè) __weak修飾的變量捕獲成自己的成員變量张足,這樣當(dāng)外面的變量被 dealloc 后显拜,block 內(nèi)的該成員變量也將置為 nil枢泰,避免循環(huán)引用

  • 在 block 里面使用的 __strong 修飾的 weakSelf 是為了在函數(shù)生命周期中防止 self 提前釋放笨蚁。strongSelf是一個(gè)局部變量,當(dāng)block內(nèi)的代碼執(zhí)行完畢就會(huì)釋放致稀,不會(huì)對(duì) self 進(jìn)行一直進(jìn)行強(qiáng)引用冈闭。

引用

ARC對(duì)self的內(nèi)存管理
深入研究 Block 捕獲外部變量和 __block 實(shí)現(xiàn)原理
深入研究 Block 用 weakSelf俱尼、strongSelf抖单、@weakify、@strongify 解決循環(huán)引用
談Objective-C block的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市矛绘,隨后出現(xiàn)的幾起案子耍休,更是在濱河造成了極大的恐慌,老刑警劉巖货矮,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羊精,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡囚玫,警方通過(guò)查閱死者的電腦和手機(jī)喧锦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)抓督,“玉大人燃少,你說(shuō)我怎么就攤上這事×逶冢” “怎么了阵具?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)定铜。 經(jīng)常有香客問(wèn)我阳液,道長(zhǎng),這世上最難降的妖魔是什么揣炕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任帘皿,我火速辦了婚禮,結(jié)果婚禮上畸陡,老公的妹妹穿的比我還像新娘矮烹。我一直安慰自己,他們只是感情好罩锐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布奉狈。 她就那樣靜靜地躺著,像睡著了一般涩惑。 火紅的嫁衣襯著肌膚如雪仁期。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,850評(píng)論 1 290
  • 那天竭恬,我揣著相機(jī)與錄音跛蛋,去河邊找鬼。 笑死痊硕,一個(gè)胖子當(dāng)著我的面吹牛赊级,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岔绸,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼理逊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼橡伞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晋被,我...
    開(kāi)封第一講書(shū)人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤兑徘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后羡洛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挂脑,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年欲侮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崭闲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡威蕉,死狀恐怖镀脂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忘伞,我是刑警寧澤薄翅,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站氓奈,受9級(jí)特大地震影響翘魄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜舀奶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一暑竟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧育勺,春花似錦但荤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至南蓬,卻和暖如春纺非,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赘方。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工烧颖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窄陡。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓炕淮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跳夭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涂圆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容