1.概念 循环引用可以简单理解为 A 强引用 B,B 又强引用了 A,双方同时保持对方的一个引用,导致引用计数不为0,始终无法释放。
影响?
导致对象无法释放,造成内存泄露。如果是 ViewController 内出现循环引用,在反复 push & pop 后,ViewController 释放不掉,导致内存激增,甚至引发crash。
2.场景 2.1.block 下面是一段关于 block 的描述:
When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:
If you access an instance variable by reference, a strong reference is made to self;
If you access an instance variable by value, a strong reference is made to the variable.
使用copy
修饰的 block 会被放到堆中。block 会强引用 block 块中捕获到的对象。
如果在 block 中访问了属性,那么 block 就会强引用 self。
如果在 block 中访问了一个局部变量,那么 block 就会强引用该变量。
block 的循环引用一般表现为,block 作为属性被类的实例(self)强引用,然后实例在block中又引用了实例本身。self -> block -> self
。编写代码时,这种循环引用编译器能捕捉到并给出提醒:“Capturing ‘self’ strongly in this block is likely to lead to a retain cycle”。
注意:并不是所有block都会造成循环引用。只有被强引用
了的 block 才会产生循环引用,一些系统封装的block就不存在循环引用问题,如:
1 2 3 4 5 dispatch_async(dispatch_get_main_queue(), ^ { } ); [UIView animateWithDuration:1 animations:^ { } ];
#循环引用示例1 自定义的一个Student
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef void (^StudentBlock)();@interface Student : NSObject @property (copy , nonatomic ) NSString *name;@property (copy , nonatomic ) StudentBlock block;@end @implementation Student - (void )dealloc{ NSLog (@"++%@ dealloced~" ,[self class ]); }@end
在某个VC中调用:
1 2 3 4 5 6 7 8 9 - (void)viewDidLoad { [super viewDidLoad]; Student *student = [[Student alloc] init]; student.name = @"D" ; student.block = ^{ NSLog(@"Hello %@ ~" ,student.name ); }; student.block (); }
此示例中,student
中有个block
属性,block
内强引用了student
对象本身,这就造成循环引用,导致student
无法释放。
在对象的 block 内访问__block
修饰的变量代替原对象,并在调用完之后将__block
变量或 block 本身置为nil
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void)viewDidLoad { [super viewDidLoad]; Student *student = [[Student alloc] init]; student.name = @"D" ; __block Student *st = student; student.block = ^{ NSLog(@"Hello %@ ~" ,st.name ); st = nil ; }; student.block (); }
这种办法的缺点很明显:如果不调用此 block 或没将其置为 nil,那么st
就不被置为 nil,依然强引用student
,循环引用依然存在。
#循环引用示例2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #import "AppDelegate.h" @interface AppDelegate ()@property (nonatomic , copy ) void (^aBlock)(void );@property (nonatomic , copy ) NSString *aName;@end @implementation AppDelegate - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self .aBlock = ^{ self .aName = @"THIS IS A NAME" ; }; dispatch_async (dispatch_get_main_queue(), ^{ self .aName = @"THIS IS A NAME 2" ; }); return YES ; }@end
通过__weak
修饰符,先弱引用self
,再通过__strong
强引用weakSelf
,然后在 block 里使用strongSelf
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog (@"+++self.rfc:%ld" ,(long )CFGetRetainCount ((__bridge CFTypeRef )(self ))); __weak typeof (self ) weakSelf = self ; NSLog (@"+++self.rfc:%ld,weakSelf.rfc:%ld" ,(long )CFGetRetainCount ((__bridge CFTypeRef )(self )),CFGetRetainCount ((__bridge CFTypeRef )(weakSelf))); self .aBlock = ^{ __strong typeof (weakSelf) strongSelf = weakSelf; NSLog (@"+++weakself.rfc:%ld" ,(long )CFGetRetainCount ((__bridge CFTypeRef )(weakSelf))); strongSelf.aName = @"THIS IS A NAME" ; }; self .aBlock(); return YES ; }
输出日志:
原理:__weak
修饰的weakSelf
是一个弱引用,它指向self
但不会增加self
的引用计数,从而打破self
与block
之间的循环引用。
ps: 这里之所以再强引用一下 weakSelf,是因为__weak
修饰的对象都是弱引用,随时可能会被系统释放,造成后面调用 weakSelf 时 weakSelf 可能已经是nil了。__strong
修饰的strongSelf
是一个强引用,保证了weakSelf
在 block 声明周期内不会被销毁,而且strongSelf
是一个自动变量,在 block 执行完毕后会自动释放。
将在 block 内要使用到的对象(一般为self对象),以 block 参数的形式传入,block 就不会捕获该对象,而是将其作为参数使用,其生命周期由系统的栈自动管理,也不会造成内存泄露。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @interface AppDelegate ()@property (nonatomic , copy ) void (^aBlock)(AppDelegate *delSelf);@property (nonatomic , copy ) NSString *aName;@end @implementation AppDelegate - (BOOL )application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self .aBlock = ^(AppDelegate *delSelf){ delSelf.aName = @"THIS IS A NAME" ; }; return YES ; }@end
2.2.NSTimer 一般是 NSTimer 被作为类的成员变量,在期望类销毁时,NSTimer 尚处于 validate 状态。此时不管 NSTimer 对象是否被置为nil,类都无法正常销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @interface ViewController ()@property (nonatomic , strong ) NSTimer *aTimer;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; _aTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector (action) userInfo:nil repeats:YES ]; } - (void )dealloc{ NSLog (@"dealloced!!!" ); } - (void )viewWillDisappear:(BOOL )animated { [super viewWillDisappear:animated]; _aTimer = nil ; } - (void )action{ NSLog (@"a Timer action!" ); }@end
上面的示例中, ViewController 出现后过段时间退出,dealloc 不会执行。这是因为 NSTimer 创建后被加入到 NSRunloop 中,失效之前会一直持有 self 作为 target。需要在合适的时机执行 invalidate 来打破这个循环引用即可。如下:
1 2 3 4 5 6 - (void )viewWillDisappear:(BOOL )animated { [super viewWillDisappear:animated]; [_aTimer invalidate]; _aTimer = nil ; }
2.3.delegate 一般是 A 类中声明了一个 strong 的 delegate,B 类强引用 A 类,同时把 A 类的 delegate 指向 B 自己。B -> A -> delegate -> B。解决办法:声明 delegate 时,使用 weak 修饰符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #import <UIKit/UIKit.h> @protocol CustomDelegate <NSObject > - (void )onDelegateCallback;@end @interface ClassA : NSObject @property (nonatomic , weak ) id <CustomDelegate> delegate;@end @interface ClassB ()@property (nonatomic , strong ) ClassA *classA;@end @implementation ClassB - (void )viewDidLoad { [super viewDidLoad]; self .classA = [[ClassA alloc] init]; self .classA.delegate = self ; }