block-循環引用

block的循環引用,在日常開發中,我們常常遇到,但是可能部分新人還不太瞭解爲何會循環引用,到底是如何循環引用理解得不夠透徹,並且在ARC環境下只知道用__weakSelf去解決,但也不知道原因,現在我們來剖析一下,循環引用的的底層原理。看看下面一段常見的代碼:

循環引用原因分析

#import <Foundation/Foundation.h>
#import "RMPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        RMPerson *person = [[RMPerson alloc] init];
        person.age = 20;
        person.block = ^{
            NSLog(@"age is %d",person.age);
        };
        person.block();
    }
    NSLog(@"----------------");
    return 0;
}

---------------RMPerson.h----------------
#import <Foundation/Foundation.h>
@interface RMPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) void (^block)(void);
@end
---------------RMPerson.h----------------
#import "RMPerson.h"
@implementation RMPerson
@end

// 控制檯打印
2018-07-04 11:27:23.129229+0800 block-循環引用[26623:2507647] age is 20
2018-07-04 11:27:23.129421+0800 block-循環引用[26623:2507647] -----------------
Program ended with exit code: 0

從上面的代碼可以得出,block調用完後,person都沒用釋放,NSLog(@"----------------");打印完了,person也還沒釋放,說明person引用計數器不爲0。
用clang 命令將 這段代碼轉換成C++代碼(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m)如下:

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  RMPerson *__strong person = __cself->person; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_0xn052bn6dgb9z7pfk8bbg740000gn_T_main_d61985_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
        }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {

_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);

}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {

    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);

}

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

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


        RMPerson *person = ((RMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((RMPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("RMPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)person, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344)));
        ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)person, sel_registerName("block"))();
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

這段代碼,我們再熟悉不過了,因爲從之前block的章節中,幾乎每一個相關的block小問題,都會上底層代碼來研究其原理。循環引用的原因就是,1.person裏的屬性block強引用block,因block又捕獲了person,block在copy的時候,_Block_object_assign根據person是strong,還是__weak,來對person的引用計數器做是否+1的處理,而此處的person是strong修飾的,所以block又強引用person,person的引用計數器+1。所以2.block又強引用了person
用圖片表示就是下面:
block循環引用

解決循環引用問題

1、用__weak、__unsafe_unretained解決
//  __weak
RMPerson *person = [[RMPerson alloc] init];
person.age = 20;
__weak typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %d",weakPerson.age);
};
person.block();
  • 如上使用__weak可解決循環引用,也是開發中最常用最安全的方法,使用__weak,block捕獲person的進去時,block不會強引用person,而是對對象弱引用,如下圖
    弱引用
// __unsafe_unretained
RMPerson *person = [[RMPerson alloc] init];
person.age = 20;
__unsafe_unretained typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %d",weakPerson.age);
};
person.block();
  • 使用__unsafe_unretained也可以解決,__unsafe_unretained顧名思義就是不安全、不retained的意思,__unsafe_unretained__weak相比較主要區別是在於,當person釋放的時候,block也隨之銷燬,但是在__unsafe__unretained修飾下的weakPerson會依然指向之前的內存空間,此時weakPerson訪問的就是”殭屍對象”,所以就是不安全。
總結:
  • __unsafe_unretained: 不會對對象進行retain,當對象銷燬時,會依然指向之前的內存空間(野指針)
  • __weak: 不會對對象進行retain,當對象銷燬時,會自動指向nil

相比之下,建議開發中使用__weak__weak更安全,更有效

2、用__block解決(必須調用block)
RMPerson *person = [[RMPerson alloc] init];
__block weakPerson = person;
self.block = ^ {
     NSLog(@"person : %@",weakPerson);
     weakPerson = nil;
};
self.block();

__block修飾的變量,會被包裝成一個對象,也持有了person對象,如下面一張圖

所以,要解決循環引用,就把__block變量持有的person對象的指針置爲nil後,就可以解決,也因此必須要調用block,才能將__block變量置空如下圖:

總結:循環引用是因爲,對象強引用了blcok,block內部也強引用了捕獲進去的對象,相互引用無法釋放。解決循環引用,1、用__weak、__unsafe_unretained解決,2、用__block解決(必須調用block).


以上分析得解決循環引用的都是在ARC環境下的,現在也簡單下分析MRC環境下是如何避免循環引用。
解決循環引用(MRC環境)

1、用__unsafe_unretained解決(MRC環境下是不支持__weak弱指針的)

// __unsafe_unretained
RMPerson *person = [[RMPerson alloc] init];
person.age = 20;
__unsafe_unretained typeof(person) weakPerson = person;
person.block = ^{
    NSLog(@"age is %d",weakPerson.age);
};
person.block();

2、用__block解決

RMPerson *person = [[RMPerson alloc] init];
__block weakPerson = person;
self.block = ^ {
     NSLog(@"person : %@",weakPerson);
};

在MRC環境下,__block不會對person強引用,所以不會存在循環引用。

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