階段
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
潮尝、NSNotificationCenter
、Block
饿序、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)
選擇Leaks工具甘畅,打開后界面如??圖:
選擇Target,在右下角Display Setting面板的Call Tree往弓,勾選Invert Call Tree和Hide System Libraries疏唾,方便接下來我們迅速查找有內(nèi)存問題的代碼段。
上面
"打鉤"
選項(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)化:
既然找到了問題所在,我們修改著也比較容易了么翰,畢竟代碼都是人寫的牺汤,看得懂就好改了??
+(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且轨, 是不是很厲害~
想更多的了解WebP知識(shí)的童鞋點(diǎn)擊 A new image format for the Web
libwebp下載地址
libwebp download
這里就介紹一下具體怎么用浮声,我們通過pod 'SDWebImage/WebP'
引入經(jīng)典圖片加載庫(kù)SDWebImage
的WebP
的支持模塊;
使用方式上跟加載普通圖片沒什么區(qū)別旋奢,當(dāng)然也需要讓SDWebImage支持WebP泳挥,設(shè)置如下Build Settings ---> Preprocessor Macros
添加 SD_WEBP = 1
;
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)致崩潰贞间。
- 圖片壓縮 / 緩存
因?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)化方案~