前言: 在iOS 中使用block 时 ,如果稍微不注意,则很容易 导致 循环引用 导致内存泄漏 二者都无法释放 。出现内存泄漏。
#import <Foundation/Foundation.h>
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@protocol EOCNetworkFetcherDelegate;
@interface EOCNetworkFetcher : NSObject
- (instancetype)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler )handler;
@property (nonatomic,weak)id<EOCNetworkFetcherDelegate>delegate;
@property (nonatomic,strong,readonly)NSURL *url;
- (void)start;
@end
@protocol EOCNetworkFetcherDelegate <NSObject>
- (void)networkFetcher:(EOCNetworkFetcher *)networkFetcher
didFinishedWithData:(NSData *)data;
@end
. m
#import "EOCNetworkFetcher.h"
@interface EOCNetworkFetcher ()
@property (nonatomic,strong,readwrite)NSURL *url;
@property (nonatomic,copy)EOCNetworkFetcherCompletionHandler completionHandler;
@property (nonatomic,strong)NSData *downloadedData;
@end
@implementation EOCNetworkFetcher
- (instancetype)initWithURL:(NSURL *)url
{
if (self=[super init]) {
_url=url;
}
return self;
}
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler
{
// 这里只是设置 并没有 调起block 模拟假设3s 后返回了数据
self.completionHandler=handler;
[self performSelector:@selector(p_requestCompleted) withObject:nil afterDelay:3.0];
}
/**
这段代码 看上去没什么 问题 实际上在使用的时候 会有循环引用 导致的 内存泄漏
*/
- (void)p_requestCompleted
{
if (_completionHandler) {
_completionHandler(_downloadedData);
}
}
#pragma mark ---- Block 循环引用测试
我们假定 controller 是调用者
@interface ViewController ()<EOCNetworkFetcherDelegate>
@property (nonatomic,strong)EOCNetworkFetcher *fetcher;
@property (nonatomic,strong)NSMutableSet *set;
@end
/**
第一种形式的 循环引用
*/
- (void)retainCycleBlock
{
//
_fetcher=[[EOCNetworkFetcher alloc]initWithURL:[NSURL URLWithString:@"fooUrlString"]];
//
[_fetcher startWithCompletionHandler:^(NSData *data) {
// foo handler
_fetchedData=data;
NSLog(@"request completion url is %@",_fetcher.url);
}];
// @property (nonatomic,strong)EOCNetworkFetcher *fetcher;
// handler block 保留了 self 因为要获取 _fetchedData
// self 通过 strong 保留了 _fetcher
// 而 _fetcher 又保留了 hander 块 那么三者都无法释放
}
- (void)solutionOne
{
//
_fetcher=[[EOCNetworkFetcher alloc]initWithURL:[NSURL URLWithString:@"fooUrlString"]];
//
[_fetcher startWithCompletionHandler:^(NSData *data) {
// foo handler
_fetchedData=data;
NSLog(@"request completion url is %@",_fetcher.url);
/*
能够 打破循环引用
但是前提是 completionHandler 执行过后才会 解除引用 这样如果没有 执行那么依然会出现
*/
_fetcher=nil;
}];
}
/**
第二种形式 的 循环引用 也就是说 调用 handler 的对象 引用了 handler
这种往往 更隐蔽
*/
- (void)retainCycleBlockTwo
{
EOCNetworkFetcher *fethcer=[[EOCNetworkFetcher alloc]initWithURL:[NSURL URLWithString:@"someUrlString"]];
// 为了保持 fethcer 的存活 不被回收 我们把 fethcer 放进全局 是set 中 待网络完成后移除
[self.set addObject:fethcer];
// 一个要在代理方法中返回 一个直接在 调用的方法中返回 代码更清晰
[fethcer startWithCompletionHandler:^(NSData *data) {
_fetchedData=data;
NSLog(@"request completion url is %@",fethcer.url);
[_set removeObject:fethcer];
}];
}
- (NSMutableSet *)set
{
if (!_set) {
_set=[NSMutableSet set];
}
return _set;
}
解决方案是 调用 之后 设置 handler 为nil
- (void)p_requestCompletedNoCycle
{
if (_completionHandler) {
_completionHandler(_downloadedData);
}
_completionHandler=nil;
}
很多人说 那我为什么不 completion handler 作为EOCNetworkFetcher 的公共属性暴露出来呢?
因为那样的话 不方便 在执行 请求操作后 将其清理掉了 因为既然 handler 是公共的属性 ,那么应该交由调用者来做, 而调用者不一定会这么做 他们会吐槽你 封装的不够好。