循環引用是一個比較常見的問題,之前面試的時候也會被問到,如何解決循環引用問題,其實大家都知道使用__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屬性,就像下面這個圖示一樣。
我們通過置爲nil解決它循環引用的方式,就是打斷一條強引用。如下圖
__unsafe_unretained
ARC環境下__unsafe_unretained與__weak使用方法相同。
MRC環境下,與__block MRC環境下的使用一樣。
__unsafe_unretained和__weak對比:
__weak:不會產生強引用,指向的對象銷燬時,會自動讓指針置爲nil
__unsafe_unretained:不會產生強引用,不安全,指向的對象銷燬時,指針存儲的地址值不變