循环引用
来源:互联网 发布:淘宝卖汽车配件怎么样 编辑:程序博客网 时间:2024/06/03 14:50
循环引用是iOS开发中经常遇到的问题,尤其对于新手来说是个头疼的问题。循环引用对App有潜在的危害,会使内存消耗过高,性能变差和Crash等,iOS常见的内存主要以下三种情况:
1.Delegate
代理协议是一个最典型的场景,需要你使用弱引用来避免循环引用。ARC时代,需要将代理声明为weak是一个即好又安全的做法:
@property (nonatomic, weak) id delegate;
2.NSTimer
NSTimer我们开发中会用到很多,比如下面一段代码
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1target:self
selector:@selector(doSomeThing)
userInfo:nil
repeats:YES];
}
- (void)doSomeThing {
}
- (void)dealloc {
[self.timer invalidate];
self.timer = nil;
}
这是典型的循环引用,因为timer会强引用self,而self又持有了timer,所有就造成了循环引用。那有人可能会说,我使用一个weak指针,比如
__weak typeof(self) weakSelf = self;
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomeThing) userInfo:nil repeats:YES];
但是其实并没有用,因为不管是weakSelf还是strongSelf,最终在NSTimer内部都会重新生成一个新的指针指向self,这是一个强引用的指针,结果就会导致循环引用。那怎么解决呢?主要有如下三种方式:
- 使用类方法 -block方式
@interface NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(xx_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)xx_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
block();
}
}
@end
- 使用weakProxy
生成一个临时对象,让Timer强用用这个临时对象,在这个临时对象中弱引用self
- 使用GCD timer
具体如何使用,我就不做具体的介绍,网上有很多可以参考。
3.1 Block
Block的循环引用,主要是发生在ViewController中持有了block,比如:
@property (nonatomic, copy) LFCallbackBlock callbackBlock;
同时在对callbackBlock进行赋值的时候又调用了ViewController的方法,比如:
self.callbackBlock = ^{
[self doSomething];
}];
就会发生循环引用,因为:ViewController->强引用了callback->强引用了ViewController,解决方法也很简单:
__weak __typeof(self) weakSelf = self;
self.callbackBlock = ^{
[weakSelf doSomething];
}];
原因是使用MRC管理内存时,Block的内存管理需要区分是Global(全局)、Stack(栈)还是Heap(堆),而在使用了ARC之后,苹果自动会将所有原本应该放在栈中的Block全部放到堆中。全局的Block比较简单,凡是没有引用到Block作用域外面的参数的Block都会放到全局内存块中,在全局内存块的Block不用考虑内存管理问题。(放在全局内存块是为了在之后再次调用该Block时能快速反应,当然没有调用外部参数的Block根本不会出现内存管理问题)。
所以Block的内存管理出现问题的,绝大部分都是在堆内存中的Block出现了问题。默认情况下,Block初始化都是在栈上的,但可能随时被收回,通过将Block类型声明为copy类型,这样对Block赋值的时候,会进行copy操作,copy到堆上,如果里面有对self的引用,则会有一个强引用的指针指向self,就会发生循环引用,如果采用weakSelf,内部不会有强类型的指针,所以可以解决循环引用问题。
那是不是所有的block都会发生循环引用呢?其实不然,比如UIView的类方法Block动画,NSArray等的类的遍历方法,也都不会发生循环引用,因为当前控制器一般不会强引用一个类。
3.2 Block不允许修改外部变量的值
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
4.其他内存问题
1 NSNotification addObserver之后,记得在dealloc里面添加remove;
2 动画的repeat count无限大,而且也不主动停止动画,基本就等于无限循环了;
3 forwardingTargetForSelector返回了self。
内存解决思路:
1 通过Instruments来查看leaks
2 集成Facebook开源的FBRetainCycleDetector
3 集成MLeaksFinderFBRetainCycleDetector 。
阅读全文