介绍循环引用问题前,首先我们要简单的介绍一下iOS的内存管理方式引用计数。引用计数是一个简单而有效的管理对象生命周期的方式:

引用计数这种管理内存的方式虽然简单,但是有一个比较大的瑕疵,它不能很好的解决循环引用问题。
对象A和对象B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减1,这就导致了A的销毁依赖于B的销毁,同样B的销毁依赖于A的销毁,这样就造成了循环引用问题。

当然循环引用也为几种:
假如有一个对象,内部强持有它的成员变量obj,若此时我们给obj赋值为原对象时,就是自循环引用。

对象A内部强持有obj,对象B内部强持有obj,若此时对象A的obj指向对象B,同时对象B中的obj指向对象A,就是相互引用。

假如类中有对象1…对象N,每个对象中都强持有一个obj,若每个对象的obj都指向下个对象,就产生了多循环引用。

例如:我们平时经常用的协议传值,如果我们委托方的delegate属性使用strong强引用,就会造成代理方和委托方互相强引用出现循环引用问题。代理方强引用委托方对象,并且委托方对象中的delegate属性又强引用代理方对象,这就造成了循环引用问题。
@property (nonatomic, strong) id <MyCustomDelegate> delegate;

为了解决这个问题,我们只需要将委托方的delegate属性改为weak修饰就行了,这样委托方的delegate就不会强引用代理方对象了,简单解决了这个循环引用问题。
@property (nonatomic, weak) id <MyCustomDelegate> delegate;

(1)并不是所有block都会产生循环引用,block是否产生循环引用是需要我们去判断的,例如:
//这样是不会产生循环引用,因为这个block不被self持有,是被UIView的类对象持有,这个block和self没有任何关系,所以可以任意使用self。
[UIView animateWithDuration:0.0 animations:^{
[self viewDidLoad];
}];
(2)self -> reachabilityManager -> block -> self,才会产生循环引用,并且Xcode会给出循环引用warning,例如:
//self -> reachabilityManager -> block -> self 都是循环引用
self.reachabilityManager.stateBlock = ^(int number){
NSLog(@"%@",self. reachabilityManager);
};
//或者(block内部没有显式地出现"self",只要你在block里用到了self所拥有的东西,一样会出现循环引用!)
self.reachabilityManager.stateBlock = ^(int number){
NSLog(@"%@",_ reachabilityManager);
};
综合上述来看,要判断block是否造成了循环引用,我们要看block中的引用的变量和block外部引用block的变量会不会形成一个强引用的闭环,以此来判断block是否造成了循环引用的问题。
解决它其实很简单,无非就是self引用了block,block又引用了self嘛,让他们其中一个使用weak修饰不就行了:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"%@",weakSelf.reachabilityManager);
}];
但是仅仅使用__weak修饰self存在一个缺陷:__weak可能会导致内存提前回收weakSelf,在未执行NSLog()时,weakSelf就已经被释放了,然后执行NSLog()时就打印(null)。
所以为了解决这个缺陷,我们需要这样在block内部再用__strong去修饰weakSelf:
__weak __typeof(self) weakSelf = self;
[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.reachabilityManager);
}];
这就是我们平时所说的强弱共舞。
我们发现上述这个方法确实解决所有问题,但是可能会有两个不理解的点:
即使用weakSelf又使用strongSelf,这么做和直接用self有什么区别?为什么不会有循环引用?这是因为block外部的weakSelf是为了打破环循环引用,而block内部的strongSelf是为了防止weakSelf被提前释放,strongSelf仅仅是block中的局部变量,在block执行结束后被回收,不会再造成循环引用。
这么做和使用weakSelf有什么区别?唯一的区别就是多了一个strongSelf,而这里的strongSelf会使self的引用计数+1,使得self只有在block执行完,局部的strongSelf被回收后,self才会dealloc。
在使用NSTimer时我们会遇到很多循环引用问题,比如下面一段代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
}
- (void)doSomething {
}
- (void)dealloc {
[self.myTimer invalidate];
self.myTimer = nil;
}
这是典型的循环引用,因为myTimer会强引用self,而 self又持有了timer,所有就造成了循环引用。那有人可能会说,我使用一个weak指针,比如:
__weak typeof(self) weakSelf = self;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
但是其实并没有用,因为不管是weakSelf还是strongSelf,最终Runloop强引用NSTimer其也就间接的强引用了对象,结果就会导致循环引用。

那怎么解决呢?有两点:
分析一下两种方法,第一种方法如果控制器对NSTimer的引用改为弱引用,则会出现NSTimer直接被回收,所以不可使,因此我们只能从第二种方法入手。
主要有如下三种方式:
创建一个继承NSObject的子类MyTimerTarget,并创建开启计时器的方法。
// MyTimerTarget.h
#import <Foundation/Foundation.h>
@interface MyTimerTarget : NSObject
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;
@end
// MyTimerTarget.m
#import "MyTimerTarget.h"
@interface MyTimerTarget ()
@property (assign, nonatomic) SEL outSelector;
@property (weak, nonatomic) id outTarget;
@end
@implementation MyTimerTarget
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
MyTimerTarget *timerTarget = [[MyTimerTarget alloc] init];
timerTarget.outTarget = target;
timerTarget.outSelector = selector;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timerTarget selector:@selector(timerSelector:) userInfo:userInfo repeats:repeats];
return timer;
}
- (void)timerSelector:(NSTimer *)timer {
if (self.outTarget && [self.outTarget respondsToSelector:self.outSelector]) {
[self.outTarget performSelector:self.outSelector withObject:timer.userInfo];
} else {
[timer invalidate];
}
}
@end
// 调用方
@interface ViewController ()
@property (nonatomic, strong) NSTimer *myTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myTimer = [MyTimerTarget scheduledTimerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
}
- (void)doSomething {
}
- (void)dealloc {
NSLog(@"MyViewController dealloc");
}
@end
VC强引用timer,因为timer的target是MyTimerTarget实例,所以timer强引用MyTimerTarget实例,而MyTimerTarget实例弱引用VC,解除循环引用。这种方案VC在退出时都不用管timer,因为自己释放后自然会触发timerSelector:中的[timer invalidate]逻辑,timer也会被释放。
我们还可以对NSTimer做一个category,通过block将 timer的target和selector绑定到一个类方法上,来实现解除循环引用:
// NSTimer+MyUtil.h
#import <Foundation/Foundation.h>
@interface NSTimer (MyUtil)
+ (NSTimer*)MyUtil_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)(void))block repeats:(BOOL)repeats;
@end
// NSTimer+MyUtil.m
#import "NSTimer+MyUtil.h"
@implementation NSTimer (MyUtil)
+ (NSTimer *)MyUtil_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)(void))block repeats:(BOOL)repeats {
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(MyUtil_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)MyUtil_blockInvoke:(NSTimer *)timer {
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
@end
// 调用方
@interface ViewController ()
@property (nonatomic, strong) NSTimer *myTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myTimer = [NSTimer MyUtil_scheduledTimerWithTimeInterval:1 block:^{
NSLog(@"doSomething");
} repeats:YES];
}
- (void)dealloc {
if (_myTimer) {
[_myTimer invalidate];
}
NSLog(@"MyViewController dealloc");
}
@end
这种方案下,VC强引用timer,但是不会被timer强引用,但有个问题是VC退出被释放时,如果要停掉timer需要自己调用一下timer的invalidate方法。
创建一个继承NSProxy的子类MyProxy,并实现消息转发的相关方法。NSProxy是iOS开发中一个消息转发的基类,它不继承自NSObject。因为他也是Foundation框架中的基类,通常用来实现消息转发,我们可以用它来包装NSTimer的target,达到弱引用的效果。
// MyProxy.h
#import <Foundation/Foundation.h>
@interface MyProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
// MyProxy.m
#import "MyProxy.h"
@interface MyProxy ()
@property (weak, readonly, nonatomic) id weakTarget;
@end
@implementation MyProxy
+ (instancetype)proxyWithTarget:(id)target{
return [[MyProxy alloc] initWithTarget:target];
}
- (instancetype)initWithTarget:(id)target {
_weakTarget = target;
return self;
}
- (void)forwardInvocation:(NSInvocation*)invocation {
SEL sel = [invocation selector];
if (_weakTarget &&[self.weakTarget respondsToSelector:sel]) {
[invocation invokeWithTarget:self.weakTarget];
}
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
return [self.weakTarget methodSignatureForSelector:sel];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.weakTarget respondsToSelector:aSelector];
}
@end
// 调用方
@interface ViewController ()
@property (nonatomic, strong) NSTimer *myTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:[MyProxy proxyWithTarget:self] selector:@selector(doSomething) userInfo:nil repeats:YES];
}
- (void)dealloc {
if (_myTimer) {
[_myTimer invalidate];
}
NSLog(@"MyViewControllerdealloc");
}
@end
上面的代码中,了解一下消息转发的过程就可以知道-forwardInvocation: 是会有一个NSInvocation对象,这个NSInvocation对象保存了这个方法调用的所有信息,包括Selector名,参数和返回值类型,最重要的是有所有参数值,可以从这个NSInvocation对象里拿到调用的所有参数值。这时候我们把转发过来的消息和weakTarget的selector信息做对比,然后转发过去即可。
这里需要注意的是,在调用方的dealloc中一定要调用timer的invalidate方法,因为如果这里不清理timer,这个调用方dealloc被释放后,消息转发就找不到接收方了,就会crash。