app 内存优化笔记

阶段

1. Leaks 处理一 😂😂😂😂😂😂
2. Leaks 处理二 🤓🤓🤓🤓🤓🤓
3. 其他处理 😁😁😁😁😁😁

简介

项目业务随着开发迭代趋于稳定的时候,进入一段空白期,但是对于我们程序猿来说闲暇不是件好事,要让自己忙碌起来,这个时候我们就可以考虑完善和优化我们的项目了,既能优化了我们的产品性能,又能提高了我们的专业技能。本次我这边简单写了个笔记,记录一下我们项目初步优化过程。

Leaks 处理一(MLeaksFinder)

我初期,选择了weread团队开发的MLeaksFinder,这款Lib有比较友好的提示页面,PS:不是所有的内存她都能检测到,目前只能自动地检测 UIViewController 和 UIView 相关的对象~!,比较方便的一点地方就是,这货Debug模式下,遇到leaks地方直接Alert view 告诉你,Release模式自动关闭,我用该库检测到了部分遗留未释放的VC和View,大部分都是NSTimerNSNotificationCenterBlockWebViewScriptMessageHandler ....引起的循环引用,;Lib git 传送门-->

👇就是个人页 Cell持有了NSTimer,在VC 消失后未对timer 进行invalidate操作;

Memory Leaks
(
    MineViewController,
    view
    tableView,
    minesettingCell
)

根据👆提示我们是不是很快发现问题并且定位到类,找到泄露位置修复它,是不是很简单~!

如果其他团队开发者选择无视这个提示,我们在基类里面设置断言,让他一次crash个够~ 😏

- (BOOL)willDealloc {
    __weak id weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf assertNotDealloc];
    });
    return YES;
}

- (void)assertNotDealloc {
    NSString *clasName = NSStringFromClass([self class]);
    if ([clasName isEqualToString:@"MineViewController"])
    {// 这里忽略不需要 断言的 vc
        return;
    }
    NSAssert(NO, @"断言 ---------------------> 泄漏了");
}

OK,这是比较简单便捷的方式,检测和处理循环引问题,我们可以借助这个Lib,但也不能完全指望它,论检测内存泄露,苹果自己提供的有专业工具Instruments,我们看👇~

Leaks 处理二 (Instruments)

Xcode的Instruments里面有一个Leaks工具,可以帮助你定位发生内存泄漏的代码段,以便修复问题。通过👇方式打开Instruments面板PS:也可在Xcode页面 Command + i 快捷键启动

打开 Instruments.png

选择Leaks工具,打开后界面如👇图:

打开 Leaks.png

选择Target,在右下角Display Setting面板的Call Tree,勾选Invert Call Tree和Hide System Libraries,方便接下来我们迅速查找有内存问题的代码段。

instruments-Leaks.png

上面"打钩"选项默认是不选的,我们通常根据自己的需求,把它们勾选上,可以帮你更快定位到关键的问题代码上。

  • Separate by Category:按类别做分析,这样可以清晰的看到吃资源的问题线程的分类。
  • Separate by Thread:按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。
  • Invert Call Tree:反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。
  • Hide System Libraries:隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。
  • Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

双击打开提示Leaks的函数,进入到可视代码页面👇,这里我们可以直观的看到内存泄露的地方,针对当前代码做出修改和优化:

instruments-Call Trees@2x.png

既然找到了问题所在,我们修改着也比较容易了,毕竟代码都是人写的,看得懂就好改了😆

+(ShareManager *)shareManager
{
    static ShareManager *instance = nil;
    static dispatch_once_t oneToken;
    dispatch_once(&oneToken,^{
        instance = [[ShareManager alloc] init];
    });
    return instance;
}
- (instancetype)init
{
    if (self = [super init]) 
    {
        // 单例 设置通知监听,肯定是释放不了的; 所以这里报了警告⚠️
        // 改之前
        [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
            if([[NSUserDefaults standardUserDefaults]boolForKey:@"isIntoGuide"])
            {
                  [self setupXHLaunchAd];
            }
        }];
        //  改之后
        if([[NSUserDefaults standardUserDefaults]boolForKey:@"isIntoGuide"])
        {
             [self setupXHLaunchAd];
        }
    }
    return self;
}

其他处理

其实有很多时候,内存泄露对象占用内存并不是太多,比如👆我们看到的几~几百Bytes,我们发现他并解决掉就完了;

很多时候,在我们应用开发中图片和视频内存开销是相当大的,如果我们没有处理好渲染--加载--缓存--释放问题,应用内存分分钟飚到GB级别,并且分分钟就crash给你看;

因为时间有限和当前发现问题比较大的地方就是图片加载,所以这次主要对图片进行了优化,因为涉及到了高清大图片加载,在处理图片的时候,有很多方式,👇介绍三种种方式:

后端 + 前端

条件允许的情况下(可扩展性强)

  • 请后端帮忙搭建流媒体服务器,对图片进行压缩处理;
  • 前端定义不同的协议,根据参数不同去取 Small / Medium / Big 不同规格的图片;
  • 后端根据前端传递的不同参数,对图片进行压缩,生成新的image url给前端;
  • 前端再对不同参数请求下来的图片进行加载(也可以再次处理,只要运营同学不嫌图片不清楚);
    这样图片加载过程中,内存会降下来很多~!真的,亲试过~
WebP image

WebP,是一种同时提供了有损压缩与无损压缩的图片文件格式。现在主流应用界面需要大量图片来,可以嵌入 WebP 的解码包,能够节省用户流量,提升访问速度优势:对于 PNG 图片,WebP 比 PNG 小了45%。👇是jpg vs webp, 是不是很厉害~

jpg vs webp.png

想更多的了解WebP知识的童鞋点击 A new image format for the Web
libwebp下载地址 libwebp download

这里就介绍一下具体怎么用,我们通过pod 'SDWebImage/WebP' 引入经典图片加载库SDWebImageWebP 的支持模块;
使用方式上跟加载普通图片没什么区别,当然也需要让SDWebImage支持WebP,设置如下Build Settings ---> Preprocessor Macros 添加 SD_WEBP = 1

Build Settings.png

PS: 这里也需要后端的童鞋们协助,对图片统一转换成webp格式,这时候你再去测试,图片压缩了很多,既提高图片下载速度、也为用户节省了流量、同样也减轻图片加载时候内存占用~一举多得;

only 前端

如果👆两种方式都没有人提供支持的话,自己的活自己干,谁让你的应用内存占用太高呢,甚至老Crash呢~
自己加入一些机制来解决 预防 避免图片加载内存过大Crash问题,也在合适的时机去释放这些资源,不要常驻内存,使用户感觉用的越久应用越卡;

目前项目加载使用的是SDWebImage,相信在座的各位基本都用过,国内外太多的App使用其进行图片加载,很主流,但是在用SDWebImage加载多个图片过程中,加载几张图片就内存暴增严重导致崩溃。

SDWebImage.png
  • 图片压缩 / 缓存
    因为目前整个工程所有的图片加载都是用的此库,我们针对该库做一些优化,SDWebImage有一个SDWebImageDownloaderOperation类来执行下载操作的,我们抽丝剥茧的定位到了 UIImage+MultiFormat文件中的 sd_imageWithData:方法

+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
    if (!data) {
        return nil;
    }
    UIImage *image;
    SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:data];
    if (imageFormat == SDImageFormatGIF) {
        image = [UIImage sd_animatedGIFWithData:data];
    }
#ifdef SD_WEBP
    else if (imageFormat == SDImageFormatWebP)
    {
        image = [UIImage sd_imageWithWebPData:data];
    }
#endif
    else {
      //⚠️⚠️⚠️发现这里面对图片的处理是直接按照原大小进行的,如果几千是分辨率这里导致占用了大量内存⚠️⚠️⚠️
        image = [[UIImage alloc] initWithData:data];  // ❗️ 这里这里这里~❗️
    
#if SD_UIKIT || SD_WATCH
        UIImageOrientation orientation = [self sd_imageOrientationFromImageData:data];
        if (orientation != UIImageOrientationUp) {
            image = [UIImage imageWithCGImage:image.CGImage
                                        scale:image.scale
                                  orientation:orientation];
        }
#endif
    }


    return image;
}

所以我们需要在这里对图片做一次等比的压缩,在UIImage+MultiFormat这个类里面添加如下压缩方法 compressImageWith:

+(UIImage *)compressImageWith:(UIImage *)image  
{  
    float imageWidth = image.size.width;  
    float imageHeight = image.size.height;  
    float width = 640;  
    float height = image.size.height/(image.size.width/width);  
    float widthScale = imageWidth /width;  
    float heightScale = imageHeight /height;  
    UIGraphicsBeginImageContext(CGSizeMake(width, height));  
      
    if (widthScale > heightScale) {  
        [image drawInRect:CGRectMake(0, 0, imageWidth /heightScale , height)];  
    }  
    else {  
        [image drawInRect:CGRectMake(0, 0, width , imageHeight /widthScale)];  
    }  
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();  
    UIGraphicsEndImageContext();  
    return newImage;  
}
      

然后我们在👆 红色❗️地方image = [[UIImage alloc] initWithData:data];
下面调用以下, 当data大于1M的时候做压缩处理:

if (data.length/1024 > 1024) {
    image = [self compressImageWith:image];
}

我们在SDWebImageDownloaderOperation 类中``URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:

  UIImage *image = [UIImage sd_imageWithData:self.imageData];
   //将等比压缩过的image在赋在转成data赋给self.imageData
  NSData *data = UIImageJPEGRepresentation(image, 1);
  self.imageData =  [NSMutableData dataWithData:data];
  • 图片释放
    本身我们图片缓存所有的库就是SDImageCache,我们在收到内存警告或者在合适的实际是可以执行下面两个方法清理图片缓存的;
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

      [[SDImageCache sharedImageCache]clearDisk];
      [[SDImageCache sharedImageCache]clearMemory];
}

- (void)dealloc{
      [[SDImageCache sharedImageCache]clearDisk];
      [[SDImageCache sharedImageCache]clearMemory];
}

时间有限,水平一般,也没别的手艺,这次先这么着了,下次有时间学学其他童鞋的优化方案~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容