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的話,就會產生循環引用,這種用法想必大家都很熟悉。

那我們看一下編譯後的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;,所以不會產生強引用,自然也就不會出現循環引用問題。

__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可以這麼用,而且關鍵是這樣用了確實編譯器就沒有了關於循環引用的警告了。

但是我們如果重寫一下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:不會產生強引用,不安全,指向的對象銷燬時,指針存儲的地址值不變

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章