iOS block 中使用 self 的那些事

414 阅读3分钟

我们在使用 block 的时候,如果在 block 中使用 self 有可能会循环引用,产生内存泄漏的问题。

通常,我们如果遇到这种情况,我们会将 self 转换成 weak automatic 的变量,这样就避免了 block 对self 强引用,即:

__weak typeof(self) weakSelf = self;
__weak __typeof__(self) weakSelf = self;
__weak __typeof(self) weakSelf = self;

以上三种写法,任选一种都是可以的,typeof 就是获取变量的类型,没别的深奥的东西。

但是,我在查看 AFNetworking 源码的时候发现发现,在有的 block 中使用self,作者并没有将 self 转化weakSelf,难道这样不会引起内存泄漏的问题吗?

为此,我做了相关的测试。我们知道如果存在内存泄漏,那么 dealloc 方法就不会被调用。

测试一:

@property (copy, nonatomic) dispatch_block_t testBlock;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.testBlock = ^(){
        NSLog(@"%@", [self class]);
    };
    self.testBlock();
}

这块我为了简单,直接使用 GCD 中的

typedef void (^dispatch_block_t)(void)

它是一个无参数,无返回值的 block。

这个 testBlock 是控制器(以下用 VC 代替)的一个属性。在 block 中直接使用self就会造成循环引用,Xcode也会做出相应的警告提示:

⚠️ Capturing self strongly in this block is likely to lead to a retain cycle.

这句话的意思就是说,此处的 block 强引用了 self,会存在保留环,即循环引用,那么 VC 的 dealloc 方法也不会被正常调用。这个时候我们就需要将其转化为 weakSelf 来打破这个保留环,避免内存泄漏。代码更正如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.testBlock = ^(){
        NSLog(@"%@", [weakSelf class]);
    };
    self.testBlock();
}

这样VC的 dealloc 方法也可以正常被调用了。

结论:当 block 直接做为 VC 的属性时,如果 block 内部没有使用 weakSelf,则会造成循环引用,导致内存泄漏。

测试二:

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;

    self.testBlock = ^(){
        [weakSelf doSomething];
    };
    self.testBlock();
}

- (void)doSomething {
    NSLog(@"%@", [self class]);
}

测试发现 VC 的 dealloc 方法被正常调用。

结论:当在 block 中调用一个方法,并且这个方法中直接或者间接的使用 self,不会出现内存泄漏的问题。

测试三:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_block_t block = ^(){
        [self doSomething];
    };
    block();
}

- (void)doSomething {
    NSLog(@"%@", [self class]);
}

测试发现 VC 的 dealloc 方法被正常调用,所以我们在使用 GCD 的时候,大部分情况都不需要做转换。

结论:当 block 不是 self 的属性时,block 内存使用 self不会造成内存泄漏的问题。

测试四:

- (void)viewDidLoad {
    [super viewDidLoad];

    Class VC = self.class;
    [VC doSomethingWithBlock:^{
        [self doSomething];
    }];
}

+ (void)doSomethingWithBlock:(dispatch_block_t)block {
    if (block) {
        block();
    }
}

- (void)doSomething {
    NSLog(@"%@", [self class]);
}

测试发现 VC 的 dealloc 方法被正常调用,我们在使用 UIView 有关动画的类方法时,大部分情况都不需要做转换。

结论:当使用类方法,并且类方法中用 block 做参数时,block 内部使用 self也不会造成内存泄漏的问题。

通过这么多的测试,我们可以看到,当且仅当 block 直接或间接的被 self 持有时,如果不做 weakSelf 转换,就会有内存泄漏的风险。

最后补充一点,同样在查看AFNetworking的时候,遇到有时候还需要转化成 strongSelf 的情况:

 __weak __typeof(self) weakSelf = self;
 
NSURLSessionDataTask *dataTask = nil;
dataTask = [self.sessionManager GET:request.URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    sf_dispatch_main_async_safely(^{
        if (success) {
            success((NSHTTPURLResponse *)task.response, responseObject);
        }
        [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:task.currentRequest.URL];
        if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
            [strongSelf.delegate webViewDidFinishLoad:strongSelf];
        }
    });
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    sf_dispatch_main_async_safely(^{
        if (failure) {
            failure(error);
        }
    });
}];

那么什么情况下,需要将 weakSelf 转化成 strongSelf 呢 ?

由于 __weak 变量的特殊性,会在对象销毁后自动置为 nil,如果在 block 中多次需要访问 self,就需要转化为 strong automatic,确保在 block 使用期间,self 不会被释放。