轉發自
http://blog.csdn.net/skylin19840101/article/details/51500900
謝謝大佬
質量問題不僅僅是商品應該注重的,在移動互聯占據人們各個生活領域的前提下,產品質量更顯重要,以最具人氣和潛力的iOS為例,iOS從系統研發和客戶端軟件開發環節對質量的要求異常高,在注重用戶體驗的同時提升產品質量,這也是很多用戶非iOS不用的原因,iOS系統已經讓移動互聯網的品質得到升級。那么我們在開發iOS產品時,如何提高它的質量呢?
涉及到質量問題,這就是一個很大的話題,包括很多方面,比如代碼書寫的質量,開發流程的規范,項目管理的到位,測試的最后把關等各個環節。編碼需要規范,命名需要有意義;接口低耦合、高內聚、易擴展,代碼能重用、避免重復代碼;提交代碼后需要做CodeReview;Release前,自測需要充分,包括單元測試、和其他模塊(服務器)的聯調測試,網絡性能測試,不同機型、不同系統版本、越獄與否等各個方面的測試;通過冒煙測試后才交于QA測試等等。在這里,我們主要分享從開發人員的角度如何提高產品質量,包含的內容如下:
<a name="t0" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>避免異常:
從上面可以看到,iOS開發中常見的異常包括以下幾種:
NSInvalidArgumentException
NSRangeException
NSGenericException
NSInternalInconsistencyException
NSFileHandleOperationException
<a name="t1" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSInvalidArgumentException
非法參數異常(NSInvalidArgumentException)是 Objective - C 代碼最常出現的錯誤,所以平時在寫代碼的時候,需要多加注意,加強對參數的檢查,避免傳入非法參數導致異常,其中尤以nil參數為甚。
<a name="t2" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>集合數據的參數傳遞
比如NSMutableArray, NSMutableDictionary的數據操作
(1) 不能刪除nil的key(2) NSDictionary不能添加nil的對象
(3) 不能插入nil的對象
[圖片上傳失敗...(image-ca0fe2-1515303094173)]
(4) 其他一些nil參數
<a name="t3" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>其他一些API的使用
<a name="t4" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
(1) NSDictionary不能刪除nil的key
<a name="t5" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
APP一般都會有網絡操作,免不了使用網絡相關接口,比如NSURL的初始化,不能傳入nil的http地址:
<a name="t6" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>未實現的方法
(1) .h文件里修改了函數名,卻忘了修改.m文件里對應的函數名;或者頭文件里定義了函數,.m文件里沒有實現
(2) 使用第三方庫時,沒有添加”-ObjC” flag
(3) MRC時,大部分情況下是因為對象被提前release了,在你心里不希望他release的情況下,指針還在,對象已經不在了。 比如:
<a name="t7" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
(4) 對象類型使用不當
[圖片上傳失敗...(image-af0fee-1515303094173)]
因為imagenS本來是NSDictionary的對象,被當做NSString來處理了
類似的,NSDictionary的對象被當做NSArrary來處理了
<a name="t8" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a> NSRangeException
越界異常(NSRangeException)也是比較常出現的異常,有如下幾種類型:
<a name="t9" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
<a name="t10" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>數組最大下標處理錯誤
比如數組長度count, index的下標范圍[0, count -1], 在開發時,可能index的最大值超過數組的范圍;
<a name="t11" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
<a name="t12" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>下標的值是其他變量賦值
這樣會有很大的不確定性, 可能是一個很大的整數值
這里的值達到32位和64位整數的最大值,肯定是一個不正常的參數,比如:
如果找不到str ,則返回NSNotFound,32位下它就是2147483647,64位下18446744073709551615 ,將它作為參數傳遞則會導致NSRangeException。
<a name="t13" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
<a name="t14" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>使用空數組
如果一個數組剛剛初始化,還是空的,就對它進行相關操作
[圖片上傳失敗...(image-4a5ea9-1515303094176)]
所以,為了避免NSRangeException的發生,必須對傳入的index參數進行合法性檢查,是否在集合數據的個數范圍內。
<a name="t15" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>
NSGenericException
NSGenericException這個異常最容易出現在foreach操作中,在for in循環中如果修改所遍歷的數組,無論你是add或remove,都會出錯,比如:
執行上面的代碼會出現以下的錯誤:
原因就在這 "for in",它的內部遍歷使用了類似 Iterator進行迭代遍歷,一旦元素變動,之前的元素全部被失效,所以在foreach的循環當中,最好不要去進行元素的修改動作,若需要修改,循環改為for遍歷,由于內部機制不同,不會產生修改后結果失效的問題。
[圖片上傳失敗...(image-c8a4bb-1515303094173)]
<a name="t16" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSMallocException
這也是內存不足的問題,無法分配足夠的內存空間
<a name="t17" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>NSFileHandleOperationException
處理文件時的一些異常,最常見的還是存儲空間不足的問題,比如應用頻繁的保存文檔,緩存資料或者處理比較大的數據:[圖片上傳失敗...(image-ec2fa3-1515303094176)]
所以在文件處理里,需要考慮到手機存儲空間的問題。
<a name="t18" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>二、錯誤處理
在進行函數調用的時候,如果有NSError的參數傳遞,最好都處理以下,特別是一些網絡和文件的處理,比如:
文件操作錯誤
比如在刪除文件時,有可能發生錯誤,我們需要做一些處理
[圖片上傳失敗...(image-60990c-1515303094172)]
網絡處理錯誤
在使用NSURLConnection進行網絡請求時,發生錯誤是難免的,必須做相應處理
<a name="t19" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>三、本地緩存的兼容處理
在APP的運行過程中,難免會有I/O的操作,一般的情況,從技術的角度來看也不會出什么問題,但是業務邏輯上可能就需要特別注意了,比如APP升級版本后,可能保存緩存數據的格式發生變化了,讀取數據后的處理邏輯也就相應發生變化,這時就需要考慮兼容舊版本數據的處理,否則對升級用戶了說,很可能由于異常發生崩潰。
<a name="t20" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>四、解析網絡數據
客戶端開發時,對接收到的服務器數據,需要特別小心,因為經過網絡傳輸回來的數據都是不可信的,什么問題都可能發生,比如協議中該有的字段沒有,是整數型的字段結果傳了一個子串,Json格式的內容變成xml了…,所以我們在解析服務器數據時,需要有充分的異常處理機制。
<a name="t21" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>五、代碼靜態檢查
在開發完成后,需要對代碼進行靜態檢查,這樣能發現一些內存泄露以及一些warning。
<a name="t22" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>內存泄露
<a name="t23" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a> MRC
在MRC的時候,內存泄露是個大問題,一不小心就會中招
[圖片上傳失敗...(image-699c34-1515303094175)]
注意上面的代碼并不是L63行存在泄漏,我們點擊“Potential leak of an object”前面的箭頭,指示會出現一些變化,如下圖。alloc一個對象的時候,其內存計數內存計數(retain count)+1
因為content的setter方發會將object的內存計數+1,如下代碼,content是retain屬性。執行完L62代碼后,self.content的內存計數就為 2
建議修改方案:
<a name="t24" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>ARC
在ARC下,這方面的問題就少很多了,但也還是會出現的,比如:
(1)使用CF CG有關的函數,內存的釋放還是需要手動調用的,不調用則會造成內存泄漏
CFUUIDRef和CFStringRef都需要手動釋放
CGImageRef類似也需要手動釋放
(2) Runtime方法中的class_copyIvarList
,class_copyMethodList
這些方法返回的對象,也需要手動釋放(free)
(3) 如果iOS中使用C/C++編程
比如使用Openssl的RSA接口,用到一些內存的分配操作,使用結束需要釋放
[圖片上傳失敗...(image-ff2626-1515303094173)]
在iOS中,怎么避免內存泄漏的產生呢?除了開發經驗的積累,我們也可以通過兩個方法來發現內存泄漏,以便及時修復,一個就是上面講到的靜態分析(Analyze),比較簡單;對于靜態沒有檢測出來的內存泄露問題,需要使用動態的方法,下一節來分享。
<a name="t25" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>無效數據監測(Dead store)
Unused、Never read....這個比較簡單,修改即可。
<a name="t26" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>邏輯錯誤監測(Logic error)
[圖片上傳失敗...(image-5b1cb5-1515303094174)]
Tag不等于1、2和3的時候,就會出現很問題了。len is a garbage value。建議在聲明變量時,同時進行初始化。
<a name="t27" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>其他一些warning
對完美主義者來說,warning在Xcode里始終都比較礙眼,直接消除。
<a name="t28" style="box-sizing: border-box; background-color: transparent; color: rgb(79, 161, 219); text-decoration: none; margin: 0px; padding: 0px; font-weight: 400; outline: 0px; background-position: initial initial; background-repeat: initial initial;"></a>Instruments分析
Analyze分析內存泄露不能把所有的內存泄露查出來,有的內存泄露是在運行時,用戶操作時才產生的。在 C、C++混編時,對于C++的內存分配,也是需要手動釋放,比如iOS中使用Openssl來進行RSA的加解密操作,其中就有很多的內存操作,如果不進行手動釋放,Analyze是檢查不出來的,這個時候就需要用到Instruments了。
初始化
釋放的代碼注釋掉
[圖片上傳失敗...(image-c34e8f-1515303094170)]
這個時候使用Analyze分析,一點內存泄漏也沒有,我們使用Instruments來分析
“Product” -> “Profile”:
按上面操作,build成功后跳出Instruments工具,選擇Leaks工具,這時候Instruments工具就運行起來了,顯示效果如下:
點擊上面的”紅色的圓圈”,才會啟動對應的APP,這時”紅色的圓圈”變成”黑色的正方形”:
這時用戶就可以在APP上任意操作,查看內存使用情況,選擇”Leak Checks”項,右邊的圖中,如果是”綠色的勾”表示內存使用正常,”紅色的叉”則表示有內存泄漏,這時點擊”黑色的正方形”,讓APP暫停下來。
代表malloc后沒有使用free釋放OutBuf的內存。
修改如下: