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