iOS:Block 循環引用問題

循環引用是一個比較常見的問題,以前面試的時候也會被問到,如何解決循環引用問題,其實你們都知道使用__block,__weak這些修飾符能夠解決循環引用問題,那今天咱們要討論的就是他們是怎麼樣解決了循環引用問題的。面試

__weak

其實__weak是比較好理解的,它的做用就是在兩方相互強引用的時候,把其中一個引用變爲弱引用,打破這個循環引用的圈。安全

咱們經過代碼看一下。函數

MyPerson * person = [[MyPerson alloc] init];
person.age = @"10";
__weak typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %@", weakPerson.age);
};

MyPerson類裏面有一個block,一個string類型的age,在執行block的時候,打印了age,若是不用weakPerson的話,就會產生循環引用,這種用法想必你們都很熟悉。spa

那咱們看一下編譯後的cpp文件。指針

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

能夠看到block內部捕獲到的是MyPerson *__weak weakPerson;,因此不會產生強引用,天然也就不會出現循環引用問題。code

__weak只在ARC環境下使用。對象

__block

最開始我覺得__block消除循環引用的方式跟__weak是同樣的。開發

//這種用法ARC環境下是錯的 MRC能夠
MyPerson * person = [[MyPerson alloc] init];
person.age = @"10";   
__block typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %@", weakPerson.age);
};

咱們如今開發一直都是在ARC環境下,首先本身反省一下,我一直都覺得__block能夠這麼用,並且關鍵是這樣用了確實編譯器就沒有了關於循環引用的警告了。rem

可是咱們若是重寫一下MyPerson類的dealloc方法,讓對象釋放時打印點東西,你會發現若是使用__weak,在main函數結束時,person會調用dealloc釋放,可是若是像上面同樣用__block,person不會釋放,仍是存在循環引用。編譯器

我強調了這種用法在ARC環境下不能夠,可是在MRC環境下是能夠的,由於MRC環境下block不會對__block修飾的屬性強引用。

下面是ARC環境正確的__block使用方式。

若是就按照__weak的使用方法使用,在block內部把weakPerson置爲nil,同時這個block必需要調用

MyPerson * person = [[MyPerson alloc] init];
person.age = @"10";
        
__block typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %@", weakPerson.age);
    weakPerson = nil;
};
person.block();//必須有這個代碼

也能夠這樣寫:

__block MyPerson * person = [[MyPerson alloc] init];
person.age = @"10";
person.block = ^{
    NSLog(@"age is %@", person.age);
    person = nil;
};
        
person.block();

兩種寫法都同樣,必須手動置爲nil,而後必須執行block。下面咱們說一下原理。

首先呢,咱們上面已經說了,若是不手動置爲nil的話,使用__block依然有循環引用,咱們結合cpp的代碼分析一下具體循環引用在什麼地方。

咱們編譯下面這種寫法。

MyPerson * person = [[MyPerson alloc] init];
__block typeof(person) weakPerson = person;
person.age = @"10";
person.block = ^{
    NSLog(@"age is %@", weakPerson.age);
};

咱們來分析一下下面的代碼

struct __Block_byref_weakPerson_0 {
  void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 typeof (person) weakPerson;
};

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

其實這個__block屬性的做用我們以前已經說過了,就是在block內部把修飾的屬性包裝成一個對象,也就是這個__Block_byref_weakPerson_0__Block_byref_weakPerson_0內部有咱們的weakPerson屬性,typeof (person) weakPerson,這裏只是叫weakPerson,他的持有方式仍是strong的。

因此說咱們能夠分析出來,__block屬性持有咱們的person變量,person持有block,block內部持有這個__block屬性,就像下面這個圖示同樣。

__block循環引用.png

咱們經過置爲nil解決它循環引用的方式,就是打斷一條強引用。以下圖

__unsafe_unretained

ARC環境下__unsafe_unretained與__weak使用方法相同。

MRC環境下,與__block MRC環境下的使用同樣。

__unsafe_unretained和__weak對比:

__weak:不會產生強引用,指向的對象銷燬時,會自動讓指針置爲nil

__unsafe_unretained:不會產生強引用,不安全,指向的對象銷燬時,指針存儲的地址值不變

相關文章
相關標籤/搜索