作者:夏斌
NSArray NSMutableArray NSDictionary NSMutableDictionary 是我們的在iOS開發中非常常用的類。當然,在享受這些類的便利的同時,它們也給我們帶來一些困擾。粗心我們可能會調用addObject:傳入一個nil, 也有可能是會objectAtIndex:傳入一個越界的index。尤其是在數據基本依賴於服務端返回的的情況,這種crash大幅增加。最近項目上經常出現NSDictionary的setObject:forKey:的nil object的崩潰。我們希望為這種崩潰找一個解決辦法。
解決方案
函數包裝
我們希望能夠用一個統一的方法解決粗心的程序員可能傳入的nil object。我們最先想到的想法是對這些函數進行一個包裝,比如objectAtIndex,我們寫一個如下的函數
- (id)safeObjectAtIndex:(NSUInteger)index { if (index >= self.count) { return nil; } return [self objectAtIndex:index]; }
以後所有調用objectAtIndex的地方統統替換為safeObjectAtIndex。 不過,這顯然不是我想要的,我不希望改變現有的調用方式,大家可能是通過[]或objectAtIndex(不推薦)的方式獲取數組元素,如果要做替換的話改動勢必非常大,而且不便於以後的移植。我們希望代碼調用objectAtIndex的時候,能夠被我們先捕獲到,進行處理之後再調用Cocoa的這個方法。Objective-C作為一門動態語言,有強大的動態加載的能力,提供了Method swizzling實現這樣的功能。現在,黑魔法起飛。
Method swizzling
關於Method swizzling的具體實現,不打算多說,這裡談一下我遇到的問題。最初在做Method swizzling的時候,我嘗試去替換NSArrat的objectAtIndex:,但我始終沒有辦法替換掉這個方法。後來,搞了半天才發現,我們使用的NSAarray或者NSMutableArray並不是我們所看到樣子,它們是class cluster。objectAtIndex:並不是他們的方法,而是他們背後的concrete class: __NSArrayI __NSArrayM的方法。解決了這個問題,剩下的就很簡單了。
使用
直接把XTSafeCollection.hXTSafeCollection.m拖入工程,NSArrayNSMutableArrayNSDictionaryNSMutableDictionary這些類的API以前是怎麼調用的,還怎麼寫,完全不用修改。Demo裡,我全部以傳統的會引起crash的方式調用代碼,以下是我的Demo的代碼和輸出
NSArray *array = @[@"a", @"b"]; NSMutableArray *mutableArray = [@[@"aa", @"bb"] mutableCopy]; // Object at index NSLog(@"%@", array[10]); NSLog(@"%@", mutableArray[100]); // add object [mutableArray addObject:nil]; // Insert object [mutableArray insertObject:nil atIndex:0]; [mutableArray insertObject:@"cc" atIndex:10]; // Replace object [mutableArray replaceObjectAtIndex:0 withObject:nil]; [mutableArray replaceObjectAtIndex:10 withObject:@"cc"]; // Dictionary NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary]; mutableDictionary[nil] = @"1"; mutableDictionary[@"1"] = nil; 2015-08-25 18:10:23.932 XTSafeCollection[29067:443479] [__NSArrayI objectAtIndex:] index {10} beyond bounds [0...1] 2015-08-25 18:10:23.933 XTSafeCollection[29067:443479] (null) 2015-08-25 18:10:23.933 XTSafeCollection[29067:443479] [__NSArrayM objectAtIndex:] index {100} beyond bounds [0...1] 2015-08-25 18:10:23.933 XTSafeCollection[29067:443479] (null) 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSArrayM addObject:], NIL object. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSArrayM insertObject:atIndex:] NIL object. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSArrayM insertObject:atIndex:] index {10} beyond bounds [0...1]. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSArrayM replaceObjectAtIndex:withObject:] NIL object. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSArrayM replaceObjectAtIndex:withObject:] index {10} beyond bounds [0...1]. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSDictionaryM setObject:forKey:] NIL key. 2015-08-25 18:10:23.934 XTSafeCollection[29067:443479] [__NSDictionaryM setObject:forKey:] NIL object.
安裝
源碼: https://github.com/wuwen1030/XTSafeCollection
pod XTSafeCollection
已知問題
替換NSMuatbelArray的objectAtIndex:引起鍵盤展示狀態態切換後台的崩潰,拋出*** -[UIKeyboardLayoutStar release]: message sent to deallocated instance 0x7f883beac9c0 在這裡找到了解決方法