為什么要減少內(nèi)存
更好的用戶體驗橘蜜,增加app流暢性剩蟀,更快的啟動速度并思,不會因為內(nèi)存過大而Crash兴喂,即便進入后臺也會活得更久
查看內(nèi)存占用高的對象或內(nèi)存飆升的操作針對性進行優(yōu)化
借助工具如圖
Persistent:該對象存在于內(nèi)存中的個數(shù)
Transient:存在過已經(jīng)被回收的對象的個數(shù)
Persistent Byte:該類對象在內(nèi)存中占得總內(nèi)存
也可以進行搜索 也可對單類對象進行分析 具體使用可搜索詳細使用教程
海量json數(shù)據(jù)處理
先說網(wǎng)絡請求执桌,多數(shù)像我一樣的野生程序員都會使用有名的第三方庫,比如AFNetworking奸笤、Alamofire, (因為我們寫的不夠好痕檬,我們多數(shù)時間都在造輪子、模仿輪子歹苦、實現(xiàn)功能青伤,可能內(nèi)存優(yōu)化的很多事情都跟我們沒有關(guān)系,因為我們根本用不到殴瘦。)通常我們會對第三方庫進行一層包裝狠角、對我們應用程序來說可以隨時替換、減少與第三庫的耦合度蚪腋。 然后開始發(fā)起請求->拿到數(shù)據(jù)json解析->數(shù)據(jù)發(fā)送到M層丰歌、M層進行model轉(zhuǎn)換->數(shù)據(jù)發(fā)送到C、C通知V數(shù)據(jù)更新屉凯,仿佛一連串的事情沒問題立帖,像我一樣。
/**
POST請求
只是一個簡簡單單的封裝悠砚、實現(xiàn)里將AF的結(jié)果通過block回調(diào)
@param control viewController 對象
@param parameters 請求參數(shù)
@param completeBlock 成功
@return 請求hash值
*/
+ (NSNumber *)PostWithControl:(NSObject *)control parameters:(NSDictionary *)parameters completeHandler:(IZQResponseBlock)completeBlock;
[IZQAPIClient PostWithControl:self parameters:[IZQInterfaceList updateTaskContentOfModel:_md] completeHandler:^(NSNumber *requestID, id data, BOOL success) {
// do something
// 數(shù)據(jù)賦值
// 刷新UI
}];
除網(wǎng)絡請求外一切的操作都在主線程晓勇。當遇上海量數(shù)據(jù),內(nèi)存飆升灌旧,(5W條60key值)json解析內(nèi)存增加150M绑咱,在讀取json中的key 拿到list 數(shù)據(jù)在次飆升、數(shù)據(jù)傳遞枢泰、遍歷描融、轉(zhuǎn)model數(shù)據(jù)會一份一份的復制,在主線程的runloop中它沒有時間釋放衡蚂、可以飚到300M 500M
解決:
多線程處理 數(shù)據(jù)處理放在子線程中去操作
1窿克、可以利用多核CPU骏庸、數(shù)據(jù)處理速度增加
2、數(shù)據(jù)處理完成只發(fā)出有效數(shù)據(jù)让歼、臨時變量敞恋、臨時指針都會被及時釋放。
3谋右、對于更多的數(shù)據(jù)可與服務器配合分段更新硬猫、分段處理
自動釋放池
關(guān)于自動釋放池看這篇文章AutoreleasePool
圖片內(nèi)存優(yōu)化
1、加載圖片資源方式
不常用的圖片 一次性渲染顯示的圖片 contentsOfFile 直接讀取渲染 不常駐內(nèi)存
對于常用的圖片 比如cell 中的小圖標 named
加載圖片 不會常駐內(nèi)存
let image = UIImage(contentsOfFile: url.path)
加載圖片到內(nèi)存 會常駐內(nèi)存 named 可以增加渲染速度 避免影響卡頓
let image = UIImage(named: name)!
2改执、圖片尺寸大小
資源圖片應該有他正確的大小啸蜜、避免小View加載大圖片,比直接加載大圖還要吃內(nèi)存辈挂。
網(wǎng)絡圖片要服務器配合返回適中的圖片衬横,雖然App可以將圖片尺寸大小進行壓縮,還是要盡量減少圖片轉(zhuǎn)換帶來的時間消耗终蒂、內(nèi)存消耗
3蜂林、適當時機清理緩存
比如SDWebImage 在app進入后臺、收到內(nèi)存警告等或需要釋放圖片的時機 clearMemory拇泣,清除圖片在內(nèi)存中的占用噪叙。設置大圖片不常駐緩存,比如特大原圖霉翔,要及時釋放內(nèi)存睁蕾。
4、圖片壓縮
這也使用最多的一種方式 例如在圖片上傳前重繪壓縮在上傳 主要是去掉一些不必要的參數(shù) 改變圖片尺寸大小
- (UIImage*)scaleToSize:(UIImage*)img size:(CGSize)size
{
// 創(chuàng)建一個bitmap的context
// 并把它設置成為當前正在使用的context
UIGraphicsBeginImageContext(size);
// 繪制改變大小的圖片
[img drawInRect:CGRectMake(0, 0, size.width, size.height)];
// 從當前context中創(chuàng)建一個改變大小后的圖片
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
// 使當前的context出堆棧
UIGraphicsEndImageContext();
// 返回新的改變大小后的圖片
return scaledImage;
}
iOS10新增UIGraphicsImageRenderer來代替UIGraphicsBeginImageContext
UIGraphicsImageRenderer官方文檔的解釋:一個支持創(chuàng)建核心圖像的渲染器债朵。
//繪制UIImage
- (UIImage*)resiImage:(NSURL*)url size:(CGSize)size{
UIImage *testIm = [UIImage imageWithContentsOfFile:url.path];
UIGraphicsImageRenderer *re = [[UIGraphicsImageRenderer alloc]initWithSize:size];
return [re imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
[testIm drawInRect:CGRectMake(0, 0, size.width, size.height)];
}];
}
5子眶、使用ImageIO加載大圖
蘋果給出的新方法
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage
{
let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
// 其他場景可以用createwithdata (data并未decode,所占內(nèi)存沒那么大),
let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
let maxDimension = max(pointSize.width, pointSize.height) * scale
let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
kCGImageSourceShouldCacheImmediately : true ,
kCGImageSourceCreateThumbnailWithTransform : true,
kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
return UIImage(cgImage: downsampleImage)
}
內(nèi)存泄漏
內(nèi)存管理 誰創(chuàng)建誰釋放 誰添加誰釋放 現(xiàn)在都使用ARC 引用計數(shù)由系統(tǒng)自動管理
內(nèi)存泄漏幾個主要原因
1、你中有我 我中有你 循環(huán)引用 使用[weak self]解決
2序芦、Timer未釋放 未設置失效時間 未置為nil timer中循環(huán)引用
3臭杰、常駐對象中強引用某對象 delegate強持有
4、C語言創(chuàng)建的對象未手動釋放
5谚中、網(wǎng)絡請求對self的強持有(延遲釋放)
6硅卢、子線程死鎖 資源不釋放
排查方法
使用Instrument工具 Leaks
MLeaksFinder 具體使用看介紹
UI
1、懶加載藏杖,延遲創(chuàng)建對象,需要的時候才創(chuàng)建節(jié)省內(nèi)存開銷
2脉顿、UI復用蝌麸,避免浪費節(jié)約開銷
3、Cell中不要臨時創(chuàng)建對象艾疟、更不要在循環(huán)中創(chuàng)建臨時對象
4来吩、對于經(jīng)常訪問的大頁面可以強引用 或 單例模式 避免重復的創(chuàng)建釋放創(chuàng)建釋放
5敢辩、圓角可以圖片重繪、CALayer重繪
6弟疆、隱藏顯示戚长、添加移除看需求選擇使用
7、宗旨:需要的時候創(chuàng)建 能重用的盡量重用 不需要的不創(chuàng)建
CoreData
CoreData中批零插入數(shù)據(jù)怠苔,在key值較多情況下 插入一條保存一條會比較慢同廉,等插入完成之后再保存會造成大量對象一直未釋放,內(nèi)存增加 我的數(shù)據(jù)有5萬條 每條60+key 每100條一保存會慢幾秒 內(nèi)存較低 每1000-5000保存一次內(nèi)存增加不多柑司,時間與完全插入在保存相當迫肖,上萬條插入在保存內(nèi)存會驟增,速度相對較快 海量數(shù)據(jù)應該在合適的時機選擇保存一次 避免達到內(nèi)存峰值 也可使系統(tǒng)更流暢
func insertPersons(persons: [Any]) {
let semaphore = DispatchSemaphore(value: 0)
persistentContainer.performBackgroundTask { [weak self](context) in
for item in 0..<persons.count {
// print(item)
let person: IZQPerson = NSEntityDescription.insertNewObject(forEntityName: "IZQPerson", into: context) as! IZQPerson
person.mapping(map: persons[item] as! [String : Any])
if item%5000 == 0 && item > 0 {
self?.saveContext(context: context)
}
}
self?.saveContext(context: context)
print(NSPersistentContainer.defaultDirectoryURL())
print("插入完成", Date.init().timeIntervalSince1970)
semaphore.signal()
}
semaphore.wait()
}