iOS_内存泄漏

2017-08-01  本文已影响160人  vvusu_w

为了测试内存泄漏方便,封装了一个Framework。

GitHub地址:https://github.com/wedxz/LNLeaksFinder

相似框架
MLeaksFinder
PLeakSniffer

使用的时候直接导入Framework就可以,而且只在debug下开启, 真机会报错误,请删处Framework。

内存泄漏注意事项

1.Block里面self强引用

_headerView.followClickBlock = ^() {
  if (![self isLogin]) {
      [[MRouter sharedRouter] handleURL:[NSURL URLWithString:@"https://passport.wallstreetcn.com/login"] userInfo:nil];
      return;
  }
  [weakSelf dealFollowClick];
};

block 里面的self用wself替换

__weak typeof(self) wself = self;

2.自定义代理时强引用delegate 会造成内存泄漏;

如果父VC持有子VC,并设置子VC的delegate为self(也就是父VC),这样的结果就是子VC也间接持有了父VC,造成循环引用,在Pop子VC的时候不会调用delloc.

@property (nonatomic, strong) id<ProfileEditPickerViewDelegate> delegate;

strong修饰符改为weak 不用assign

3.OC和CF转化出现的内存警告用完添加CFRelease()释放对象

CFStringRef cfString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)picDataString,NULL,CFSTR(":/?#[]@!$&’()*+,;="),kCFStringEncodingUTF8);
NSString *baseString = [NSString stringWithString:(NSString *)cfString];
CFRelease(cfString); //不添加会泄漏

4.CATransition 动画removedOnCompletion属性

    CATransition *animation = [CATransition animation];
    animation.delegate = self;
    animation.duration = 0.3f;
    animation.timingFunction = UIViewAnimationCurveEaseInOut;
    animation.fillMode = kCAFillModeForwards;
    animation.type = kCATransitionPush;
    animation.subtype = kCATransitionFromLeft;
    animation.startProgress = 0.0;
    animation.endProgress = 1.0;
    animation.removedOnCompletion = NO; //有内存泄漏的风险
    [self.layer addAnimation:animation forKey:@"animation"];
    [self.textField resignFirstResponder];

5.NSTimer
NSTimer会造成循环引用,timer会强引用target即self,在加入runloop的操作中,又引用了timer,所以在timer被invalidate之前,self也就不会被释放。
所以我们要注意,不仅仅是把timer当作实例变量的时候会造成循环引用,只要申请了timer,加入了runloop,并且target是self,虽然不是循环引用,但是self却没有释放的时机。如下方式申请的定时器,self已经无法释放了。

NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

解决这种问题有几个实现方式,大家可以根据具体场景去选择:

增加startTimer和stopTimer方法,在合适的时机去调用,比如可以在viewDidDisappear时stopTimer,或者由这个类的调用者去设置。
每次任务结束时使用dispatch_after方法做延时操作。注意使用weakself,否则也会强引用self。

- (void)startAnimation {
    __weak typeof(self) wself = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf commentAnimation];
    });
}

使用GCD的定时器,同样注意使用weakself。

__weak typeof(self) wself = self;
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
    [weakSelf commentAnimation];
});
dispatch_resume(timer);

6.NSNotification
使用block的方式增加notification,引用了self,在删除notification之前,self不会被释放,与timer的场景类似,其实这段代码已经声明了weakself,但是调用_eventManger方法还是引起了循环引用。
也就是说,即使我们没有调用self方法,_xxx也会造成循环引用。

[[NSNotificationCenter defaultCenter] addObserverForName:kUserSubscribeNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
    if (note) {
        Model *model=(Model *)note.object;
        if ([model.subId integerValue] == [weakSelf.subId integerValue]) {
            [_eventManger playerSubsciption:NO];
        }
    }
}

7.有一些图片很大用[UIImage imageNamed:@""]加载的话回常驻内存,建议大的图片加载从本地文件加载[UIImage imageWithContentsOfFile:@""]

8.performSelector延时调用导致的内存泄露

[self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3];

有时切换场景时延时函数已经被调用但还没有执行,这时tableLayer的引用计数没有减少到0,也就导致了切换场景dealloc方法没有被调用,出现了内存泄露。
所以解决办法就是取消那些还没有来得及执行的延时函数:

[NSObject cancelPreviousPerformRequestsWithTarget:self]
上一篇下一篇

猜你喜欢

热点阅读