iOS源碼解析:Block的本質(zhì)<二>

Block的本質(zhì)<一>

1.對象類型的auto變量

在第一篇文章中我們講了在block中使用基本類型的自動變量的情況,現(xiàn)在我們研究一下在block中使用對象類型的自動變量又是什么情況。
首先看一段代碼:

typedef void(^PDBlock)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        block = ^{
            
            NSLog(@"%ld", (long)person.age);
        };
        
        block();
        
        return 0;
    }
}

這段代碼執(zhí)行的結(jié)果是10招狸,完全沒有問題蜡塌。我們?yōu)榱搜芯縫erson對象的釋放時機古今,在person.m文件中實現(xiàn)dealloc方法:

- (void)dealloc{
    
    NSLog(@"--------dealloc");
}

然后我們把代碼改一下,在大括號下面一行打斷點:

1391D5A2-4A01-4C3F-9F72-ECC3966A1488.png

運行代碼轮蜕,發(fā)現(xiàn)運行到斷點處的時候,已經(jīng)打印了--------dealloc良姆,這說明這個時候person對象已經(jīng)被釋放了肠虽。這個很好理解幔戏,因為person對象是在大括號內(nèi)聲明的局部變量玛追,它的生命周期僅限于這個大括號。
下面我們在大括號里加入一個block:
9F553304-3BD1-48BE-A1FB-6AA25F175B75.png

這個時候運行代碼闲延,發(fā)現(xiàn)代碼運行到斷點處痊剖,也就是出了大括號,還沒有打印--------dealloc垒玲,這說明person對象出了大括號還沒有被釋放陆馁。為了探索其中的原因,我們把代碼轉(zhuǎn)化為c++源碼看看合愈,為了簡潔叮贩,把代碼簡化如下:

typedef void(^PDBlock)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        PDBlock block = ^{
            
            NSLog(@"%ld", (long)person.age);
        };
        
        
    }
    return 0;
}

直接看_mian_block_imol_0這個結(jié)構(gòu):

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

這個結(jié)構(gòu)中多了一個成員變量Person *person,因為person是自動變量佛析,所以這里捕獲了自動變量person作為_main_block_impl_0結(jié)構(gòu)體的成員變量益老。而且還要注意的是,由于是自動變量寸莫,所以在block外面捺萌,自動變量是什么類型,在結(jié)構(gòu)體里面作為成員變量就是什么類型膘茎。person在結(jié)構(gòu)體外面作為自動變量是指針類型桃纯,所以作為結(jié)構(gòu)體里面的成員變量也是指針類型。

55AF6C91-DB29-4B0C-BF00-18205E4146F0.png

結(jié)構(gòu)體里面有一個成員變量是person指針披坏,這個person指針指向在外面創(chuàng)建的那個person對象态坦。所以出了大括號以后,外面的person指針被銷毀了棒拂,但是block內(nèi)有一個person指針還指向原來的person對象驮配,所以原來的person對象沒有銷毀。
下面我們在MRC下運行一下代碼着茸,對代碼進行一下小修改:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                
                NSLog(@"%ld", (long)person.age);
            };
            
            [person release];
        }
        
    }
    return 0;
}

還是在這個大括號外面打斷點壮锻,我們驚奇的發(fā)現(xiàn)代碼運行到斷點處時已經(jīng)打印了--------dealloc,這個時候唯一的不同就是在MRC環(huán)境下block是在椾汤空間猜绣,那這是不是就說明在棧空間的block對person對象沒有像堆空間的block那樣的強引用效果呢敬特?我們把這個block進行copy操作查看一下結(jié)果掰邢。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = [^{
                
                NSLog(@"%ld", (long)person.age);
            } copy];
            
            [person release];
        }
        
    }
    return 0;
}

這個時候我們發(fā)現(xiàn)在大括號外的斷點處牺陶,并沒有打印--------dealloc,這說明這個時候person對象還沒有被釋放辣之,也就是說此時堆區(qū)的block對person對象是有強引用作用的掰伸,棧空間的block對person對象沒有強引用作用怀估。
我們再切換回ARC狮鸭,執(zhí)行下面代碼:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *weakPerson = person;
            block = ^{
                
                NSLog(@"%ld", (long)weakPerson.age);
            };
        
        }
        
    }
    return 0;
}

還是在大括號外面打斷點,我們看到在斷點處就已經(jīng)打印了--------dealloc多搀,這說明在這里person對象就已經(jīng)被銷毀了歧蕉。我們輸入clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.00 main.m將其轉(zhuǎn)化為c++的代碼查看一下:

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

查看_main_block_impl_0,我們發(fā)現(xiàn)康铭,這里面多了一個成員變量Person *__weak weakPerson惯退,可以看到weakPerson這個成員變量帶有weak屬性嗎,這和外面的自動變量weakPerson保持一致从藤。由于這里成員變量是weak屬性催跪,所以對person對象是弱引用,也就不能改變person對象的生命周期夷野。

總結(jié) 當block內(nèi)部訪問了對象類型的auto變量時
如果block是在棧上懊蒸,將不會對auto變量產(chǎn)生強引用
如果block被拷貝到堆上
  • 會調(diào)用block內(nèi)部的copy函數(shù)
  • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
  • _Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong,_weak)做出相應的操作,形成強引用扫责,弱引用榛鼎。
如果block從堆上移除
  • 會調(diào)用block內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放引用的auto變量,類似于release鳖孤。

2.__block修飾符

有時候我們想要在block內(nèi)部修改自動變量的值者娱,就像下面這樣:

        PDBlock block;
        
        int age = 10;
        
        block = ^{
            
            age = 20;
            NSLog(@"%d", age);
        };

但是很快我們就會發(fā)現(xiàn)這樣寫報了編譯錯誤,不能在block內(nèi)修改自動變量的值苏揣。其實從c++的源碼上我們也能明白黄鳍,age是在main函數(shù)中聲明的自動變量,而block內(nèi)的代碼塊是_main_block_func_0這個函數(shù)平匈,這兩個函數(shù)互不相干框沟,所以在_main_block_func_0函數(shù)內(nèi)是不能修改在main函數(shù)中定義的自動變量的。換個角度思考這個問題增炭,由于自動變量被block捕獲是值傳遞忍燥,所以block只是捕獲了age的值10,并把它賦值給自己的成員變量隙姿,使得結(jié)構(gòu)體中有一個成員變量的值是10梅垄,要用的時候就把這個值讀出來,能夠修改結(jié)構(gòu)體的成員變量的值输玷,使之變成20队丝,但是這并不會改變外面自動變量的值靡馁。

那么如果我們把局部變量改成用static修飾會怎么樣呢?

PDBlock block;
        
        static int age = 10;
        
        block = ^{
            age = 20;
            NSLog(@"%d", age);
        };
        
        block();

編譯沒有問題机久,打印結(jié)果:

 20

得到了我們所期望的結(jié)果臭墨。
那么為什么使用static修飾的局部變量就可以在block內(nèi)修改值呢?
因為用static修飾的局部變量膘盖,被block捕獲時是指針傳遞胧弛,也就是把存放這個值的地址給傳進去了,在結(jié)構(gòu)體內(nèi)會有一個指針類型的成員變量存放傳經(jīng)來的這個地址衔憨,如果要修改外面的局部變量的值叶圃,那么就可以通過地址值去改變其值袄膏。

但是使用static修飾符會改變變量的生命周期践图,使之一直存在內(nèi)存中,有時候這并不是我們想要的沉馆,那么有沒有其它辦法使自動變量的值能夠在block內(nèi)被修改呢码党?這個時候就要用到__block修飾符了。

        PDBlock block;
        
        __block int age = 10;
        
        block = ^{
            age = 20;
            NSLog(@"%d", age);
        };
        
        block();

執(zhí)行代碼斥黑,輸出結(jié)果是20揖盘。那么__block修飾符的本質(zhì)原理是什么呢?我們還是從c++的源碼來尋找結(jié)果:
main函數(shù):

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        PDBlock block;

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

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

    }
    return 0;
}

化簡一下:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        PDBlock block;

         __Block_byref_age_0 age = {0,
                                    &age, 
                                    0, 
                                    sizeof(__Block_byref_age_0), 
                                     10};

        block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &age, 570425344));

       block)->FuncPtr(block);
    }
    return 0;
}

我們可以看到锌奴,__block int age = 10這句賦值語句被轉(zhuǎn)換成了:

         __Block_byref_age_0 age = {0,
                                    &age, 
                                    0, 
                                    sizeof(__Block_byref_age_0), 
                                     10};

把age轉(zhuǎn)換成了一個結(jié)構(gòu)體兽狭,我們看一下_Block_byref_age_0這個結(jié)構(gòu)體的結(jié)構(gòu):

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

通過賦值我們可以知道,&age被賦值給了自己的成員變量_forwarding這個指針鹿蜀,_size是結(jié)構(gòu)體的大小箕慧,成員變量age存放的是自動變量的值。所以_forwarding這個指針是指向結(jié)構(gòu)體自身的一個指針茴恰。總結(jié)起來就是age這個結(jié)構(gòu)體的成員變量包括isa指針颠焦,指向自身的_forwarding指針,還有一個int類型的成員變量存放值往枣。

我們再看一下_main_blcok_impl_0的結(jié)構(gòu):

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

這里面有一個結(jié)構(gòu)體指針age伐庭,在其結(jié)構(gòu)體函數(shù)中是用&age來初始化的,也就是用前面創(chuàng)建的age結(jié)構(gòu)體的地址來初始化_main_blcok_impl_0的成員變量(age指針)分冈。
我們再看一下_main_blcok_func_0的結(jié)構(gòu):

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

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_74_wk04zv690mz36wn0g18r5nxm0000gn_T_main_db631a_mi_0, (age->__forwarding->age));
        }

通過_cself->age訪問自己的成員變量來獲取結(jié)構(gòu)體指針圾另,然后age->_forwarding->age通過結(jié)構(gòu)體指針訪問成員變量來改變成員變量的值,最后在取值的時候也是通過age->_forwarding->age來取值雕沉。

3.__block內(nèi)存管理

我們已經(jīng)知道了在棧上的block不會對對象類型的局部變量產(chǎn)生引用集乔,但是堆上的block會對對象類型的局部變量產(chǎn)生引用。那么這個過程是怎么樣的呢蘑秽?我們看一段代碼:

       PDBlock block;
        
        NSObject *objc = [[NSObject alloc] init];
        __weak NSObject *weakObjc = objc;
        ^{
            NSLog(@"%@ %@", objc, weakObjc);
        };

我們把它轉(zhuǎn)化為c++的源碼看看:
找到_main_block_desc_0這個結(jié)構(gòu)體:

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

可以看到饺著,這個結(jié)構(gòu)體對比之前有一些變化箫攀,以前成員變量只有reserved和Block_size這兩個,但是這里又增加了兩個函數(shù)作為成員變量幼衰,一個是copy靴跛,一個是dispose函數(shù)。并且我們從結(jié)構(gòu)體的構(gòu)造函數(shù)可以知道這個copy函數(shù)是由_main_block_copy_0這個函數(shù)初始化的渡嚣,dispose是由_main_block_dispose_0這個函數(shù)初始化的梢睛。當block從棧區(qū)復制到堆區(qū)的時候會調(diào)用這里的copy函數(shù),當block銷毀的時候會調(diào)用dispose函數(shù)识椰。我們往上查看一下這兩個函數(shù):

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

在_main_block_copy_0這個函數(shù)中主要是調(diào)用了_Block_object_assign這個函數(shù)绝葡,這個函數(shù)的作用就是對捕獲的對象類型的自動變量產(chǎn)生引用。如果外面的對象類型的自動變量是用weak修飾腹鹉,那么_Block_object_assign函數(shù)就會使block對這個對象產(chǎn)生弱引用藏畅,如果是strong類型的,那么就會產(chǎn)生強引用功咒。
在_main_block_dispose_0這個函數(shù)中愉阎,主要是調(diào)用了_Block_object_dispose這個函數(shù),釋放對對象的強引用或弱引用力奋。

上面是分析的對對象類型的auto變量的內(nèi)存管理問題榜旦,我們知道,__block修飾的自動變量在本質(zhì)上就轉(zhuǎn)化為了結(jié)構(gòu)體實例景殷,也就是一個OC對象溅呢,既然這樣,那么也必然存在內(nèi)存管理的問題猿挚。
在block還未復制到堆上時咐旧,由__block修飾的自動變量產(chǎn)生的結(jié)構(gòu)體實例也存在棧上,當block從棧上復制到堆上后亭饵,block結(jié)構(gòu)體中指向自動變量產(chǎn)生的結(jié)構(gòu)體實例的那個指針也隨之被復制到了堆區(qū)捍壤,這樣就產(chǎn)生了堆區(qū)的指針指向棧區(qū)的對象這個問題例证,這顯然是不行的困乒,所以就要將棧區(qū)的結(jié)構(gòu)體實例賦值到堆區(qū)斜友,同時,block對堆區(qū)的結(jié)構(gòu)體實例進行強引用八秃。

image.png
image.png
總結(jié)

當block在棧上時碱妆,并不會對__block變量產(chǎn)生強引用。
當block被copy到堆上時

  • 會調(diào)用block內(nèi)部的copy函數(shù)
  • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
  • _Block_object_assign函數(shù)會對__block變量形成強引用
    當block從堆中移除時
  • 會調(diào)用block內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
  • _Block_object_dispose函數(shù)會自動釋放引用的__block變量

4._forwarding指針的作用

在2中我們了解到昔驱,讀取和改變age的值都是通過age->_forwarding->age這樣的疹尾,也就是先通過_main_block_impl_0這個結(jié)構(gòu)體獲取其成員變量age指針-指向結(jié)構(gòu)體的指針,然后再通過age指針獲取結(jié)構(gòu)體的成員變量_forwarding指針,這個指針是指向結(jié)構(gòu)體自身的纳本,然后再通過這個指向自身的結(jié)構(gòu)體指針訪問自己的成員變量age窍蓝,這個age成員變量里面真正存放著值。那么問題來了繁成,這個_forwarding指針到底有什么用呢吓笙?

我們思考一個問題,當block還在棧區(qū)時巾腕,這時候包裝age的結(jié)構(gòu)體實例也是分配在棧區(qū)的面睛,所以block這個結(jié)構(gòu)體內(nèi)部的age指針是指向棧區(qū)的結(jié)構(gòu)體實例的,這時候通過age結(jié)構(gòu)體指針直接訪問結(jié)構(gòu)體內(nèi)部的age成員變量值和通過age->_forwarding->age來訪問尊搬,結(jié)果都是一樣的叁鉴。
但是,當block從棧區(qū)復制到堆區(qū)時佛寿,封裝age的結(jié)構(gòu)體也會復制一份到堆區(qū)幌墓,這個時候其實block結(jié)構(gòu)體內(nèi)部age指針還是指向棧區(qū)的age結(jié)構(gòu)體,那這樣的話無論如何都無法取得堆區(qū)的age結(jié)構(gòu)體狗准。Apple顯然想到了這個問題克锣,在age結(jié)構(gòu)體從棧區(qū)復制到堆區(qū)的過程中茵肃,內(nèi)部將棧區(qū)的age結(jié)構(gòu)體的_forwarding指針修改了腔长,改為指向堆區(qū)的age結(jié)構(gòu)體。這樣一來验残,我們再通過block結(jié)構(gòu)體訪問age指針時捞附,訪問到了棧區(qū)的age結(jié)構(gòu)體,然后通過棧區(qū)的age結(jié)構(gòu)體的_forwarding指針去訪問堆區(qū)的age結(jié)構(gòu)體您没,也就是age->_forwarding->age這樣一個過程鸟召。

293F6497-973C-4558-A003-7C21ACDA61D9.png

5.__block修飾對象

前面分析了__block修飾基本類型,現(xiàn)在來分析一下__block修飾對象類型氨鹏。我們看一段代碼:

        PDBlock block;
        
        __block Person *person = [[Person alloc] init];
        
        block = ^{
            NSLog(@"%@", person);
        };

這段代碼是用__block修飾對象類型的簡單的例子欧募,我們看一下轉(zhuǎn)化的源碼:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        PDBlock block;

        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {
            (void*)0,
            (__Block_byref_person_0 *)&person,
            33554432,
            sizeof(__Block_byref_person_0),
            __Block_byref_id_object_copy_131,
            __Block_byref_id_object_dispose_131,
            ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));

    }
    return 0;
}

我們可以看,這里對象類型的自動變量person也是被封裝為了一個結(jié)構(gòu)體仆抵,查看一下_Block_byref_person_0這個結(jié)構(gòu)體的結(jié)構(gòu):

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//從棧上復制到堆上要進行的操作
 void (*__Block_byref_id_object_dispose)(void*);//從棧上復制到堆上要進行的操作
 Person *person;
};

這里和之前的結(jié)構(gòu)不同的是最后一個成員變量是一個指針跟继,這個指針是指向外面用alloc創(chuàng)建的person對象。同樣镣丑,在block結(jié)構(gòu)體中也有一個結(jié)構(gòu)體指針舔糖,這個指針是指向_Block_byref_person_0這個結(jié)構(gòu)體的,總結(jié)一下就是:
62316B47-97CB-4C29-B046-3C88DB3D5609.png

如此一來莺匠,當block從棧區(qū)復制到堆區(qū)的時候金吗,會調(diào)用_main_block_copy_0來實現(xiàn)第一個箭頭的引用,那么第二個箭頭的引用是怎么實現(xiàn)的呢?我們發(fā)現(xiàn)摇庙,在_Block_byref_person_0中多了兩個函數(shù)旱物,通過其初始化可以知道這兩個函數(shù)分別是__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131這兩個函數(shù),這兩個函數(shù)就是在block從棧區(qū)復制到堆區(qū)時卫袒,實現(xiàn)person指針對person對象的引用异袄,也就是圖中第二個箭頭。我們搜索一下這兩個函數(shù):

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

這兩個函數(shù)其實和_main_block_copy_0和_main_block_dispose_0一樣玛臂,最終都是調(diào)用_Block_object_assign和_Block_object_dispose這兩個函數(shù)烤蜕。那么這里為什么都加上了40呢?我們分析一下_Block_byref_person_0的結(jié)構(gòu):

struct __Block_byref_person_0 {
  void *__isa;    //指針迹冤,8字節(jié)
__Block_byref_person_0 *__forwarding; //指針讽营,8字節(jié)
 int __flags; //int型,4字節(jié)
 int __size;  //int型泡徙,4字節(jié)
 void (*__Block_byref_id_object_copy)(void*, void*);//指針型橱鹏,8字節(jié)
 void (*__Block_byref_id_object_dispose)(void*);//指針型,8字節(jié)
 Person *person;
};

這樣一來堪藐,_Block_byref_person_0的地址和person指針的地址就相差40字節(jié)莉兰,所以+40的目的就是找到person指針。

如果我們對__block修飾的對象類型用__weak類型來修飾會怎樣呢礁竞?
         PDBlock block;
        
        Person *person0 = [[Person alloc] init];
        
        __block __weak Person *person = person0;
        
        block = ^{
            NSLog(@"%@", person);
        };

我們從c++的源碼查找一下不同之處:

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak person;
};

發(fā)現(xiàn)不同之處只體現(xiàn)在_Block_byref_person_0這個結(jié)構(gòu)體上糖荒,這個結(jié)構(gòu)體的person指針變成了使用__weak修飾了,也就是person指針對person對象產(chǎn)生弱引用模捂,即上圖中第二條線是虛線捶朵,弱引用。
要驗證person指針對person對象是強引用還是若引用非常簡單

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        {
            Person *person0 = [[Person alloc] init];
            
            __block __weak Person *person = person0;
            
            block = ^{
                NSLog(@"%@", person);
            };
        }
      //在這里打斷點
        
    }
    return 0;
}

我們在代碼中標記的位置打斷點狂男,發(fā)現(xiàn)執(zhí)行在斷點時已經(jīng)打印了--------dealloc综看,說明person對象出了大括號就被釋放了,所以_Block_byref_person_0結(jié)構(gòu)體中的person指針對person對象是弱引用岖食。
如果代碼是這樣:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        PDBlock block;
        {
            Person *person = [[Person alloc] init];
            
            block = ^{
                NSLog(@"%@", person);
            };
        }
      //在這里打斷點
        
    }
    return 0;
}

那么代碼執(zhí)行到斷點處是還沒有打印--------dealloc红碑,說明此時person對象還沒有被釋放,也即person指針對person對象有強引用的作用泡垃。

6.Block中循環(huán)引用的問題

首先看一個循環(huán)引用的例子:

//Person.h
typedef void(^PDBlock)(void);

@interface Person : NSObject

@property (nonatomic, copy)PDBlock mblock;

@property (nonatomic, assign)NSInteger age;

@end
//main.m
int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        {
            
            Person *person = [[Person alloc] init];
            person.mblock = ^{
                NSLog(@"%@", person);
            };
        }
        
        //在這里打斷點
    }
    return 0;
}

這段代碼寫完析珊,編譯器會提示會出現(xiàn)循環(huán)引用,那么我們一起來分析一下它們之間的相互引用問題兔毙。

首先分析這個block:

^{
            NSLog(@"%@", person);
        };

在這個block中唾琼,由于使用了對象類型的auto變量,所以block結(jié)構(gòu)體中有一個成員變量person指針澎剥,這個person指針指向的是在外面利用alloc創(chuàng)建的person對象锡溯,并且有強引用赶舆,用圖表示就是這樣:
D68F35F9-E62C-4E68-9CD7-A999D531AA14.png

接下來我們再來分析一下person對象,這個person對象本質(zhì)也是一個結(jié)構(gòu)體祭饭,這個結(jié)構(gòu)體中就有成員變量mblock芜茵,mblock又對上面的block是強引用,同時外面創(chuàng)建的person指針也會強引用alloc出來的person對象倡蝙,所以總的引用關系圖就是這樣的:


79A0D278-6B42-424C-B4CE-827002C1DE63.png

當代碼運行到代碼中的斷點處時九串,并沒有打印--------dealloc,這說明這個時候person對象還沒有被釋放寺鸥,我們希望的是person對象出了大括號也就是作用域就被釋放猪钮,不然就是內(nèi)存泄漏了,那么這里為什么會內(nèi)存泄漏呢胆建?上圖中烤低,當出了大括號時,圖中的紅色箭頭就沒有了笆载,紅色箭頭沒有之后就變成了block強引用person對象扑馁,person對象強引用block,形成循環(huán)引用凉驻,就這樣person對象的內(nèi)存就一直不能被釋放腻要。

循環(huán)引用的解決辦法

要解決循環(huán)引用這個問題,很明確就要從上面的綠箭頭和黑箭頭上想辦法涝登,如果把其中之一變成虛線雄家,也即是弱引用,那循環(huán)引用就解決了缀拭。
那這個時候一個合適的方法就是把黑色的箭頭變成虛線咳短。

__weak,__unsafe_unretained

使用__weak或者__unsafe_unretained創(chuàng)建weakPerson對象,這樣block結(jié)構(gòu)體內(nèi)的成員變量person指針就是一個弱指針蛛淋,就能使黑色箭頭變成虛線:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        {
            
            Person *person = [[Person alloc] init];
            __weak typeof(person) weakperson = person;
            person.mblock = ^{
                NSLog(@"%@", weakperson);
            };
        }
        
        //在這里打斷點
    }
    return 0;
}

這樣代碼執(zhí)行到斷點處已經(jīng)打印了--------dealloc,說明循環(huán)引用已經(jīng)解決了。

用__block解決(必須要調(diào)用block)
image.png

使用__block,篡腌,它們之間的相互引用是這樣的:


7C9CA28D-C583-4D9D-A4D7-C779C975E803.png

那么有人就好奇了褐荷,這不是一個更大的循環(huán)引用嗎?這確實是一個循環(huán)引用嘹悼,但是如果block執(zhí)行了叛甫,也即將auto類型的自動變量置為nil,那么這條藍線就消失了杨伙,就打破了循環(huán)引用其监。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市限匣,隨后出現(xiàn)的幾起案子抖苦,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌历,死亡現(xiàn)場離奇詭異贮庞,居然都是意外死亡,警方通過查閱死者的電腦和手機究西,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門窗慎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卤材,你說我怎么就攤上這事遮斥。” “怎么了扇丛?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵伏伐,是天一觀的道長。 經(jīng)常有香客問我晕拆,道長藐翎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任实幕,我火速辦了婚禮吝镣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昆庇。我一直安慰自己末贾,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布整吆。 她就那樣靜靜地躺著拱撵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪表蝙。 梳的紋絲不亂的頭發(fā)上拴测,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音府蛇,去河邊找鬼集索。 笑死,一個胖子當著我的面吹牛汇跨,可吹牛的內(nèi)容都是我干的务荆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼穷遂,長吁一口氣:“原來是場噩夢啊……” “哼函匕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚪黑,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盅惜,失蹤者是張志新(化名)和其女友劉穎中剩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酷窥,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡咽安,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓬推。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妆棒。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沸伏,靈堂內(nèi)的尸體忽然破棺而出糕珊,到底是詐尸還是另有隱情,我是刑警寧澤毅糟,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布红选,位于F島的核電站,受9級特大地震影響姆另,放射性物質(zhì)發(fā)生泄漏喇肋。R本人自食惡果不足惜迹辐,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一蝶防、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧明吩,春花似錦间学、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仍律,卻和暖如春嘿悬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背染苛。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工鹊漠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茶行。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像登钥,于是被迫代替她去往敵國和親畔师。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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