循环引用

循环引用是iOS开发中经常遇到的问题,尤其对于新手来说是个头疼的问题。循环引用对App有潜在的危害,会使内存消耗过高,性能变差和Crash等,iOS常见的内存主要以下三种情况:
1.Delegate
代理 协议 是一个最典型的场景,需要你使用弱引用来避免循环引用。ARC时代,需要将代理声明为weak是一个即好又 安全 的做法:
@property (nonatomic, weak) id  delegate;
2.NSTimer
NSTimer我们开发中会用到很多,比如下面一段代码
- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 tar get: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 NSNotifi cat ion addObserver之后,记得在dealloc里面添加remove;
2 动画的repeat count无限大,而且也不主动停止动画,基本就等于无限循环了;
3 forwardingTargetFor Select or返回了self。
内存解决思路:
1 通过Instruments来查看leaks
2 集成 Facebook 开源 的FBRetainCycleDetector
3 集成MLeaksFinder FBRetainCycleDetector  。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值