循环引用(Retain Cycle)是iOS开发中一个非常关键且容易出现的内存泄漏问题。以下是一些最常见的场景:
-
Block捕获外部对象:
-
场景:在对象内部定义一个Block,并在Block内部使用了该对象的属性或方法。
-
原因:如果对象强引用了这个Block(例如将Block赋值给对象的属性),而Block又强引用了对象自身(通过
self),就会形成循环引用。 -
示例:
@interface MyClass : NSObject @property (nonatomic, strong) NSString *name; @end @implementation MyClass - (void)doSomething { // 这里的self被block捕获,形成了循环引用 void (^myBlock)(void) = ^{ NSLog(@"Name is: %@", self.name); // block强引用了self }; // 如果将block赋值给self的属性(强引用) self.block = myBlock; // self -> block -> self (循环引用!) } @end -
解决方法:使用
__weak修饰符捕获self,避免强引用。- (void)doSomething { __weak typeof(self) weakSelf = self; // 使用weakSelf void (^myBlock)(void) = ^{ // block内使用weakSelf代替self NSLog(@"Name is: %@", weakSelf.name); }; self.block = myBlock; }
-
-
Delegate模式:
-
场景:A对象持有B对象的引用(强引用),而B对象又持有A对象的引用(通常是delegate属性)。
-
原因:如果A和B都使用强引用对方,则形成循环引用。
-
示例:
@interface ViewController : UIViewController @property (nonatomic, strong) UITableView *tableView; @end @interface CustomTableViewDataSource : NSObject <UITableViewDataSource> @property (nonatomic, weak) ViewController *viewController; // 使用weak避免循环引用 @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.dataSource = [[CustomTableViewDataSource alloc] init]; // 如果CustomTableViewDataSource的属性是strong的,且指向self,则可能产生循环引用 // 但通常dataSource使用weak,所以这里不会直接造成循环引用 } @end -
解决方法:确保Delegate属性使用
weak修饰符,这是约定俗成的做法。如上例所示,viewController属性应该声明为weak。
-
-
定时器(NSTimer) :
-
场景:对象A创建了一个NSTimer,并将timer的target设置为自身(self)。
-
原因:如果timer的target是self,那么timer会强引用self;同时,如果timer被添加到RunLoop中,RunLoop也会强引用timer。如果对象A也强引用了timer,就会形成循环引用。
-
示例:
@interface MyClass : NSObject @property (nonatomic, strong) NSTimer *timer; @end @implementation MyClass - (void)startTimer { // 这里将timer的target设置为自己,容易形成循环引用 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; // self -> timer -> self (循环引用!) } - (void)timerFired:(NSTimer *)timer { // ... } - (void)dealloc { // 必须在dealloc中invalidate timer,否则可能在timer fire时访问已释放的对象 [self.timer invalidate]; self.timer = nil; } @end -
解决方法:
- 使用
weak修饰符(如iOS 10+的NSTimer支持weak)。 - 或者将timer的target设置为一个不持有timer的中间对象。
- 更重要的是,在对象销毁前调用
[timer invalidate]。
- 使用
-
-
自定义的强引用关系:
-
场景:开发者在代码中手动建立了对象之间的强引用关系。
-
原因:如果A强引用B,B强引用A,就会形成循环引用。
-
示例:
@interface Parent : NSObject @property (nonatomic, strong) Child *child; @end @interface Child : NSObject @property (nonatomic, strong) Parent *parent; @end @implementation Parent - (void)addChild { self.child = [[Child alloc] init]; self.child.parent = self; // 造成循环引用 } @end -
解决方法:通常在这种情况下,其中一个方向应该使用
weak引用。例如,Child的parent属性应声明为weak。
-
总结来说,循环引用的核心在于两个或多个对象之间存在相互的强引用关系。解决的关键是打破这种强引用链,通常使用weak或unowned来避免强引用,特别是在Block、Delegate、定时器等场景中。同时,遵循良好的编程习惯,如及时清理资源(如移除KVO观察者、销毁定时器等),也是防止循环引用的重要手段。