使用多線程遍歷或操作集合對(duì)象的時(shí)候,務(wù)必要注意內(nèi)存的及時(shí)釋放,否則一不小心就觸發(fā)內(nèi)存峰值而閃退了。
最近新版本發(fā)布后,有較多用戶反饋閃退;經(jīng)過(guò)部分走訪用戶初步得出結(jié)論是內(nèi)存閃退,而且和PHPLibrary相關(guān)修改有關(guān);
聯(lián)系用戶
聯(lián)系了部分用戶,撈取到較多日志;確定原因基本是內(nèi)存閃退,發(fā)生閃退時(shí)瀏覽器占用了1.3G左右的內(nèi)存,超過(guò)了內(nèi)存限制而被jetsam殺死;這個(gè)不太合理,我們基本不會(huì)這樣瘋狂申請(qǐng)這么大內(nèi)存而不釋放的。
最后發(fā)現(xiàn)用戶都有一個(gè)共性,相冊(cè)圖片都很多。而恰好我們?cè)谛掳姹居袀€(gè)較大的修改為了適配iOS12而全面廢棄了ALAssetLibrary,所以懷疑是這里。
分析
給用戶打包集成了QAPM后,終于找到問(wèn)題禍?zhǔn)琢?,原?lái)是PHP的localIdentifier接口導(dǎo)致的

找到PHP接口說(shuō)明,

每次調(diào)用localIdentifier會(huì)觸發(fā)一次deep copy字符串,從而分配了內(nèi)存;但是一個(gè)字符串也不可能這么容易導(dǎo)致內(nèi)存飆漲呢?
定位
原因是我們?cè)跀?shù)組遍歷中,頻繁觸發(fā)了localIdentifier接口的調(diào)用,根據(jù)ARC說(shuō)明,不是new,alloc,等創(chuàng)建的對(duì)象,不是屬于我們管理的,因此該接口返回的是一個(gè)autorelease的對(duì)象,其釋放是由autoreleasepool統(tǒng)一釋放的。
而我們的代碼是如下調(diào)用的:
[albums enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(PHAsset* _Nonnull result, NSUInteger idx, BOOL * _Nonnull stop) {
if (result) {
@synchronized (self.lockFileList) {
NSArray *arr=[_fileList copy];
for (int i = 0; i < arr.count; i++) {
MttPictureFile *file = arr[i];
if ([file.assetURL.absoluteString isEqual:result.localIdentifier]) {
*stop = YES;
return;
}
}
}
for (int i = 0; i < self.videoFiles.count; i++) {
MttAlbumVideoFile *file = self.videoFiles[i];
if ([file.assetURL.absoluteString isEqual:result.localIdentifier]) {
*stop = YES;
return;
}
}
[self addPictureFileWithAsset:result];
}
///...其余代碼
}];
存在2個(gè)問(wèn)題,
1.數(shù)組元素copy沒(méi)有及時(shí)釋放,這個(gè)還好,不會(huì)影響太大,但是遍歷的時(shí)候訪問(wèn)了localIdentifier,這個(gè)會(huì)導(dǎo)致極端的情況下,觸發(fā)上萬(wàn)個(gè)localIdentifier創(chuàng)建
2.另一個(gè)更嚴(yán)重的問(wèn)題,經(jīng)過(guò)反匯編調(diào)試,發(fā)現(xiàn)
[PHPFetchResult enumerateObjectsWithOptions:usingBlock:] 這里踩坑了,這里不帶autoreleasepool而導(dǎo)致內(nèi)存一直增加直到PHP遍歷執(zhí)行完畢后的autoreleasepool統(tǒng)一回調(diào)了才統(tǒng)一釋放。
以上2點(diǎn)會(huì)導(dǎo)致,當(dāng)用戶有1萬(wàn)張圖時(shí),假設(shè)_fileList為5000,即用戶一次性新增了5千張圖,而原先有5000張圖,那么這次回到就會(huì)觸發(fā)5000 * 5000 * 2次localIdentifier的創(chuàng)建,這些遍歷均會(huì)等到GCD block執(zhí)行完畢才統(tǒng)一釋放;這里就輕松的能占用幾百M(fèi)B到上千MB內(nèi)存了
結(jié)論
使用數(shù)組遍歷時(shí),切記多添加autoreleasepool
[NSArray enumerateObjectsWithOptions:usingBlock:] 防止踩坑系統(tǒng)的各種array對(duì)于非自己alloc,new的autorelease對(duì)象,要及時(shí)@autoreleasepool包裹讓其盡早釋放;