iOS 用Runtime解决服务器返回NSNull问题

iOS 用Runtime解决服务器返回NSNull问题

奇技yin巧 指过于奇巧而无益的技艺与制品.

转载请注明出处 http://blog.csdn.net/uxyheaven/article/details/48299599

系列文章请看http://blog.csdn.net/uxyheaven/article/category/5800569

问题描述

众所周知,服务器返回的数据时不时的就不靠谱一下

{
    "名字":"漂亮妹子",
    "备胎们":,
    "年纪":18
}

明明木有备胎, 结果返回@[]也好啊, 偏偏返回一个null.
这个时候呢, 我们native端就得在取倒数据的时候, 先判断类型是不是NSArray 还得判断非空

    NSArray *boys = data[@"备胎们"];
    if ([boys isKindOfClass:[NSArray class]] && boys.count > 0)
    {
        doSomething
    }

校验的代码写多了总是很影响心情, 服务器就不能加个空的就不返回或者返回@[]么, 就不能让我们安心的写个备胎1号 = boys[0]么?

让我们来看看奇技的解决办法吧.

思路:重写NSNull的消息转发方法, 让他能处理这些异常的方法.

消息转发的相关知识不了解的同学需要自行搜索下. 在写的时候,我们要考虑 @"",@0,@{},@[]这几种常用的类型空值, 再问到NSNull的一些不属于它的方法的时候, 如果那些空值可以响应的时候就丢给他们去处理去.

// count理所当然的就等于0了
int count = boys.count;

奇技有了, 接着是yin巧.

如果我们要二号备胎囧么办?

id boy = boys[1];

这可就直接越界了啊. 我们仔细的看下NSArray里的方法, 是不是发现一个规律, 基本上返回的是id类型的是取里面的元素的. 既然这样, 那么我们就干脆只要你问NSArray里要一个元素, 我就都返回’nil’给你.

if (strcmp(anInvocation.methodSignature.methodReturnType, "@") == 0)
{
    anInvocation.selector = @selector(__uxy_nil);
    [anInvocation invokeWithTarget:self];
    return;
}

- (id)__uxy_nil
{
    return nil;
}

完整的代码如下

// .h文件
@interface NSNull (XY_InternalNullExtention)
@end

// .m文件
#define UXY_NullObjects @[@"",@0,@{},@[]]

@implementation NSNull (XY_InternalNullExtention)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];

    if (signature != nil) return signature;

    for (NSObject *object in UXY_NullObjects)
    {
        signature = [object methodSignatureForSelector:selector];

        if (signature)
        {
            if (strcmp(signature.methodReturnType, "@") == 0)
            {
                signature = [[NSNull null] methodSignatureForSelector:@selector(__returnNil)];
            }
            break;
        }
    }

    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if (strcmp(anInvocation.methodSignature.methodReturnType, "@") == 0)
    {
        anInvocation.selector = @selector(__uxy_nil);
        [anInvocation invokeWithTarget:self];
        return;
    }

    for (NSObject *object in UXY_NullObjects)
    {
        if ([object respondsToSelector:anInvocation.selector])
        {
            [anInvocation invokeWithTarget:object];
            return;
        }
    }

    [self doesNotRecognizeSelector:anInvocation.selector];
}

- (id)__uxy_nil
{
    return nil;
}

@end

测试的代码

    UXY_EXPECTED( ((NSArray *)[NSNull null])[1] == nil );
    UXY_EXPECTED( ((NSDictionary *)[NSNull null])[nil] == nil );
    UXY_EXPECTED( [((NSString *)[NSNull null]) substringToIndex:2] == nil );

这个文件在我的开源库的NSNull+XY文件里, 若果本文对你有所启发, 欢迎给个star.
https://github.com/uxyheaven/XYQuick

### iOS 开发中返回值为 `null` 的原因分析 在 iOS 开发过程中,当函数或方法的返回值为 `null` 或者解析 JSON 数据时出现 `[NSNull length]: unrecognized selector sent to instance` 错误,通常是因为数据源中的某些字段被标记为了 `NSNull` 类型[^3]。这种情况下,程序尝试调用不存在的方法(如 `length` 或 `count`),从而引发崩溃。 以下是可能的原因以及对应的解决方案: --- #### 原因一:JSON 数据中存在 NSNull 字段 在处理来自服务器端的 JSON 数据时,如果某个键对应的数据为空,服务端可能会将其设置为 `null`。而在 Objective-C 中,`null` 被映射为 `NSNull` 对象。因此,在访问这些字段时如果没有进行类型判断,则会触发上述错误。 ##### 解决方案: 在解析 JSON 数据之前,先检查字段是否为 `NSNull` 并对其进行特殊处理。例如: ```objc id value = jsonObject[@"key"]; if (value == [NSNull null]) { value = @""; // 将其替换为默认值或其他逻辑处理 } NSString *stringValue = (NSString *)value; ``` 通过这种方式可以有效避免直接对 `NSNull` 发送不支持的消息。 --- #### 原因二:内存管理问题导致的对象释放 另一个可能导致返回 `null` 的原因是内存管理不当。尤其是在手动引用计数模式下(MRC),未正确保留对象或者过早释放对象都可能导致后续操作获取到已销毁实例的结果——即表现为 `null` 或其他异常行为[^1]。 ##### 解决方案: 确保遵循正确的所有权语法规则来管理对象生命周期;对于 ARC(Automatic Reference Counting),确认没有意外断开强引用链路的情况发生。同时也可以利用 Instruments 工具检测潜在泄漏点与悬空指针问题。 --- #### 原因三:接口设计差异引起冲突 有时 API 定义与其实际实现之间可能存在矛盾之处,比如声明了一个特定类型的返回值却最终给出了不同的结果(如上面提到的例子)。这不仅限于自定义类库内部也可能存在于第三方框架集成场景之中。 ##### 解决方案: 仔细核对接口文档说明并与真实运行表现对比找出不符之处加以修正调整。必要时候联系供应商获得最新版更新修复此类 bug 。 --- ### 总结代码片段示范 下面给出一段综合考虑以上各点后的安全取值方式样例: ```objc NSDictionary *jsonObject = ... ;//假设这是从网络请求得到并成功转换成字典形式的数据结构 for (NSString *key in jsonObject) { id value = jsonObject[key]; if ([value isKindOfClass:[NSNull class]]) { NSLog(@"%@ is Null", key); continue;//跳过该条目不做进一步处理 } if (![value respondsToSelector:@selector(length)]){ //这里可以根据具体需求扩展更多兼容性验证条件 NSLog(@"%@ does not support method 'length'", key); continue ; } NSString* safeString=(NSString*)value; NSLog(@"Key:%@ has Length:%lu ",key,(unsigned long)[safeString length]); } ``` ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值