在iOS開發過程中,我們常見的集合類中主要包括:
- 不可變的數組–NSArray
- 可變的數組–NSMutableArray
- 不可變的字典–NSDictionary
- 可變的字典–NSMutableDictionary
- 不可變的集合–NSSet
- 可變的集合–NSMutableSet
我們最常用且容易出錯的集合類就是:NSArray,NSMutableArray,NSMutableDictionary!
NSArray主要的問題是數組越界問題
NSMutableArray主要的問題是數組越界,和設置了nil到數組中
NSMutableDictionary 主要是在我們網絡請求時,設置參數時若參數為nil則閃退!
為了解決以上問題,我們想出了兩種方案:
- 為這些類添加類別,對之前的讀取方法進一步包裝,在包裝中添加錯誤判斷!這種方法入侵性過大,若工程之前已經開始了,大量的地方需要替換方法!
- 為這些類添加類別,使用runtime的method swizzling方法,為原讀取方法添加錯誤判斷,這種方法完全無侵入,我們只需要為其添加類別即可!
為此下面我們著重討論第二種方法
按照我們的第一想法肯定是類似下面的代碼:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzlingMethod];
});
}
+ (void)swizzlingMethod {
Class class = NSClassFromString(@"NSArray");
Method originMethod = class_getInstanceMethod(class, @selector(objectAtIndex:));
Method destMethod = class_getInstanceMethod(class, @selector(rh_objectAtIndex:));
method_exchangeImplementations(originMethod, destMethod);
}
- (id)rh_objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
代碼寫完,模擬數組越界訪問,結果根本沒有調用!
經過資料查詢:
Foundation框架里面有很多類使用類簇(class cluster)設計模式,來實現一些類如:UIButton,NSNumber,集合類等!其本質就是工廠設計模式;思路就是比如我們有一個類有很多的子類,如果把這些子類全部暴露給用戶,用戶會很暈,不如只暴露父類給用戶,然后在父類中提供初始化方法:直接獲取子類實例!如我們的NSNumber類他提供了:
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
這么多子類如果全部暴露給用戶,多復雜,我們只需要通過以上方法,想要獲取哪個子類的實例,就調用不同的初始化方法,多好!
回到我們的集合類,Foundation框架對集合類的實現也是采取這種模式;
比如我們的NSArray就有__NSArray0,__NSSingleObjectArrayI,__NSArrayI!為什么要分這幾種實例呢?首先__NSArray0是個單例,表示空數組,所有的空數組都一樣,__NSSingleObjectArrayI表示單個元素的數組!__NSArrayI表示多個元素的數組!
所以我們在method swizzling的時候必須使用真正的class,否則無效果!
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzlingMethod];
});
}
+ (void)swizzlingMethod {
// __NSArrayI,__NSArray0,__NSSingleObjectArrayI
[self swizzlingClass:NSClassFromString(@"__NSArray0")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_0objectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSArray0")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_0objectAtIndexedSubscript:)];
[self swizzlingClass:NSClassFromString(@"__NSArrayI")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_1objectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSArrayI")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_1objectAtIndexedSubscript:)];
[self swizzlingClass:NSClassFromString(@"__NSSingleObjectArrayI")
selector:@selector(objectAtIndex:)
destSelector:@selector(rh_IobjectAtIndex:)];
[self swizzlingClass:NSClassFromString(@"__NSSingleObjectArrayI")
selector:@selector(objectAtIndexedSubscript:)
destSelector:@selector(rh_IobjectAtIndexedSubscript:)];
}
+ (void)swizzlingClass:(Class)class
selector:(SEL)selector
destSelector:(SEL)destSelector {
Method originMethod = class_getInstanceMethod(class, selector);
Method destMethod = class_getInstanceMethod(class, destSelector);
method_exchangeImplementations(originMethod, destMethod);
}
- (id)rh_0objectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_0objectAtIndexedSubscript:idx];
}
}
- (id)rh_0objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_0objectAtIndex:index];
}
}
- (id)rh_1objectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_1objectAtIndexedSubscript:idx];
}
}
- (id)rh_1objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_1objectAtIndex:index];
}
}
- (id)rh_IobjectAtIndexedSubscript:(NSUInteger)idx {
if (self.count <= idx) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndexedSubscript:idx];
}
}
- (id)rh_IobjectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
這里面有個知識點是:當我們使用arr[]這種語法獲取數組值的時候,我們調用的是:objectAtIndexedSubscript:方法,若該方法未method swizzling的話當你使用arr[]這種語法的時候還是會有數組越界問題的出現!
在method swizzling過程中還遇到了一個坑:就是為了偷懶只寫一個
- (id)rh_objectAtIndex:(NSUInteger)index {
if (self.count <= index) {
[RHExceptionHelper raiseException:@"index beyond array count"];
return nil;
} else {
return [self rh_IobjectAtIndex:index];
}
}
然后不同的類都exchange這個方法,邏輯肯定是錯的,多次交換實現,最終成功的只有一個!
此外我們還加了一個異常處理,原因就是,我們在調試的時候還是希望數組越界等這種問題暴露出來,及時的修改bug,所以我們添加了一個方法實現:
+ (void)raiseException:(NSString *)reason {
#if DEBUG
[[NSException exceptionWithName:reason
reason:reason
userInfo:nil] raise];
#else
#endif
}