iOS Block 中 循环引用的解决

前言: 在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 是公共的属性 ,那么应该交由调用者来做, 而调用者不一定会这么做 他们会吐槽你 封装的不够好。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值