常见内存泄露

一, 常见内存泄露种类

  1. block 循环引用 及 delegate 的强引用.
  2. 单例对 block 进行了 copy 操作, 或对 delegate 进行了强引用操作.
  3. 给 NSTimer 添加了 runloop, runloop 一直持有 NSTimer
  4. 某些非OC对象的内存要及时释放
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
CGImageRelease(image);

二, block 类型的循环引用常见形式

搜索了项目中所有的"^(", 共 6681 处, 排除参数为MASConstraintMaker类型的block, 名称为 enumerateObjectsUsingBlock: 类型的 block, 或者名称为animateWithDuration:animations:completion: 方法且最后一个参数为(BOOL finished)的, 发现了以下几种情况.

1. block 中含有下划线的属性, 如:

        [_moreButton bk_addEventHandler:^(UIButton *sender) {
            sender.selected = !sender.selected;
            _moreTableView.hidden = !sender.selected;
        } forControlEvents:UIControlEventTouchUpInside];

    WeakSelf
    item1.operationBlock = ^(){
            _bAllow = !_bAllow;
            [self updateForbidMsg:_bAllow];
            if (_bAllow) {
                [self postAllowMsgSettingLevel:_level];
            } else {
                [self postForbidMsgSetting];
            }
    };

2. cell 的block 中含有tableview

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CarTeamInformationActCell *cell = [tableView dequeueReusableCellWithIdentifier:[CarTeamInformationActCell reuseID] forIndexPath:indexPath];
    WeakSelf
    __weak UITableView *weakTableView = tableView;
    cell.didSelectedPickUp = ^{
        [weakSelf selectPickUp:weakTableView indexPath:indexPath];
    };
    return cell;
}

3. 单例类中含有 copy 的对象

由于单例的内存 build 释放, copy 的 block 也会对对象进行强引用.

+ (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
    MJRefreshFooter *cmp = [[self alloc] init];
    cmp.refreshingBlock = refreshingBlock;
    return cmp;
}

三, runloop 对 target 的持有

NSTimer 会强引用传入的target,如果同时把 NSTimer 加入 NSRunLoop , 这个timer又会被NSRunLoop强引用, 铁索连环, 导致 target 无法释放.

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

在快速进入并退出直播间的时候, MLeaksFinder 会弹框提示直播间的内存没有及时释放, 点击对应的 retain cycle, 发现是 NSTimer -> __ NSCFTimer 泄露, 可以使用 NStimer 的 userinfo 参数, 将定时器操作封装成 block 通过 userinfo 传递.

+ (id)safe_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)())inBlock repeats:(BOOL)inRepeats {
    void (^block)() = [inBlock copy];
    id ret = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(jdExecuteSimpleBlock:) userInfo:block repeats:inRepeats];
    return ret;
}

+ (void)jdExecuteSimpleBlock:(NSTimer *)inTimer {
    if ([inTimer userInfo]) {
        void (^block)() = (void (^)())[inTimer userInfo];
        block();
    }
}

由于 NSTimer 是基于runloop的,所以在GCD(延迟操作)里使用时会出现问题. 也可以使用YYWeakProxy中转

- (void)initTimer {
    YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
    _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(onBtnClick:) userInfo:nil repeats:YES];
 }

四, 强引用父类

曾经遇到过子类在初始化时, 用一个 strong 类型的属性指向了父类, 导致父类拥有了子类, 子类也强引用父类, 无法释放内存.查遍了相关类的所有属性, 才发现此问题.

@property (nonatomic,strong) UIViewController *parentVc;
  ...
- (instancetype)initWithParentVc:(UIViewController *)vc{
    if (self = [super init]) {
        self.parentVc = vc;
    }
    return self;
}

五, 总结

  1. 也是最近崩溃的问题稍微有点儿多, 反复进入直播间, 很容易出现崩溃
  2. 排除了有直接导致崩溃的原因后, 发现有可能是内存太高了
  3. 对比以往版本, 发现内存增高的原因只可能是内存泄露了, 有一些循环引用导致内存没有及时释放.
  4. 使用腾讯的 MLeaksFinder 初步定位没有释放内存的对象, 然后在有嫌疑的类中查找"^{" 和 "^(", 检查被 copy 的 block 中是否有对 self 的强引用, 或者下划线属性.
  5. 在这些类中加上 dealloc, 检查有没有被调用.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容