本章節(jié)列舉對內(nèi)存使用不合理的場景及優(yōu)化方案,在實(shí)際的研發(fā)中還是需要同學(xué)們嚴(yán)格遵守代碼規(guī)范,避免踩坑。
共有以下幾點(diǎn):
1、使用NSCache
2、避免內(nèi)存泄漏
3、移除當(dāng)前未使用的內(nèi)存
4、優(yōu)化圖片使用
使用緩存
使用 NSCache不僅能保證線程安全,同時在收到內(nèi)存警告時,可以釋放未使用的內(nèi)存,此外針對compressed memory,NSCache 也有優(yōu)化。
在實(shí)際的研發(fā)中,也推薦對緩存設(shè)置上限,比如YY 中關(guān)于圖片緩存設(shè)置一個最大的緩存值。對于緩存設(shè)置上限后引入的緩存清除策略,也可以參考YY 使用 LRU(least recently use)方式,更新緩存值。
避免內(nèi)存泄漏
內(nèi)存泄漏是指申請的內(nèi)存空間使用完畢之后未回收。造成內(nèi)存泄漏的原因有很多,各個場景也不盡相同,下面列舉常見一些場景。
Block 使用不當(dāng)?shù)仍蛟斐傻难h(huán)引用
- self 持有的Block 內(nèi)直接使用了self,這種屬于非常常見的循環(huán)引用了,不過這里還是不推薦大家遇到Block無腦使用weakSelf,使用 weakSelf是會造成額外的性能損耗的,像常見的使用
GCDBlock內(nèi)是不需要使用弱引用的 - 隱含的循環(huán)引用,比如superView -> self.xxxView -> self.xxxView.block -> superView。有一些場景并不會直接使用self關(guān)鍵字,但是由于addSubview等方式會持有一些子View,這些子View 又可能持有其他子View且這些子View 可能會使用Block,這時候就需要特別注意,最好理清所有對象的引用關(guān)系,避免循環(huán)引用。
子線程runloop沒有停掉,導(dǎo)致子線程一直存活。
一般來說子線程在任務(wù)結(jié)束后 會自動關(guān)閉,但是如果子線程中由于定時器功能,會打開runloop,如果不先停止runloop的話,子線程也就無法正常關(guān)閉
移除未使用的內(nèi)存
- 消息列表中已不可見的消息數(shù)據(jù)
- 進(jìn)入后臺后,當(dāng)前頁面可以移除
優(yōu)化圖片使用
圖片所占用的內(nèi)存對于大多數(shù)APP 來說都是不能忽視的,合理的使用圖片不僅需要研發(fā)盡量保證編碼規(guī)范,還需要實(shí)施一些圖片大小監(jiān)控。
1、避免將圖片放在內(nèi)存里
- 解碼后的UIImage 占用的內(nèi)存比較大,如果當(dāng)前不需要顯示時,可以不放在內(nèi)存里
- 同一個頁面使用重復(fù)資源時,避免重復(fù)創(chuàng)建(比如 解碼的YYImage)
- 批量使用圖片的場景,推薦使用
autoreleasepool等方式及時釋放內(nèi)存 - 大圖不放在Asset中且通過
imageWithContentOfFile讀取
2、圖片裁剪
大多情況下業(yè)務(wù)場景需要顯示的圖片尺寸小于圖片的原始尺寸。圖片裁剪一般有兩種選擇,一是在后端下發(fā)圖片的時候,不同場景下發(fā)不同尺寸的URL;二是圖片下載后,客戶端對原圖進(jìn)行合適的裁剪。
3、圖片繪制及縮放使用 UIGraphicsImageRenderer 和 ImageIO
常見的UIGraphicsBeginImageContextWithOptions繪圖方式會有兩個問題:
(1)默認(rèn)是 SRGB 的格式,也就是說每個像素需要占 4 個 bytes 的空間,對于一些黑白或者僅有 alpha 通道的數(shù)據(jù)來說是沒有必要的。
(2)需要將原圖片完全解碼后渲染出來,原圖片的解碼會造成內(nèi)存占用的高峰。
iOS 12之后,使用UIGraphicsImageRenderer繪圖。系統(tǒng)會自動選擇合適的顏色格式,避免不必要的內(nèi)存消耗。
對于原方法需要解碼原圖造成內(nèi)存暴漲的問題,可以考慮用ImageIO來解決。ImageIO可以直接讀取圖像大小和元數(shù)據(jù)信息,不會帶來額外的內(nèi)存開銷。
這里附上ImageIO 創(chuàng)建縮略圖的使用代碼
+ (UIImage *)getThumbImageWithMax:(CGFloat)max imageData:(NSData *)imageData {
if (imageData.length == 0) {
return nil;
}
CFDictionaryRef dicOptionsRef = (__bridge CFDictionaryRef) @{(id)kCGImageSourceCreateThumbnailFromImageAlways : @(YES),
(id)kCGImageSourceThumbnailMaxPixelSize : @(max),
(id)kCGImageSourceShouldCache : @(NO),
(id)kCGImageSourceCreateThumbnailWithTransform:@(YES)
};
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, nil);
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(src, 0, dicOptionsRef);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
if (imageRef != nil) CFRelease(imageRef);
CFRelease(src);
return newImage;
}