Post

iOS 开发中的循环引用

iOS 开发中的循环引用

在 iOS 开发中,retain cycle(循环引用)是一个常见的内存管理问题,尤其是在使用 ARC (Automatic Reference Counting) 进行内存管理的环境下。如果两个对象相互持有强引用 (strong),它们都不会被 ARC 释放,从而导致内存泄漏。

什么是循环引用?

在 Objective-C 中,循环引用通常发生在 blockdelegate 之间。让我们来看一个典型的 block 相关的 retain cycle 示例。

示例:Block 引起的 Retain Cycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@property (nonatomic, strong) void (^myBlock)(void);
- (void)setupBlock;
@end

@implementation MyClass

- (void)setupBlock {
    self.myBlock = ^{
        NSLog(@"%@", self); // 这里的 `self` 被 `block` 强引用
    };
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyClass *obj = [[MyClass alloc] init];
        [obj setupBlock];
    }
    return 0;
}

在上面的代码中,self.myBlockblock 进行了强引用,而 block 也捕获了 self,导致循环引用,MyClass 的实例无法被释放。

如何避免循环引用?

方法 :使用 __weak 解决 Retain Cycle

Objective-C 提供了 __weak 关键字,可以让 self弱引用(weak reference) 的方式被 block 捕获,从而避免 retain cycle

1
2
3
4
5
6
- (void)setupBlock {
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        NSLog(@"%@", weakSelf); // weakSelf 不是强引用
    };
}

为什么 __weak 可以打破循环?

因为 __weak 只是一个 非强引用,它不会增加对象的引用计数,因此即使 block 还存在,也不会阻止 self 被释放。

循环引用的另一种常见场景:Delegate

循环引用不仅发生在 block,也可能出现在 delegate 模式,如果 A 持有 Bdelegate,而 B 强引用 A,就会发生循环引用。

示例:Delegate 引起的 Retain Cycle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface MyViewController : UIViewController
@property (nonatomic, strong) UITableView *tableView;
@end

@implementation MyViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.tableView.delegate = self; // 强引用可能会导致循环引用
    [self.view addSubview:self.tableView];
}

@end

如何解决?

1
@property (nonatomic, weak) id<UITableViewDelegate> delegate;

因为 delegate 通常是一个指向 self 的指针,我们应使用 weak 修饰以防止 retain cycle

总结

  • 循环引用发生在 对象相互强引用 的情况下,常见于 block 和 delegate
  • 使用 __weak 可以避免 block 持有 self,从而防止 retain cycle
  • delegate 设计模式中,使用 weak 来修饰 delegate 以避免循环引用。
This post is licensed under CC BY 4.0 by the author.