app 內(nèi)存優(yōu)化筆記

階段

1. Leaks 處理一 ????????????
2. Leaks 處理二 ????????????
3. 其他處理 ????????????

簡(jiǎn)介

項(xiàng)目業(yè)務(wù)隨著開發(fā)迭代趨于穩(wěn)定的時(shí)候同窘,進(jìn)入一段空白期蹬挺,但是對(duì)于我們程序猿來說閑暇不是件好事窘拯,要讓自己忙碌起來胃榕,這個(gè)時(shí)候我們就可以考慮完善和優(yōu)化我們的項(xiàng)目了番挺,既能優(yōu)化了我們的產(chǎn)品性能镜盯,又能提高了我們的專業(yè)技能。本次我這邊簡(jiǎn)單寫了個(gè)筆記揩页,記錄一下我們項(xiàng)目初步優(yōu)化過程旷偿。

Leaks 處理一(MLeaksFinder)

我初期烹俗,選擇了weread團(tuán)隊(duì)開發(fā)的MLeaksFinder,這款Lib有比較友好的提示頁面萍程,PS:不是所有的內(nèi)存她都能檢測(cè)到幢妄,目前只能自動(dòng)地檢測(cè) UIViewController 和 UIView 相關(guān)的對(duì)象~!,比較方便的一點(diǎn)地方就是茫负,這貨Debug模式下蕉鸳,遇到leaks地方直接Alert view 告訴你,Release模式自動(dòng)關(guān)閉忍法,我用該庫(kù)檢測(cè)到了部分遺留未釋放的VC和View,大部分都是NSTimer潮尝、NSNotificationCenterBlock饿序、WebViewScriptMessageHandler ....引起的循環(huán)引用勉失,;Lib git 傳送門-->

??就是個(gè)人頁 Cell持有了NSTimer原探,在VC 消失后未對(duì)timer 進(jìn)行invalidate操作乱凿;

Memory Leaks
(
    MineViewController,
    view
    tableView,
    minesettingCell
)

根據(jù)??提示我們是不是很快發(fā)現(xiàn)問題并且定位到類,找到泄露位置修復(fù)它咽弦,是不是很簡(jiǎn)單~徒蟆!

如果其他團(tuán)隊(duì)開發(fā)者選擇無視這個(gè)提示,我們?cè)诨惱锩嬖O(shè)置斷言型型,讓他一次crash個(gè)夠~ ??

- (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段审,這是比較簡(jiǎn)單便捷的方式,檢測(cè)和處理循環(huán)引問題闹蒜,我們可以借助這個(gè)Lib,但也不能完全指望它寺枉,論檢測(cè)內(nèi)存泄露,蘋果自己提供的有專業(yè)工具Instruments嫂用,我們看??~

Leaks 處理二 (Instruments)

Xcode的Instruments里面有一個(gè)Leaks工具型凳,可以幫助你定位發(fā)生內(nèi)存泄漏的代碼段,以便修復(fù)問題嘱函。通過??方式打開Instruments面板PS:也可在Xcode頁面 Command + i 快捷鍵啟動(dòng)

打開 Instruments.png

選擇Leaks工具甘畅,打開后界面如??圖:

打開 Leaks.png

選擇Target,在右下角Display Setting面板的Call Tree往弓,勾選Invert Call Tree和Hide System Libraries疏唾,方便接下來我們迅速查找有內(nèi)存問題的代碼段。

instruments-Leaks.png

上面"打鉤"選項(xiàng)默認(rèn)是不選的函似,我們通常根據(jù)自己的需求槐脏,把它們勾選上,可以幫你更快定位到關(guān)鍵的問題代碼上撇寞。

  • Separate by Category:按類別做分析顿天,這樣可以清晰的看到吃資源的問題線程的分類堂氯。
  • Separate by Thread:按線程分開做分析,這樣更容易揪出那些吃資源的問題線程牌废。特別是對(duì)于主線程咽白,它要處理和渲染所有的接口數(shù)據(jù),一旦受到阻塞鸟缕,程序必然卡頓或停止響應(yīng)晶框。
  • Invert Call Tree:反向輸出調(diào)用樹。把調(diào)用層級(jí)最深的方法顯示在最上面懂从,更容易找到最耗時(shí)的操作授段。
  • Hide System Libraries:隱藏系統(tǒng)庫(kù)文件。過濾掉各種系統(tǒng)調(diào)用番甩,只顯示自己的代碼調(diào)用侵贵。
  • Flattern Recursion:拼合遞歸。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因?yàn)檫f歸函數(shù)會(huì)調(diào)用自己)合并為一條对室。

雙擊打開提示Leaks的函數(shù)模燥,進(jìn)入到可視代碼頁面??咖祭,這里我們可以直觀的看到內(nèi)存泄露的地方掩宜,針對(duì)當(dāng)前代碼做出修改和優(yōu)化:

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]) 
    {
        // 單例 設(shè)置通知監(jiān)聽,肯定是釋放不了的浩嫌; 所以這里報(bào)了警告??
        // 改之前
        [[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;
}

其他處理

其實(shí)有很多時(shí)候檐迟,內(nèi)存泄露對(duì)象占用內(nèi)存并不是太多,比如??我們看到的幾~幾百Bytes码耐,我們發(fā)現(xiàn)他并解決掉就完了;

很多時(shí)候追迟,在我們應(yīng)用開發(fā)中圖片和視頻內(nèi)存開銷是相當(dāng)大的,如果我們沒有處理好渲染--加載--緩存--釋放問題骚腥,應(yīng)用內(nèi)存分分鐘飚到GB級(jí)別敦间,并且分分鐘就crash給你看;

因?yàn)闀r(shí)間有限和當(dāng)前發(fā)現(xiàn)問題比較大的地方就是圖片加載束铭,所以這次主要對(duì)圖片進(jìn)行了優(yōu)化廓块,因?yàn)樯婕暗搅烁咔宕髨D片加載,在處理圖片的時(shí)候契沫,有很多方式带猴,??介紹三種種方式:

后端 + 前端

條件允許的情況下(可擴(kuò)展性強(qiáng))

  • 請(qǐng)后端幫忙搭建流媒體服務(wù)器,對(duì)圖片進(jìn)行壓縮處理;
  • 前端定義不同的協(xié)議懈万,根據(jù)參數(shù)不同去取 Small / Medium / Big 不同規(guī)格的圖片;
  • 后端根據(jù)前端傳遞的不同參數(shù)拴清,對(duì)圖片進(jìn)行壓縮靶病,生成新的image url給前端;
  • 前端再對(duì)不同參數(shù)請(qǐng)求下來的圖片進(jìn)行加載(也可以再次處理口予,只要運(yùn)營(yíng)同學(xué)不嫌圖片不清楚)嫡秕;
    這樣圖片加載過程中,內(nèi)存會(huì)降下來很多~苹威!真的昆咽,親試過~
WebP image

WebP,是一種同時(shí)提供了有損壓縮與無損壓縮的圖片文件格式⊙栏Γ現(xiàn)在主流應(yīng)用界面需要大量圖片來掷酗,可以嵌入 WebP 的解碼包,能夠節(jié)省用戶流量窟哺,提升訪問速度優(yōu)勢(shì):對(duì)于 PNG 圖片泻轰,WebP 比 PNG 小了45%。??是jpg vs webp且轨, 是不是很厲害~

jpg vs webp.png

想更多的了解WebP知識(shí)的童鞋點(diǎn)擊 A new image format for the Web
libwebp下載地址 libwebp download

這里就介紹一下具體怎么用浮声,我們通過pod 'SDWebImage/WebP' 引入經(jīng)典圖片加載庫(kù)SDWebImageWebP 的支持模塊;
使用方式上跟加載普通圖片沒什么區(qū)別旋奢,當(dāng)然也需要讓SDWebImage支持WebP泳挥,設(shè)置如下Build Settings ---> Preprocessor Macros 添加 SD_WEBP = 1

Build Settings.png

PS: 這里也需要后端的童鞋們協(xié)助至朗,對(duì)圖片統(tǒng)一轉(zhuǎn)換成webp格式屉符,這時(shí)候你再去測(cè)試,圖片壓縮了很多锹引,既提高圖片下載速度矗钟、也為用戶節(jié)省了流量、同樣也減輕圖片加載時(shí)候內(nèi)存占用~一舉多得嫌变;

only 前端

如果??兩種方式都沒有人提供支持的話吨艇,自己的活自己干,誰讓你的應(yīng)用內(nèi)存占用太高呢腾啥,甚至老Crash呢~
自己加入一些機(jī)制來解決 預(yù)防 避免圖片加載內(nèi)存過大Crash問題东涡,也在合適的時(shí)機(jī)去釋放這些資源,不要常駐內(nèi)存碑宴,使用戶感覺用的越久應(yīng)用越卡软啼;

目前項(xiàng)目加載使用的是SDWebImage,相信在座的各位基本都用過,國(guó)內(nèi)外太多的App使用其進(jìn)行圖片加載延柠,很主流祸挪,但是在用SDWebImage加載多個(gè)圖片過程中,加載幾張圖片就內(nèi)存暴增嚴(yán)重導(dǎo)致崩潰贞间。

SDWebImage.png
  • 圖片壓縮 / 緩存
    因?yàn)槟壳罢麄€(gè)工程所有的圖片加載都是用的此庫(kù)贿条,我們針對(duì)該庫(kù)做一些優(yōu)化雹仿,SDWebImage有一個(gè)SDWebImageDownloaderOperation類來執(zhí)行下載操作的,我們抽絲剝繭的定位到了 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 {
      //??????發(fā)現(xiàn)這里面對(duì)圖片的處理是直接按照原大小進(jìn)行的胧辽,如果幾千是分辨率這里導(dǎo)致占用了大量?jī)?nèi)存??????
        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;
}

所以我們需要在這里對(duì)圖片做一次等比的壓縮,在UIImage+MultiFormat這個(gè)類里面添加如下壓縮方法 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;  
}
      

然后我們?cè)?? 紅色??地方image = [[UIImage alloc] initWithData:data];
下面調(diào)用以下, 當(dāng)data大于1M的時(shí)候做壓縮處理:

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

我們?cè)?code>SDWebImageDownloaderOperation 類中``URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:

  UIImage *image = [UIImage sd_imageWithData:self.imageData];
   //將等比壓縮過的image在賦在轉(zhuǎn)成data賦給self.imageData
  NSData *data = UIImageJPEGRepresentation(image, 1);
  self.imageData =  [NSMutableData dataWithData:data];
  • 圖片釋放
    本身我們圖片緩存所有的庫(kù)就是SDImageCache公黑,我們?cè)谑盏絻?nèi)存警告或者在合適的實(shí)際是可以執(zhí)行下面兩個(gè)方法清理圖片緩存的邑商;
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

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

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

時(shí)間有限,水平一般凡蚜,也沒別的手藝人断,這次先這么著了,下次有時(shí)間學(xué)學(xué)其他童鞋的優(yōu)化方案~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末朝蜘,一起剝皮案震驚了整個(gè)濱河市恶迈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谱醇,老刑警劉巖暇仲,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異副渴,居然都是意外死亡奈附,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門佳晶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桅狠,“玉大人讼载,你說我怎么就攤上這事轿秧。” “怎么了咨堤?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵菇篡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我一喘,道長(zhǎng)驱还,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任凸克,我火速辦了婚禮议蟆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萎战。我一直安慰自己咐容,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚂维。 她就那樣靜靜地躺著戳粒,像睡著了一般路狮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔚约,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天奄妨,我揣著相機(jī)與錄音,去河邊找鬼苹祟。 笑死砸抛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的树枫。 我是一名探鬼主播锰悼,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼团赏!你這毒婦竟也來了箕般?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤舔清,失蹤者是張志新(化名)和其女友劉穎丝里,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體体谒,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杯聚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抒痒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幌绍。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖故响,靈堂內(nèi)的尸體忽然破棺而出傀广,到底是詐尸還是另有隱情,我是刑警寧澤彩届,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布伪冰,位于F島的核電站,受9級(jí)特大地震影響樟蠕,放射性物質(zhì)發(fā)生泄漏贮聂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一寨辩、第九天 我趴在偏房一處隱蔽的房頂上張望吓懈。 院中可真熱鬧,春花似錦靡狞、人聲如沸耻警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榕栏。三九已至畔勤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扒磁,已是汗流浹背庆揪。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妨托,地道東北人缸榛。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像兰伤,于是被迫代替她去往敵國(guó)和親内颗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容