现状
在实际工作中,可能会遇到,跟后台的同事约定某个变量的类型后,上线后某些情况不按约定的类型返回,导致程序在运行时Crash。
例如,声明一个KLUser的对象,里面有2个属性,name
和age
,开发的时候约定的类型都是字符串,突然某一天,返回的数据中age
字段的类型变成NSNumber
,这时代码中,某些使用age字段调用-(void)length
方法就会出现crash。
@interface KLUser : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *age;
@end
原来正确的样品数据接口
{
name:"ruofeng",
age: "18"
}
后来异常的数据结构
{
name:"ruofeng",
age: 18
}
备注:Json转Model使用Mantle版本1.5.1
(robb released this on 8 Oct 2014)
目标
当声明的类型不匹配时,并且能够互相转换时,不要crash
路径
Plan A
升级Mantle到最新版本2.0.1,看是否解决的这个问题,如果解决了,再看如何把现有代码适配到最新版本,如果没有解决,此方案无效。
#489 and #499 fix Objective-C++ compatibility (thanks @zsk425, @johanrydenstam, and @robb!)
#503 improves error messaging for validating transformers (thanks @robb!)
#520 dramatically improves the performance of dynamic method invocations (thanks @adamkaplan!)
#503
Improve error message for +mtl_validatingTransformerForClass:
Plan B
手动重新网络成校验方法,当为每个model添加一个方法类型约束,如果类型不匹配,debug模式下崩溃。
每个需要校验的model都需要重写该方法,成本太高。
Plan C
使用runtime重写以下几个方法
- NSNumber的
-(NSInteger)length
- NSNumber的
-(BOOL)isEqualToString:(NSString *)string
- NSString的
-(BOOL)isEqualToNumber:(NSNumber *)number
优点, 改动小,可控
缺点,只能覆盖重写了的方法,调用其他方法还是会有问题
C的具体实现
NSNumber的- (NSInteger)length和- (BOOL)isEqualToString:(NSString *)string
@implementation NSNumber (KLSafeHook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//__NSCFNumber
swizzleInstanceMethod(NSClassFromString(@"__NSCFNumber"), @selector(length), @selector(kl_length));
swizzleInstanceMethod(NSClassFromString(@"__NSCFNumber"), @selector(isEqualToString:), @selector(kl_IsEqualToString:));
});
}
- (NSInteger)kl_length
{
if ([self isKindOfClass:[NSString class]]) {
return [(NSString *)self length];
} else {
if ([self isKindOfClass:[NSNumber class]]) {
NSString *str = [NSString stringWithFormat:@"%@", self];
#if KL_SAFE_HOOK_FLAG
NSAssert(NO, @"current type【NSNumber】 is not match the interface(NSString),here has auto transfer into 【NSString】, please check!!!");
#endif
return [str length];;
} else {
NSAssert(NO, @"current type is not match the interface(NSString), please check!!!");
return -1;
}
}
}
- (BOOL)kl_IsEqualToString:(NSString *)string
{
if ([self isKindOfClass:[NSString class]]) {
return [(NSString *)self isEqualToString:string];
} else {
if ([self isKindOfClass:[NSNumber class]]) {
NSString *str = [NSString stringWithFormat:@"%@", self];
#if KL_SAFE_HOOK_FLAG
NSAssert(NO, @"current type【NSNumber】 is not match the interface(NSString),here has auto transfer into 【NSString】, please check!!!");
#endif
return [str isEqualToString:str];;
} else {
NSAssert(NO, @"current type is not match the interface(NSString), please check!!!");
return NO;
}
}
}
- (BOOL)isEqualToNumber:(NSNumber *)number
@implementation NSString (KLSafeHook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzleInstanceMethod(NSClassFromString(@"NSTaggedPointerString"), @selector(isEqualToNumber:), @selector(kl_isEqualToNumber:));
swizzleInstanceMethod(NSClassFromString(@"__NSCFConstantString"), @selector(isEqualToNumber:), @selector(kl_isEqualToNumber:));
});
}
- (BOOL)kl_isEqualToNumber:(NSNumber *)number
{
if ([self isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)self isEqualToNumber:number];
} else {
if ([self isKindOfClass:[NSString class]]) {
NSString *str = self;
NSNumber *numberVlue = [NSNumber numberWithFloat:str.doubleValue];
#if KL_SAFE_HOOK_FLAG
NSAssert(NO, @"current NSString is not match the interface(NSNumber),here has auto transfer into NSNumber, please check!!!");
#endif
return [numberVlue isEqualToNumber:number];
} else {
NSAssert(NO, @"current type is not match the interface(NSNumber), please check!!!");
return NO;
}
}
}
@end
测试用例
NSArray *array = @[@"123", @123];
for (NSString *str in array) {
NSLog(@"length:%ld",str.length);
NSLog(@"isEqual:%@",[str isEqualToString:@"123"] ? @"YES" : @"NO");
}
for (NSNumber *number in array) {
NSLog(@"isEqual:%@",[number isEqualToNumber:@123] ? @"YES" : @"NO");
}
网友评论