Cocoa開發優化之道

jopen 9年前發布 | 14K 次閱讀 Cocoa iOS開發 移動開發

對于寫過C/C++ for Unix的童鞋,應該都會知道有“高危函數”的說法, 比如gets、strcpy、strcat……都是典型的高危函數。“高危函數”顧名思義,是存在重大隱患的函數,它并不是不可用或者性能不佳, 而是在某些條件會導致嚴重甚至致命的錯誤。高危函數往往先是常用函數,對于它們的使用變得需要足夠地小心,否則可能帶來災難性的后果。對于“高危函 數”的處理,大多時候選擇使用更為安全的函數去替代它們,也可以自己重新定義實現這些函數。

那么,Ojective-C for Cocoa編程中是否也會存在類似的”高危函數”?盡管作為移動應用,相比較后臺服務,其后果的影響可能不可以直接拿來比較。然而借鑒Unix編程的這個 概念,了解一些帶有隱患的接口,規范使用,不失為提高移動應用編程質量的好方法。考慮Ojective-C習慣用語,我們可以將它們稱為“埋坑方法”,以 此提醒自己不要重復踩坑。

 

1、NSArray類

</div>

- (id)objectAtIndex:(NSUInteger)index;

</div>

Parameters

index    

An index within the bounds of the array.

Return Value

The object located at index.

objectAtIndex:是一個非常常用的NSArray容器方法,當index大于等于count的時候,方法會crash,對,會崩潰,原因是越界。因為NSArray使用實在非常普遍,objectAtIndex:成了應用crash的一個主因。

代碼:

Cocoa開發優化之道

Log:

Cocoa開發優化之道

<
</div>

解決辦法:一是規范用法,使用前,先做好index越界判斷和過濾;二是通過swizzle(《Objective-C與Runtime》)或者繼承等方式對objectAtIndex:插入index是否越界判斷和過濾。方案二直接適用于應用全局,改動可以說很小,弊端也明顯,會帶來額外計算量。 

<

2、NSMutableDictionary類

- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey;

</div>

Parameters

anObject    

The value for aKey. A strong reference to the object is maintained by the dictionary.

IMPORTANT

Raises an NSInvalidArgumentException if anObject is nil. If you need to represent a nil value in the dictionary, use NSNull.

aKey    

The key for value. The key is copied (using copyWithZone:; keys must conform to the NSCopying protocol). If aKey already exists in the dictionary, anObject takes its place.

IMPORTANT

</div> </div>

Raises an NSInvalidArgumentException if aKey is nil.

</div> </div>

setObject:forKey: 同樣是一個普遍用到的字典方法,上面的參數說明也說得比較清楚,當key和value為nil時,方法會拋異常并crash,因此,實踐 中,setObject:forKey:成了objectAtIndex:之外的crash另外一個主因。

代碼:

Cocoa開發優化之道

Log:

Cocoa開發優化之道

<

解決辦法:一是使用方法前,先做好判空兼容邏輯;二是通過合適的機制比如swizzle(《Objective-C與Runtime》)或者繼承插入判空兼容邏輯。總的來說,對于老項目,方案二是一個投入小回報高做法;

<

3、UIView類

- (void)setFrame:(CGRect)rect;

</div>

setFrame:UIKit 中最基本的類UIView的屬性,用于控制視圖的布局(layout),一般來說,該方法的使用不會有什么問題。只是在UILabel、UIButton 這些帶text顯示的類里面,如果rect參數沒有取整,設置了帶小數點的值,那么系統做文字渲染的時候可能會出現模糊(不銳利)的情況,在過去多個 iOS系統版本都能發現這樣的問題。

解決辦法:可以針對UILabel、UIButton、甚至UITableViewCell這些帶text顯示的類做setFrame:的overrride,或者引入safe方法,用floor、floorf、ceil、ceilf等先做取整處理。

<

4、UIViewController類

- (UIView *)view;

</div>

這是一個get方法,是UIViewController用來獲取自身的底View或者說主View的。如果按照我們平時的使用,想想也不會覺 得該方法有 什么不妥之處,確實是,view的問題并不在自身。不知道大家在類似initWithNibName:bundle:這些 UIViewController的初始化方法中,會不會對view做一個self.view或者viewController.view的引用操作,如 果有,不妨斷點跟蹤下,你會發現: UIViewController的初始化方法中,第一次 引用view的時候,系統會自動創建一個空白的view,同時會同步先調用viewDidLoad方法,再返回view對象。這時候大家可能看出問題所 在,init初始化方法 ->loadView->viewDidLoad,這是大家熟悉的UIKit的加載流程,大家在做布局,做邏輯時候,也往往是約定這樣流程的 前提下做的,那么viewDidLoad在init被提前調用,就可能打亂了一些原來的布局和邏輯,出現莫名其妙的bug。這樣的bug并非是假想出來, 而是實踐中實打實見過一次又一次的踩坑。。。
代碼:

Cocoa開發優化之道

Log:

Cocoa開發優化之道<

解決辦法:如果說從代碼上去 規避的話,也還沒有什么想到好的辦法,更多的是依靠攻城師自己的規范和意識吧:在init初始化方法里面,盡量不要提前引用view去做subviews 的layout,盡量把setFrame和addSubview等操作放到loadView或者viewDidLoad當中做。

<

5、NSData類

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;

</div>

這個方法大家都不會陌生,是data數據對象轉二進制文件的快捷方法。那么,寫文件能有什么問題?或許有人猜到了,path,對,路徑上,通過簡單的實驗,就可以確定該方法不會自動生成路徑中間的目錄,如果路徑中某個目錄不存在,則方法return NO,寫文件失敗。

解決辦法:一方面,目錄路徑生成接口需要做有效性檢查,發現目錄不存在,自動創建;另一方面,通過繼承或者代理等方式插入目錄有效性檢查的相關邏輯;

 <

6、UIColor類

+ (UIColor *)colorWithPatternImage:(UIImage *)image;

</div>

這 是一個挺有意思的類方法,我們可以根據一張圖片的效果去生成一個Color對象,從而以圖片的內容去渲染背景色。該方法經常對backgroundColor做設置來實現背景圖片效果。使用該方法的問題在于,圖片本身的size和UI控件的size需要保持一致性,因為該方法出來 的圖片效果并不支持縮放等特性,僅僅平鋪,所以,如果控件發生微調,圖片沒有跟進調整,往往就是視覺上的bug。

代碼:

Cocoa開發優化之道

效果:

Cocoa開發優化之道

代碼:

Cocoa開發優化之道

效果:

Cocoa開發優化之道

<

解決辦法:建議盡量只用于適合圖片平鋪的場景,避免其它需要自適應圖片的場景使用該方法。

<

7、NSString類

+ (instancetype)stringWithFormat:(NSString *)format;

- (instancetype)initWithFormat:(NSString *)format;

</div> </div>

這 兩個方法不用多說,最常用的格式化string方法,存在的問題大家或多或少也會接觸到。當格式化參數為對象類型的時候,比如[NSString stringWithFormat:@"%@", object]; 對象可能會是空指針,導致出現@”NULL”這樣的輸出結果。 

解決辦法:當然,由于方法使用數量過于大,我們不可能讓使用場景確保不為空,更多地可以通過swizzle、繼承、擴展等方法內部加以避免,以下就是用擴展方式做的過濾(由于這種方式效率不佳,并不適合原方法替換,所以只做了擴展,可用于UI顯示時候用):

Cocoa開發優化之道

<

8、UIView類

- (void)layoutSubviews;

Discussion
The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.
……
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.

</div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div> </div>

上面一段描述已經很好說明該方法的用途,它 埋下坑就是最后一句提到的——不可以直接invoke該方法,系統會在方法invoke前后做上下文的處理,應用自己直接invoke的話,上下文沒有 ready,可能會導致意想不到的情況,所以需要用setNeedsLayout和layoutIfNeeded間接invoke。

</div>

<

9、UIView類

- (void)drawRect;

</div> </div>

Discussion

The default implementation of this method does nothing. Subclasses that use technologies such as Core Graphics and UIKit to draw their view’s content should override this method and implement their drawing code there. You do not need to override this method if your view sets its content in other ways. For example, you do not need to override this method if your view just displays a background color or if your view sets its content directly using the underlying layer object.

……

This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.

</div> </div>

類似layoutSubviews,不可以直接invoke該方法,系統會在方法 invoke前后處理上下文,應用自己直接invoke的話,上下文沒有ready,導致draw rect失敗,需要用setNeedsDisplay和setNeedsDisplayInRect間接invoke。

<

</div> </div> </div> </div> </div> </div> </div> </div>

10、CALayer類

- (void)setCornerRadius;

</div> </div>

這是一個能對視圖的可視邊界快捷生成圓角效果的方法,Apple每年推陳出新的交互指導文檔上,iOS7之后圓角將成為新流行的設計語言,圓角相關的設計相信會被大量運用。由于使用便捷,所以該方法很受攻城師歡迎,然而實測該方法會有明顯的增加渲染的工作量,最主要原因是觸發了離屏渲染的邏輯,運用到tableview的cell上面,可能會影響滾動的流暢度,甚至明顯發生卡頓。

解決辦法:一是:簡單的圓角可以采用雙視圖疊加的方式,用上面一層視圖固定承擔圓角遮罩的效果,避免重復渲染;二是:原來,視圖的圖層(CALayer)設計上,已經支持遮罩的子圖層屬性,我們同樣可以很方便的利用該屬性(《巧用Layer做遮罩效果》)(此方案無優勢)

>>>>>>>>>>>>>>>>>> 2014-11-28 >>>>>>>>>>>>>>>>>>

經過反復驗證,layer的setMask和setCornerRadius的效 率其實接近,方案二無優勢,它們都遇到相同的問題——觸發了離屏渲染(Offscreen Rendering),嚴重會導致滑動卡頓。其實,遇到類似流暢度問題,還可以通過“壓縮圖片”的方向去嘗試優化。

<<<<<<<<<<<<<<<<<<< 2014-11-28 <<<<<<<<<<<<<<<<<<<

<

11、NSURL類

+ (instancetype)URLWithString:(NSString *)URLString;

</div>

Parameters

</tr> </tbody> </table>

這是iOS上的基礎類,提供url對象的操作實例,method是熟悉的,但對于參數的定義就未必。從Parameters的說明可以看 出,method對于輸入的string要求必須是符合RFC 2396的規范,即空格等保留字需要按百分號編碼(url encode)。同時解析(url decode)是支持RFC 1738的,即遇到+自動轉義為空格。對 于大部分的保留字,比如!*’();:@&=+$,/?#[],URLWithString:還是做了兼容,不影響url對象的創建。但是%、空 格以及非ASCII字符,則會直接返回null。%和非ASCII字符一般問題不大,因為涉及這些字符的參數值往往都會被及時發現并做url encode處理,但是,空格比較特殊,它可能一開始不會被發現,然后某種條件下在參數值的隨機字符串中間出現(比如例子中的guid),當然這是小概率 事件,然而當出現的時候,后果是非常嚴重,url無法生成意味著網絡訪問直接失敗。

代碼:

Cocoa開發優化之道

Log:

Cocoa開發優化之道

<

</div>

解決辦法:最簡單有效的方式是借鑒NSString類的NULL問題處理方式,針對輸入做safe method加以兼容過濾。

<

從2007年1月份的到現在,iOS的發展歷經近8年8大版本(App應用SDK從第2大版本開始),每一個版本都帶來大量的新特性和New API。我相信每個iOS攻城師都能從自身長期的實踐中總結出自己的“埋坑方法”。我也相信發現問題,解決了問題,就是意味著往前走一步,往更好用戶體驗 推進。因此,為極致的產品和可靠地項目,找坑,補坑,記之,share之。

來自: http://springox.w18.net/2015/09/04/cocoa開發優化之道/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
URLString The URL string with which to initialize the NSURL object. Must be a URL that conforms to RFC 2396. This method parses URLString according to RFCs 1738 and 1808.
  • sesese色