iOS 循环引用

标签: 编程学习 iOS学习

什么是循环引用

循环引用是内存管理里面的一个概念,大家都知道iOS是使用引用计数来管理内存的,虽然目前大部分iOS开发都使用的是自动引用计数管理,但是并非绝对不会产生内存泄露,比如常见的循环引用问题。 循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。如果存在大量循环引用问题可能会导致内存暴增,导致App挂掉。

常见的循环引用

1.Delegate

如果 delegate 的修饰词写的 strong, 那么该对象强引用delegate,外界不能销毁 delegate 对象,会导致循环引用

@class TableViewCell;
@protocol TableViewCellDelegate <NSObject>
@required
- (void)tableViewCell:(TableViewCell *)cell;
@end

@interface TableViewCell : UITableViewCell
@property (nonatomic, weak) id <TableViewCellDelegate> delegate;
@end

2.Block

Block 是我们常用到的,也是我们最要注意循环引用的。在此,先拿一个我们使用 Block 的例子:

typedef void(^CellBlock)(void);

@interface View : UIView
@property (nonatomic, copy) CellBlock callBack;
@end

block 在 copy 时都会对 block 内部用到的对象进行强引用。 该类又将 block 作为自己的属性变量,而该类在 block 的方法体里面又使用了该类本身,此时就形成了一个环。 解决办法:

__weak typeof(self) weakSelf = self;
self.callBack = ^{
  __strong typeof(weakSelf) strongSelf = weakSelf;
  [strongSelf doSomething];
};

3.父组件与子组件

例如,我们在使用 UITableView 的时候,将 tableView 传给 Cell 使用

@interface TableViewCell : UITableViewCell
@property (nonatomic, strong) UITableView *tableView;
@end

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if(!cell) {
        cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    cell.tableView = tableView;
    return cell;
}

此时运行之后发现一堆 cell 是无法释放的,解决的办法就是将 cell 中 tableView 属性的修饰词换为 weak 就可以释放了。

@interface TableViewCell : UITableViewCell
@property (nonatomic, weak) UITableView *tableView;
@end

4.NSTimer,CADisplayLink

NSTimer、CADisplayLink 其实相对来说,我们其实是很容易忽略它这种情况的,毕竟还是很特殊的。

@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest {
    NSLog(@"%s", __func__);
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

@end

这样是无法释放滴,主要原因在于 target 那里,看了上面的处理其实只要打破双向强引用这个闭环就可以了,我们只需要让一方的引用变为弱引用就可以了,我这里的解决办法就是引用一个三方的类 我这里用的 NSProxy 这个类,这个类是专门用来做转发的

@interface DCProxy : NSProxy
@property (weak, nonatomic) id target;
+ (instancetype)proxyWithTarget: (id)target;
@end

@implementation DCProxy
+ (instancetype)proxyWithTarget:(id)target {
    // NSProxy 没有init方法
    DCProxy *proxy = [DCProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

最后直接在 target 哪里设置就行了

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[DCProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];