番外特別篇之 為什麼我不建議你直接使用UIImage傳值?--從一個詭異的相冊九圖連讀崩潰bug談起

来源:http://www.cnblogs.com/ios122/archive/2016/09/22/5895853.html
-Advertisement-
Play Games

所謂"番外特別篇",就是系列文章更新期間內,隨機插入的一篇文章.目前我正在更新的系列文章是 [實現iOS圖片等資源文件的熱更新化](https://github.com/ios122/ios_assets_hot_update).但是,這兩天,被一個自己App中詭異的相冊讀取的Bug困擾,暫時延緩了... ...


關於"番外特別篇"

所謂"番外特別篇",就是系列文章更新期間內,隨機插入的一篇文章.目前我正在更新的系列文章是 實現iOS圖片等資源文件的熱更新化.但是,這兩天,被一個自己App中詭異的相冊讀取的Bug困擾,暫時延緩了文章的更新進度.這個BUG,詭異而又有趣,既然花了10個小時才理清,不妨再投入1個小時,曬出來供大家鑒賞,品玩.

Bug 的詳細描述

詭異的畫風

bug_img

此Bug僅在操作多張高像素圖片時才會觸發,所謂高像素就是圖片本身並不算大,但是圖片寬高非常大的圖片.這次觸發這個問題的是一組 5701 * 3171 的圖片.畫風大家可以點擊鏈接查看原圖自行感受下 --https://github.com/ios122/why_not_uiimage/blob/master/bug_img.jpg?raw=true

當BOSS剛好是一個攝影愛好者

在大多數情況下,是很少有用戶觸發這個問題的,但是BOSS是一個攝影愛好者,手機里有許多高像素圖,一天他想往自己公司的App上傳分享幾張圖片時,他竟然沒法把一次性地從相冊選取九張圖,每次選中後,點擊"確定",都會理解Crash.是的,就是那九張圖,其他圖片是沒問題的,8張圖,也是OK的,他還強調了下是用的最新版本的App.

關於 BUG 的預處理

首先,我的第一反應是肯定是他的手機太燙了吧,重啟下,就好了.恩,肯定是這樣.發佈作品的邏輯,好幾個版本都沒動過.模擬器,手機,我自己試了下,都是OK的.也沒有其他用戶反饋過,fabric也看不到任何log.對,手機太燙了.我稍後,再聯繫他,肯定就OK了.

稍後,再直接聯繫BOSS,竟然還是會Crash,他甚至給我錄屏演示了一下,真的每次都會crash.而且我還無法復現.而且BOSS手機iPhone6 plus,自身記憶體不足的原因非常非常小.

形勢,瞬間變得很緊張,這個問題的優先順序瞬間被提到了最高!再次嘗試了各種可能的情況.圖片大小?它是9張1.5M的圖,我就用9張3M的圖,也是OK的呀!選取時,順序有問題?我試著按照錄屏中演示的順序去選取圖片,也是OK的.一股深深地無力感!竟然連復現都無法復現不了!

最後的最後,說是會拿手機給我測試.不過,最後BOSS的手機,還是沒有拿到,只是拿到了開篇那張畫風詭異的圖片.沒錯,就是它,連續選取9張,就Crash了.

至少,我現在能復現問題了.下麵的,需要的就只是時間,耐心還有大開的腦洞了.

Bug 分析思路的簡要描述

我不覺得,分析Bug真的有什麼思路可言.Bug產生的原因,是有許多可能性的,可能行驗證的順序,方式和深度很大程度上取決於coder本身已有的經驗,天賦,甚至還有些許的運氣!我能描述的,可能僅僅是我處理這個問題的一個相對的完整腦洞過程.部分分析過程間,明顯不是有邏輯性的.越是詭異的問題,越是不能循規蹈矩,要時刻嘗試去問自己最可能地問題是什麼,而不是沿著一條路,一條道走到黑.

1.排除通用邏輯問題

Coder有些許高傲,有時候是有利於自己更冷靜地處理問題的.稍微不自信點的童鞋,可能就會懷疑:我代碼是不是有什麼特殊的臨界判斷沒有加?不行,我得去看看.一行一行,看代碼,從天黑到天亮,從期待到絕望...其實,稍微有一些對比實驗常識的人,都很容易猜到: 兩種情況,唯一的變數是 圖片素材本身,那 最可能 的原因肯定是 圖片本身的問題.一種高大上的說法,這某種程度上,也暗合了所謂的"貪心演算法".每次,都只從最可能的原因入手,管他誰是誰,我的代碼就算有問題,那觸發這個問題的可能性,也是遠小於 圖片素材本身的.---多麼朴素的真理呀!

2.確定是相冊選取圖片記憶體過高

這個問題,在真機上,並不好確定,因為連續讀取9張高像素圖時,記憶體是瞬間飆升的,你幾乎沒有機會去觀察記憶體占用,給人一種因為某種邏輯判斷而導致的Crash的錯覺.如果換做模擬器,會很容易看到,這個記憶體占用,是飆升到G單位的.當然,我也沒那麼睿智,我是單個N個斷點,最終確認了Crash的代碼的準確位置.一個for迴圈,每次step 1,這下很明顯地看到記憶體,幾乎是 100M/張的速度在飆升,而圖片本身的大小隻有 1.5M/張.此處我想說的是,打斷點也是有技巧的,最後沒有辦法的辦法也是講究辦法的.可是試著註釋掉可能的引起的代碼,然後逐步放開註釋,這要觀察,會比直接打斷點快些.--意會!

3.確定是PHImageManager 的問題requestImageForAsset:方法引起的高記憶體占用

當你通過註釋法,配合斷點,很容易就可以引起記憶體高占用的代碼.此處,我的App中,是讀取相冊原圖,用的是 PHImageManagerrequestImageForAsset:targetSize:contentMode:options: resultHandler: 方法.此處接下來的解決思路,有大坑呀!你可能會想,是UIImage載入的問題吧?那就研究下UIImage渲染機制吧.然後1天過去了,等你學成歸來,驀然發現 PHImageManager 是一個系統方法,它載入的圖片機制,你無力干涉!我可能運氣比較好些吧,研究UIImage的渲染機制,想想都頭疼,抱著試一試的態度,我google了下: PHImageManager requestImageForAsset memory high,然後第一條鏈接的第二個回答就是我要到答案: http://stackoverflow.com/questions/33274791/high-memory-usage-looping-through-phassets-and-calling-requestimageforasset

是的,我運氣,似乎總是很好~

4.使用requestImageDataForAsset:替換的問題requestImageForAsset:

答案原文是:

I found that if i switch from

- requestImageForAsset:targetSize:contentMode:options:resultHandler:

to

- requestImageDataForAsset:options:resultHandler:

i will receive the image with the same dimension {5376, 2688} but the size in byte is much smaller. So the memory issue is solved.

hope this help !!

(note : [UIImage imageWithData:imageData] use this to convert NSData to UIImage)

簡單說,就是用 - requestImageDataForAsset:options:resultHandler: 替換 requestImageForAsset:targetSize:contentMode:options:resultHandler: 就可以了,前者是直接返回二進位數據,不渲染.

但是,這裡有一個可能不是問題的問題, 這個方法調用是位於一個名為第三方庫 TZImagePickerController 內,我方便直接改嗎? 我是直接給改了.此處,將來必成大患,以後再用到,肯定還會有相同問題,還不如直接把原來的實現直接替換掉.當然,這也是成本最小的方法.這個庫,本身,已經在App內,深度定製和重寫了,如果一些成熟的第三方庫,這麼做,最好先備份或備註下.

5.使用imageWithData:相容原來的調用

為了和原來的Api介面調用相容,用imageWithData:將NSData轉換為 UIImage 傳出,同時擴展方法,使支持同時傳出 UIImage和原始的 NSData對象.傳出NSData對象的原因是,是因為高像素圖片,會引起一些列的問題,故事到此遠遠沒有結束,詳見衍生問題部分.

6.變更前後的代碼對比

還是來段代碼感受下吧,一碼剩千言:

/*原來的代碼*/
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && result) {
                result = [self fixOrientation:result];
                if (completion) completion(result,info);
            }
        }];
/*優化後代碼*/
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            UIImage * result = [UIImage imageWithData:imageData];

            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && result) {
                result = [self fixOrientation:result];
                if (completion) completion(result,info,imageData);
            }
        }];

此類Bug的可能的通用解決思路

首先,我要說明下,我解決的思路和方式,很大程度上依賴也受限於我已有的經驗,此處的解法,可能不是最優解,最多只能算是個通用解.說不定,將來等我再研究下渲染機制一類的技術,會有一個新的更簡單的方法.歡迎大神補充!

未來遇到UIImage記憶體問題的童鞋,至少能從此處獲取的一個至少驗證可用的解決策略.

回到問題本身,用一句概括就是:永遠不要直接傳遞UIImage對象.在需要傳遞UIImage的場景中,請使用圖片名或者NSData二進位對代替.

衍生問題應用與解決

故事,真的還沒有完結.從相冊順利讀取這張詭異的高像素圖後,我發現我沒有辦法將它上傳,也無法在輪播圖上,連續顯示.簡要概括如下.

無法直接以UIImage格式,連續把九張圖保存到緩存目錄

圖片選取後,並不是立即上傳的,為了能實現"重發"功能,需要在緩存目錄保留副本.原來是將 UIImage 轉換為 NSData寫入.在此過程中,又一次引起了巨額的記憶體開銷.解決方法,就是直接緩存原始獲取的 NSData 的對象,而不要 NSData --> UIImage --> NSData.

無法直接以UIImage格式,連續在輪播圖上顯示九張圖

此處對應的是一個本地大圖預覽功能,實現是在前一個頁面把九張本地圖的UIImage傳遞給輪播預覽組件.此處的坑是: 把一個存放在 數組中的UIImage對象傳遞給 UIImageView的 image屬性,當UIImageView載入到父視圖時,會引起巨額的記憶體占用.原因初步猜測是 UIImage 對象顯示到 UIImageView 會有一個特殊的耗費記憶體的操作,如果原始的 UIImage 對象一直存在,這一塊記憶體那就無法釋放.這一步,困擾了我很久很久,好幾個小時!我真沒想到,一個UIImage對象,竟然會二次引起高記憶體占用.最終的解決方法,就是在前一個頁面傳遞 NSData數組,在賦值處,再使用imageWithData:轉換為 UIImage.這樣,記憶體使用基本沒什麼起伏.

或許,我應該研究下 一個UIImage對象,竟然會二次引起高記憶體占用 的原因.歡迎大神完善!

參考鏈接



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著Android應用開發規模的擴大,客戶端業務邏輯也越來越複雜,已然不是簡單的數據展示了。如同後端開發遇到瓶頸時採用的組件拆分思想,客戶端也需要進行架構設計,拆分視圖和數據,解除模塊之間的耦合,提高模塊內部的聚合度。 開始之前先上一張內部分享時用的PPT圖: 以上是筆者在客戶端開發過程中面臨的問題 ...
  • 1、SpannableString、SpannableStringBuilder與String的關係 首先SpannableString、SpannableStringBuilder基本上與String差不多,也是用來存儲字元串,但它們倆的特殊就在於有一個SetSpan()函數,能給這些存儲的Str... ...
  • Android Weekly Issue #223 中文筆記, 本期內容包括: Offline時間戳處理; Accessibility的安全問題可能並不是個問題; 如何在單元測試和UI測試之間共用代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava,... ...
  • 前言: 單挑Android項目,最近即時通訊用到環信,集成sdk的時候 官方有一句 即:混淆規則。 自己沒寫過關於混淆打包的文章,在此補上。 下麵瞭解Android studio環境下 項目混淆打包的操作。 一、打包: 即 將Android項目生成.apk文件,讓用戶去安裝。 1、工具欄 Build ...
  • 官方地址: http://global.18wifibank.com/ github: https://github.com/yibawifi/wifisdk ...
  • 上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html 這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。 核心代理方法有如下兩個: 第一個代理方法,要求在視圖 ...
  • Fragment是activity的界面中的一部分或一種行為。可以把多個Fragment組合到一個activity中來創建一個多界面並且可以在多個activity中重用一個Fragment。可以把Fragment任務模塊化的一段activity,它具有自己的生命周期,接收它自己的事件,並可以在act ...
  • runtime簡介 RunTime簡稱運行時。OC就是 ,也就是在運行時候的一些機制,其中最主要的是消息機制。 對於C語言, 。 對於OC的函數,屬於 ,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。 事實證明: 在編譯階段,OC可以 ,即使這個 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...