Block 是如何實現(xiàn)的售担?如何避免循環(huán)引用?

本文是作者Lefe所創(chuàng)署辉,轉(zhuǎn)載請注明出處族铆,如果你在閱讀的時候發(fā)現(xiàn)問題歡迎一起討論。本文會不斷更新哭尝。

說明:

使用 Block 的時候哥攘,我們通常會有以下幾點疑問,我們帶著這種疑問來閱讀本文材鹦,本文難免會有遺漏或者錯誤逝淹,望讀者朋友們提出來。Lefe 在使用 Block 的時候主要遇到了以下問題:

  • 難道只要使用了 self 就需要使用 __weak 來避免循環(huán)引用嗎桶唐?
  • 為避免循環(huán)應(yīng)用栅葡,為什么使用了 __weak 還需要使用 __strong ?
  • 為什么使用 __block 修改變量后就可以在 Block 內(nèi)部修改它的值?
  • Block 什么時候釋放呢尤泽?它是如何進(jìn)行內(nèi)存管理的欣簇?
  • Block 為什么要用 copy?
  • 為什么有些 Block 即使捕獲了 self 也不會產(chǎn)生循環(huán)引用坯约?
  • 自動變量(局部變量)如何被 Block 捕獲的熊咽,對象又是如何被 Block 捕獲的?

帶著這些問題闹丐,我們一塊來揭開 Block 的真實面目横殴,本文篇幅較長,可以分段閱讀卿拴,建議讀者耐心閱讀衫仑,很枯燥的,如果能動手實現(xiàn)以下巍棱,會有趣很多。在閱讀之前我們先了解下 Clang

Clang

本文主要用到了 Clang蛋欣,那什么是 Clang 呢航徙?它是 Xcode 默認(rèn)的編譯器。更多關(guān)于Clang 可以參考 本文 陷虎。這里我們主要用 Clang 把 Block 的實現(xiàn)轉(zhuǎn)換成 C++ 到踏,其實和 C 差不多杠袱,除了構(gòu)造函數(shù)外昂利。

打開 shell森缠,進(jìn)入 Lefe 的測試項目中,輸入:

clang -rewrite-objc HelloLefe.m瞧栗,這是會在當(dāng)前目錄下生產(chǎn)一個對應(yīng)的 HelloLefe.cpp 文件伴榔,打開它就對了纹蝴。截個圖看看,別光看美女踪少。

屏幕快照 2017-06-25 上午9.36.43.png

內(nèi)存分配

在閱讀下文前塘安,我們需要對內(nèi)存分配有一定的了解

  • 棧: 在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建援奢,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放兼犯。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高集漾,但是分配的內(nèi)存容量有限切黔。
  • 堆: 就是那些由 new分配的內(nèi)存塊,他們的釋放編譯器不去管具篇,由我們的應(yīng)用程序去控制纬霞,一般一個new就要對應(yīng)一個 release。如果程序員沒有釋放掉栽连,那么在程序結(jié)束后险领,操作系統(tǒng)會自動回收。這部分內(nèi)存需要程序員手動釋放秒紧。當(dāng)然使用 ARC 后我們不需要處理绢陌。
  • 全局/靜態(tài)存儲區(qū):全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,在以前的C語言中熔恢,全局變量又分為初始化的和未初始化的脐湾,在C++里面沒有這個區(qū)分了,他們共同占用同一塊內(nèi)存區(qū)叙淌。這部分?jǐn)?shù)據(jù)不需要程序員手動釋放秤掌,他會隨著程序的消失二釋放。
  • 常量存儲區(qū): 這是一塊比較特殊的存儲區(qū)鹰霍,他們里面存放的是常量闻鉴,不允許修改。
屏幕快照 2017-06-25 上午10.20.09.png

關(guān)于內(nèi)存分配的閱讀 本文

Block 是如何實現(xiàn)的

掌握了 Clange 的基本使用茂洒,那我們就看看 Block 究竟做了什么孟岛。從一個簡單的例子開始。

Lefe 在 HelloLefe.m 文件中,寫了一個 Block渠羞,使用 clang -rewrite-objc HelloLefe.m 轉(zhuǎn)換斤贰,轉(zhuǎn)換后可以看到 Block 的具體實現(xiàn)。

- (void)lefeTestComplete
{
    void (^complete)(void) = ^(void){
        NSLog(@"Block\n");
    };
    complete();
}

@end

轉(zhuǎn)換后的代碼如下:

  • 發(fā)現(xiàn)每個結(jié)構(gòu)體的生成都會是一個又長又臭的名字次询,它會使用類名 HelloLefe 和方法名 lefeTestComplete等生成一個結(jié)構(gòu)體荧恍,也就是 block 的實現(xiàn),它是一個很重要的結(jié)構(gòu)體屯吊。它主要包含了2個結(jié)構(gòu)體和一個構(gòu)造方法送巡。
struct __HelloLefe__lefeTestComplete_block_impl_0 {
    struct __block_impl impl;
    struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
    
    // 構(gòu)造方法
    __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

  • __block_impl結(jié)構(gòu)體,是 __HelloLefe__lefeTestComplete_block_impl_0 結(jié)構(gòu)體的第一個變量
struct __block_impl {
  void *isa;  // isa 指針雌芽,block 其實也是一個 OC 對象授艰,每個類都有一個指向其實例的一個指針
  int Flags;
  int Reserved;
  void *FuncPtr; // 相當(dāng)于 block 中要執(zhí)行的函數(shù)的指針
};
  • 結(jié)構(gòu)體 __HelloLefe__lefeTestComplete_block_impl_0 的第二個變量
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};

  • 同樣,以類名和方法名生成一個函數(shù)世落,這個函數(shù)也就是 ^(void){ NSLog(@"Block\n"); }; 轉(zhuǎn)換后的結(jié)果淮腾,__cself 和 OC 中的 self 差不多一個意思,它就是 __HelloLefe__lefeTestComplete_block_impl_0屉佳,是指向結(jié)構(gòu)體 __HelloLefe__lefeTestComplete_block_impl_0的指針
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__cv2l59cn5x90wh88hrkp35x80000gp_T_HelloLefe_b006ae_mi_0);
}
  • 主函數(shù)谷朝,編譯器編譯后會自動給每個方法添加兩個參數(shù) self_cmd
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {

     // 這段代碼是對 void (^complete)(void) = ^(void){ NSLog(@"Block\n");}; 的轉(zhuǎn)換
     
    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA));
    
    // 這段代碼相當(dāng)于對 complete(); 的轉(zhuǎn)換,
    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}

從上面的轉(zhuǎn)換過程可以看出武花,聲明一個 block 首先調(diào)用結(jié)構(gòu)體 __HelloLefe__lefeTestComplete_block_impl_0 的構(gòu)造函數(shù)圆凰,得到一個 IMP,相當(dāng)于 OC 中的 IPM体箕,它保存了這個 block 所需要的信息专钉,當(dāng)調(diào)用 block 的時候,直接調(diào)用 IPM-> FuncPtr累铅。

到這里相信讀者還是對 Block 的實現(xiàn)很陌生跃须,很正常,堅持閱讀一會娃兽,試試看菇民。頭腦中試著把 Block 就當(dāng)做是一個 NSObject 對象。

Block 捕獲變量

記得剛接觸 Block 的時候投储,只是隱約聽到 Block 可以自動捕獲 Block 中使用的變量第练。是的,Block 可以捕獲它所用到的自動變量或?qū)ο舐贶瘢撬皇遣东@了它所用到的變量娇掏,其他用不到的變量它并不會捕獲,這里就是引起循環(huán)引用的一個重點勋眯,下文會詳細(xì)將到婴梧。對應(yīng)全局變量 Block 并不或去捕獲壁涎。

以上的 block 的實現(xiàn)多少有點眉目了,那么 block 是如何捕獲變量的志秃,我把將要轉(zhuǎn)換的代碼改為:

- (void)lefeTestComplete
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    
    void (^complete)(void) = ^(void){
        printf(fmt, val);
    };
    complete();
}

轉(zhuǎn)換后的代碼如下,觀察的實現(xiàn)發(fā)現(xiàn)多了
const char *fmt; int val; 這就是 block 捕獲的變量嚼酝,但我們發(fā)現(xiàn) dmy 這個變量并沒有捕獲浮还,因為在 block 中壓根就沒使用。結(jié)構(gòu)體的構(gòu)造方法也需要傳入捕獲的變量來構(gòu)造結(jié)構(gòu)體闽巩。

struct __HelloLefe__lefeTestComplete_block_impl_0 {
  struct __block_impl impl;
  struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
  const char *fmt;
  int val;
  __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_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;
  }
};

static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt, val);
    }

static struct __HelloLefe__lefeTestComplete_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};

static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";

    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, fmt, val));
    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}

修改 Block 中的捕獲的變量

上面的例子中钧舌,并不能在 Block 中修改所捕獲的變量,那么如何修改 Block 中所捕獲的變量呢涎跨?可以使用 __block洼冻。如果修改 Block 中的變量,編譯器會直接報錯隅很。比如:

- (void)leftTestBlock
{
    int age = 0;
    void (^block)(void) = ^{
        age = 10;
    };
}

這段代碼編譯器直接會報錯撞牢,可能有些同學(xué)會說直接用 __block,但是為什么使用 __block 就可以呢叔营?再看一下下面的代碼:

// 全局變量
int global_val = 1;
// 全局靜態(tài)變量
static int static_global_val = 2;

- (void)lefeTestComplete
{
    // 靜態(tài)變量
    static int static_val = 3;
    void (^complete)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    complete();
}

這段代碼是沒有任何問題的屋彪,它可以正常的編譯通過,它沒有使用 __block绒尊。詳細(xì)大部分的同學(xué)讀到這里都會有一個疑惑畜挥,這是為什么呢?我們不妨來看一下他的具體實現(xiàn)婴谱。發(fā)現(xiàn)全局變量并沒有被捕獲到 __HelloLefe__lefeTestComplete_block_impl_0 中蟹但,僅僅捕獲了 static_val,想想也是谭羔,全局變量直接可以獲取到华糖,為什么還要捕獲他呢?但捕獲靜態(tài)變量和以前不一樣的是它捕獲的是一個指針 int *static_val; 哦口糕,對啊缅阳,直接使用它的指針就可以修改它了,但是為什么普通變量不可以使用其指針呢景描?因為一個 block 必須存在即使它所捕獲變量的作用域釋放掉十办,作用域釋放掉后其變量也隨之銷毀,這意味著 block 就不能訪問所捕獲的自動變量了超棺,如何修改向族?但是靜態(tài)變量和全局變量不會釋放啊棠绘!

int global_val = 1;
static int static_global_val = 2;


struct __HelloLefe__lefeTestComplete_block_impl_0 {
  struct __block_impl impl;
  struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
  int *static_val;
  __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *= 3;
    }

static struct __HelloLefe__lefeTestComplete_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};

static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    static int static_val = 3;
    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}

通過上面的學(xué)習(xí)我們可以了解到件相,修改 Block 中捕獲的變量再扭,可以使用一下幾種方式:

  • 通過 __block 修飾變量
  • 使用靜態(tài)變量
  • 使用全局變量、全局靜態(tài)變量
  • 使用指針夜矗,靜態(tài)變量就是通過指針來修改它的值的

讀到這里泛范,相信你已經(jīng)明白如何捕獲自動變量了,也知道如何修改 Block 中所捕獲的變量了紊撕,難道你不想知道為啥使用 __block 修飾后就可以修改 Block 中所捕獲的變量嗎罢荡?哈哈,堅持一下对扶!

__block 究竟是如何實現(xiàn)的呢区赵?

__block 如同 static, auto 等修飾符,主要作用是覺得某一變量該保存到哪里浪南×牛看看它是如何實現(xiàn)的。把下面的代碼轉(zhuǎn)化:

- (void)lefeTestComplete
{
    __block int val = 10;
    void (^complete)(void) = ^{val = 1;};
    
    complete();
}

轉(zhuǎn)換后發(fā)現(xiàn)多了很多內(nèi)容络凿,為什么使用 __block 需要增加這么多代碼呢骡送?Lefe 表示很好奇。當(dāng)使用 __block 變量時絮记,會將 __block 變量從椄餮瑁拷貝的堆上。當(dāng)多個 block 共用一個 __block 變量時到千,__block 變量有一個計數(shù)器來記錄有多少個 block 引用了它昌渤,block 釋放掉的時候,__block 變量的引用計數(shù)將減1憔四,直到為0時膀息,__block 變量才會釋放。

  • __block 轉(zhuǎn)換后的結(jié)構(gòu)體
struct __Block_byref_val_0 {
  void *__isa;
  // __forwarding 主要用來獲取 __block 變量的值了赵,它的指向會根據(jù) block 所處的內(nèi)存位置不同潜支,所指向的也不同。
  __Block_byref_val_0 *__forwarding; 
  int __flags;
  int __size;
  int val; // 值
};
  • Block 的實現(xiàn)柿汛,發(fā)現(xiàn)多了 __Block_byref_val_0冗酿,它就是一個 block 變量
struct __HelloLefe__lefeTestComplete_block_impl_0 {
  struct __block_impl impl;
  struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  
  __HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_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;
  }
};
  • ^{val = 1;} 的實現(xiàn)被轉(zhuǎn)換后:
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __HelloLefe__lefeTestComplete_block_copy_0(struct __HelloLefe__lefeTestComplete_block_impl_0*dst, struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

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

static struct __HelloLefe__lefeTestComplete_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __HelloLefe__lefeTestComplete_block_impl_0*, struct __HelloLefe__lefeTestComplete_block_impl_0*);
  void (*dispose)(struct __HelloLefe__lefeTestComplete_block_impl_0*);
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0), __HelloLefe__lefeTestComplete_block_copy_0, __HelloLefe__lefeTestComplete_block_dispose_0};
  • __block int val = 10; 轉(zhuǎn)化后的代碼如下,轉(zhuǎn)換后變成一個結(jié)構(gòu)體络断,并且初始化的時候值為 10
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
        (void*)0,
        (__Block_byref_val_0 *)&val, 
        0, 
        sizeof(__Block_byref_val_0), 
        10
    };
    
    void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}

Block 的內(nèi)存段

下圖主要說明 block 主要保存在棧裁替,堆和數(shù)據(jù)區(qū)。


屏幕快照 2017-06-23 下午10.06.35.png

那什么樣的變量分派到棧中貌笨、堆中或數(shù)據(jù)區(qū)呢弱判?

  • 1、當(dāng) block 字面量寫在全局作用域時锥惋,即為 global block昌腰;
  • 2开伏、當(dāng) block 字面量不獲取任何外部變量時,即為 global block遭商;

除了以上2中情況外固灵,其他的都分派到棧區(qū),分派到棧區(qū)的 block 劫流,當(dāng)作用域結(jié)束后怎虫,它所捕獲的變量也就釋放掉了。為了解決這個問題困介,Blocks 提供了一個函數(shù),可以把棧上的 block 拷貝到堆上蘸际。這樣即使作用域結(jié)束也不會使 block 被釋放座哩。被 copy 后的 block ,它的 isa 指針就會變成 impl.isa = &_NSConcreteMallocBlock粮彤。Block 也就成了堆上的 Block根穷。

使用 ARC 后,編譯器會自動把棧中的 block 復(fù)制到堆上导坟。

typedef int (^blk_t)(int);
blk_t func(int rate) {
    return ^(int count){return rate * count;}; 
}
blk_t func(int rate) {
    blk_t tmp = &__func_block_impl_0(
    __func_block_func_0, &__func_block_desc_0_DATA, rate);
    // 直接復(fù)制了一個 block屿良,也就是拷貝到了堆上,即使當(dāng)這個函數(shù)結(jié)束后惫周,這個 block 任然不會被銷毀 
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp); }

但是不是所有的時候尘惧,編譯器都會執(zhí)行 copy 操作的,以下情況編譯器不會執(zhí)行 copy 操作的

  • Block 作為一個參數(shù)傳遞給一個函數(shù)或方法時递递。

舉個例子喷橙,下面這個例子會直接 crash,所以需要給數(shù)組中的 block 要執(zhí)行 copy 操作

+ (id)getBlockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d", val);},
            ^{NSLog(@"blk1:%d", val);},
            nil
            ];
}

+ (void)lefeTestComplete
{
    id obj = [self getBlockArray];
    typedef void (^blk_t)(void);
    blk_t blk = (blk_t)[obj objectAtIndex:0];
    blk();
}

但是使用系統(tǒng)提供的方法不需要執(zhí)行 copy 操作登舞,比如 GCD贰逾,因為在函數(shù)內(nèi)部自己已經(jīng)實現(xiàn)了 copy。

捕獲對象:

前面都在講捕獲的是基本類型的變量菠秒,那么 Block 是如何捕獲對象的呢疙剑?下面的例子中的數(shù)組中,打印結(jié)果為:

Array: (
    Lefe,
    Wang,
    Su,
    Yan
)

說明數(shù)組沒有被釋放掉践叠。Block 內(nèi)部會強(qiáng)引用對象言缤,直到 Block 被釋放,被引用的對象也將被釋放禁灼。

@implementation HelloLefe

LefeBlock block;

+ (void)lefeTestComplete
{
    NSMutableArray *array = [NSMutableArray array];
    block = ^(NSString *name){
        [array addObject:name];
        
        NSLog(@"Array: %@", array);
    };
}

+ (void)addObject
{
    block(@"Lefe");
    block(@"Wang");
    block(@"Su");
    block(@"Yan");
    
}

@end

循環(huán)引用一:

- (void)testMemoryLeakCase1
{
    self.logId = @"Hello logId";
    
    /**
     這種情況最容易發(fā)現(xiàn)轧简,因為編譯器會自動提示出現(xiàn)循環(huán)引用
     Why?
     self(SecondViewController)持有了 finshBlock匾二,你可以把它當(dāng)作一個普通的屬性哮独,是強(qiáng)引用
     而 finshBlock 又引用了 self拳芙,這樣就形成了一個閉環(huán)。
     How皮璧?
     既然是因為出現(xiàn)了閉環(huán)舟扎,我們只需要打破這層閉環(huán)就可以,讓 finshBlock 持有一個弱引用悴务,這樣 self(SecondViewController)持有了 finshBlock睹限,但是 finshBlock 沒有持有 self
     */
    
    // __weak typeof(self) weakSelf = self; 一般的宏定義是這樣的
    __weak SecondViewController *wSelf = self;
    
    self.finshBlock = ^(BOOL isSuccess) {
        [wSelf loginTest];
    };
    
    /**
     在我們的應(yīng)用中一般是下面這種方式寫,為啥使用了 __weak 和 __strong ?
     有人可能會問讯檐,先 weak 后 strong羡疗,那相當(dāng)于還是強(qiáng)引用了 self,你確定 strong的是 self别洪?
     */
    
    /**
     打舆逗蕖:
     (lldb) p weakSelf
     (SecondViewController *) $0 = 0x0000000101c16f10
     (lldb) p self
     (SecondViewController *) $1 = 0x0000000101c16f10
     (lldb) p strongSelf
     (SecondViewController *) $2 = 0x0000000101c16f10
     (lldb)
     
     發(fā)現(xiàn) weakSelf self 和 strongSelf 的內(nèi)存地址是一樣的,只是一次淺拷貝;
     */
    __weak typeof(self) weakSelf = self;
    
    self.finshBlock = ^(BOOL isSuccess) {
        // 如果沒有這句話挖垛,當(dāng) self 被釋放后痒钝,weakSelf 就變?yōu)榱丝眨躁P(guān)于 weakSelf 的一些操作也就沒什么意義了痢毒,如果還想讓 weakSelf 所調(diào)用的一些方法有意義那么久需要強(qiáng)引用 weakSelf送矩;
        __strong typeof(self) strongSelf = weakSelf;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"weakSelf.logId: %@", strongSelf.logId);
            NSString *name = strongSelf.logId;
            if (name.length > 0) {
                NSLog(@"Hello world");
            }
            [strongSelf loginTest];
        });
    };
    self.finshBlock(YES);
    
    /**
     修改前的:
     self.finshBlock = ^(BOOL isSuccess) {
        [self loginTest];
     };
     */
}

循環(huán)引用二:

- (void)testMemoryLeakCase2
{
    /**
     這里面出現(xiàn)了兩個對象的內(nèi)存泄漏: task 和 self
     task的內(nèi)存泄漏:
     task 有個屬性叫 blcok,但是在 block 中又捕獲了 task哪替,這樣就形成了一個閉環(huán)
     self 的內(nèi)存泄漏:
     因為這個 block 中捕獲了 self栋荸,block 沒有釋放那么 self 咋么能釋放呢?
     所以只要打破這個閉環(huán)凭舶,self 就釋放了蒸其。
     
     */
    AsyncTask *task = [AsyncTask new];
    
    __weak AsyncTask *wTask = task;
    task.block = ^(BOOL isFinish) {
        NSString *name = wTask.lastLoginId;
        self.logId = name;
    };
    [task sendLogin];
    
    /**
     AsyncTask *task = [AsyncTask new];
     task.block = ^(BOOL isFinish) {
     NSString *name = task;
     self.logId = name;
     };
     [task sendLogin];
     */
}

循環(huán)引用三:

其實實例變量是通過 self->name 訪問的,所以也可能造成循環(huán)引用库快。

- (void)testMemoryLeakCase3
{
    /**
     這里可能不太容易看出來摸袁,訪問 name 實例變量相當(dāng)于 self->name
     這樣 self 持有 finshBlock, finshBlock 持有 self义屏,形成閉環(huán)靠汁,造成循環(huán)引用
     */
    
    __weak SecondViewController *wSelf = self;
    self.finshBlock = ^(BOOL isFinish) {
        /*
         Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
         */
        // 發(fā)現(xiàn)這樣寫不行,還報錯闽铐,它的意思是 __weak 指針可能為空蝶怔,必須要強(qiáng)引用
        // wSelf->name = @"Hello lefe";
        
        /**
         那么為什么在 testMemoryLeakCase1 中 wSelf.logId = @"Hello logId"; 沒有編譯錯誤呢?我想
         估計 wSelf.logId 等價于 [wSelf logId]兄墅,相當(dāng)于調(diào)用了一個方法踢星,
         nil 調(diào)用方法是沒有錯誤的。你知道屬性和實例變量的區(qū)別嗎隙咸?
         
         下面這行代碼也會報錯的:
         __weak AsyncTask *task;
         task->_sex;
         
         */
        wSelf.logId = @"Hello logId";
        
        __strong SecondViewController *strongSelf = wSelf;
        strongSelf->_name = @"Hello lefe";
    };
    
    /**
    也可以使用下面方法來解除循環(huán)引用
    __block id temp = self;
    self.finshBlock = ^(BOOL isFinish) {
        temp = nil;
    };
    self.finshBlock(YES);
    */
    
    
    /**
     修改前的代碼:
     self.finshBlock = ^(BOOL isFinish) {
        name = @"Hello lefe";
     };
     */
}

===== 我是有底線的 ======
喜歡我的文章沐悦,歡迎關(guān)注我的新浪微博 Lefe_x成洗,我會不定期的分享一些開發(fā)技巧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市藏否,隨后出現(xiàn)的幾起案子瓶殃,更是在濱河造成了極大的恐慌,老刑警劉巖副签,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遥椿,死亡現(xiàn)場離奇詭異,居然都是意外死亡淆储,警方通過查閱死者的電腦和手機(jī)冠场,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來本砰,“玉大人碴裙,你說我怎么就攤上這事」嗑撸” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵譬巫,是天一觀的道長咖楣。 經(jīng)常有香客問我,道長芦昔,這世上最難降的妖魔是什么诱贿? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮咕缎,結(jié)果婚禮上珠十,老公的妹妹穿的比我還像新娘。我一直安慰自己凭豪,他們只是感情好焙蹭,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嫂伞,像睡著了一般孔厉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帖努,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天撰豺,我揣著相機(jī)與錄音,去河邊找鬼拼余。 笑死污桦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的匙监。 我是一名探鬼主播凡橱,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼小作,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梭纹?” 一聲冷哼從身側(cè)響起躲惰,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎变抽,沒想到半個月后础拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绍载,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年诡宗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片击儡。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡塔沃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阳谍,到底是詐尸還是另有隱情蛀柴,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布矫夯,位于F島的核電站鸽疾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏训貌。R本人自食惡果不足惜制肮,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望递沪。 院中可真熱鬧豺鼻,春花似錦、人聲如沸款慨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檩奠。三九已至约素,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笆凌,已是汗流浹背圣猎。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留乞而,地道東北人送悔。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親欠啤。 傳聞我的和親對象是個殘疾皇子荚藻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

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