iOS之NSString和NSNumber安全调用实例方法探索

现状

在实际工作中,可能会遇到,跟后台的同事约定某个变量的类型后,上线后某些情况不按约定的类型返回,导致程序在运行时Crash。

例如,声明一个KLUser的对象,里面有2个属性,nameage,开发的时候约定的类型都是字符串,突然某一天,返回的数据中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");
    }

猜你喜欢

转载自blog.csdn.net/weixin_34319640/article/details/87540592