在处理后端返回的 null
值时,尤其是在复杂的数据结构中,如字典、数组中的某些元素为 null
,确实是一个常见的问题。大公司和行业中的解决方案通常会从多个维度来处理这个问题,包括网络层、数据解析层、模型转换层等。以下是一些常见的策略和工具类来处理 null
值问题:
1. 服务器端数据清理
最好的解决方案是从源头避免问题。大公司通常会在后端严格定义 API 的返回结构,避免传递 null
,使用空字符串、空数组、空字典等安全值代替。这种约定可以极大减少客户端需要做的判断。
-
GraphQL:例如一些大公司使用的
GraphQL
强类型查询语言,它可以在数据定义层严格要求哪些字段是必选的,哪些字段是可选的,避免null
值传递到客户端。 -
RESTful API:在 REST API 设计中,也可以通过 API 文档严格定义返回值的类型,并使用后端中间件确保返回的数据中不会出现
null
。
2. 客户端通用工具类
在客户端,尤其是处理复杂嵌套数据结构时,常用的做法是使用通用工具类来处理 null
值,以减少每次手动判断的成本。比如大公司会建立数据清理工具类,自动清理模型中的 NSNull
。
-
NSNull 清理工具:你可以使用递归遍历模型,自动替换
NSNull
为安全值。前面提到的replaceNullInModel
方法就是一个很好的例子。 -
深度清理 JSON:你可以在请求成功的回调中,统一清理返回的 JSON 数据,将所有
null
替换为安全值(如nil
、空数组等),避免在解析和使用时出现问题。
示例工具类:
+ (id)removeNullsFromObject:(id)obj {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:obj];
for (id key in [mutableDict allKeys]) {
mutableDict[key] = [self removeNullsFromObject:mutableDict[key]];
}
return [mutableDict copy];
} else if ([obj isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:obj];
for (NSInteger i = 0; i < [mutableArray count]; i++) {
mutableArray[i] = [self removeNullsFromObject:mutableArray[i]];
}
return [mutableArray copy];
} else if (obj == [NSNull null]) {
return nil; // 或者返回安全值,如 @""、@{}、@[]
}
return obj;
}
+ (BOOL)isEmpty:(id)result {
if (result == nil || [result isEqual:[NSNull null]] || [result isKindOfClass:[NSNull class]]) {
return YES;
} else if ([result isKindOfClass:[NSString class]]) {
NSString *str = (NSString *)result;
return (str.length == 0);
} else if ([result isKindOfClass:[NSArray class]]) {
NSArray *array = (NSArray *)result;
return (array.count == 0);
} else if ([result isKindOfClass:[NSDictionary class]]) {
NSDictionary *dict = (NSDictionary *)result;
return (dict.allKeys.count == 0);
}
return NO;
}
#import <objc/runtime.h>
@implementation NSObject (NullSafe)
+ (void)replaceNullInModel:(id)model {
unsigned int count = 0;
// 获取 model 的属性列表
objc_property_t *properties = class_copyPropertyList([model class], &count);
for (unsigned int i = 0; i < count; i++) {
// 获取属性名称
const char *propName = property_getName(properties[i]);
if (propName) {
NSString *propertyName = [NSString stringWithUTF8String:propName];
id value = [model valueForKey:propertyName];
if (value == [NSNull null]) {
// 如果属性是 NSNull,替换为 nil
[model setValue:nil forKey:propertyName];
} else if ([value isKindOfClass:[NSArray class]]) {
// 如果属性是数组,递归检查数组中的每一个元素
NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:value];
for (int j = 0; j < [mutableArray count]; j++) {
id arrayItem = mutableArray[j];
if (arrayItem == [NSNull null]) {
// 将 NSNull 替换为空对象,空数组或空字符串视场景而定
mutableArray[j] = @"";
} else if ([arrayItem isKindOfClass:[NSObject class]]) {
// 如果是一个自定义对象,递归替换其内部的 NSNull
[self replaceNullInModel:arrayItem];
}
}
[model setValue:[mutableArray copy] forKey:propertyName];
} else if ([value isKindOfClass:[NSDictionary class]]) {
// 如果属性是字典,递归检查字典的每一个键值对
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionaryWithDictionary:value];
for (NSString *key in [mutableDict allKeys]) {
id dictValue = mutableDict[key];
if (dictValue == [NSNull null]) {
mutableDict[key] = nil;
} else if ([dictValue isKindOfClass:[NSObject class]]) {
// 如果是一个自定义对象,递归替换其内部的 NSNull
[self replaceNullInModel:dictValue];
}
}
[model setValue:[mutableDict copy] forKey:propertyName];
}
}
}
free(properties);
}
@end
3. 网络层处理 (AFNetworking / Alamofire)
在使用 AFNetworking 或者 Alamofire 等网络库时,可以在数据返回后,统一处理 NSNull
问题。
-
AFNetworking:可以在请求成功后的回调中,统一对 JSON 数据进行
NSNull
处理。AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager GET:urlString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { id cleanedResponse = [self removeNullsFromObject:responseObject]; // 使用 cleanedResponse 进行后续操作 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { // 错误处理 }];
Alamofire(Swift)也可以使用类似的方式,或者在
responseDecodable
中间层实现NSNull
清理。
4. MJExtension 处理 null 值
MJExtension
是一个常用的 JSON 转模型工具,可以通过一些配置来规避 null
问题。
-
自动将
null
值转为nil
:在MJExtension
中,你可以通过mj_setupReplacedKeyFromPropertyName
来定制字段的处理。[SGLayoutNaviModel mj_setupReplacedKeyFromPropertyName:^NSDictionary *{ return @{ @"primaryText" : @"text", // 其他字段映射 }; }];
-
忽略
null
值:你可以通过 MJExtension 的mj_setupBlockReturnValue
来处理null
。[SGLayoutNaviModel mj_setupBlockReturnValue:^id _Nullable(NSString *propertyName, id value) { if ([value isKindOfClass:[NSNull class]]) { return nil; // 或者返回你想要的安全值 } return value; }];
5. 通过 Swift 语言的 Optionals (Swift)
如果项目中使用的是 Swift,Swift 的 Optional
机制是处理 null
的最佳方式。Swift 中的 nil
和 Optional
强类型保证了你必须显式处理可能为空的值,从根本上避免了 null
的出现。
let text: String? = dict["text"] as? String
通过 Swift 的 Optional 解包机制,你可以轻松确保在使用前处理 nil
,从而避免 null
导致的潜在崩溃。
6. 大公司和行业惯例
大公司在处理数据流时,通常会建立一整套规范和工具链来处理 null
值。
后端数据标准化:通过后端数据标准化和严格的 API 设计(如 Swagger 或 OpenAPI),来确保每个字段都有明确的类型和是否可选,避免
null
值的传递。前端数据层的清理工具:如上文提到的,在客户端,通常会使用工具类对每一个模型进行深度清理。
ViewModel 层的封装:很多项目会使用 ViewModel 来封装和处理业务逻辑。在 ViewModel 层会将原始数据清洗和标准化,使之符合界面的需求,避免直接操作后端返回的原始数据。
7. 使用 JSONSerialization
配置
在解析 JSON 时,NSJSONSerialization
自带的配置可以用来帮助处理 null
值。你可以使用 NSJSONReadingOptions
的配置避免解析过程中出现 NSNull
。
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
虽然这不会自动处理 NSNull
,但你可以在后续处理时结合前面的工具类来清洗数据。
8. 自定义模型初始化方法
为了确保安全性,可以在自定义模型的初始化方法中对所有属性进行检查,并替换 null
值。
- (instancetype)initWithDictionary:(NSDictionary *)dict {
self = [super init];
if (self) {
_text = dict[@"text"] != [NSNull null] ? dict[@"text"] : @"";
_link = dict[@"link"] != [NSNull null] ? dict[@"link"] : @[];
// 其他属性初始化
}
return self;
}
总结:
从多个角度来处理 null
值问题:
-
后端:尽量在后端保证返回的数据中避免
null
。 -
网络层:在 AFN 或其他网络层中添加统一的
null
清理逻辑。 -
工具类:使用递归的工具类清理 JSON 中的
NSNull
。 - 模型层:在 MJExtension 等 JSON 转模型工具中设置默认处理。
-
行业实践:大公司通常使用严格的 API 设计和前端数据标准化工具,结合 Swift 的 Optional 类型可以彻底避免
null
带来的问题。
这种多层级的处理方式,能确保项目各个层级都能够有效处理 null
,避免崩溃和不安全的数据操作。