NSError NSLocalizedDescription 自动生成
source link: https://xiangwangfeng.com/2017/05/19/NSError-NSLocalizedDescription-%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
NSError NSLocalizedDescription 自动生成
19 May 2017
在 iOS 中我们常常会使用 NSError
来封装错误信息,相比于单纯的错误码,NSError
包含更多的信息。主要有
- domain 错误发生域
- code 错误码
- userInfo 详细信息
但这也意味着它的使用更繁琐些,一个简单的 NSError
初始化方法往往是这样
NSError *error = [NSError errorWithDomain:NIMLocalErrorDomain
code:NIMLocalErrorCodeInvalidParam
userInfo:nil];
这里我们定义了自己的字符串常量 NIMLocalErrorDomain
来表示错误发生域和 NIMLocalErrorCode
枚举表示对应的错误码。但一个懒得要死的程序员显然对这种繁琐的初始化方法不感冒,然后就引入了宏定义使自己少写几行代码:
#define NIMLocalError(x) ([NSError errorWithDomain:NIMLocalErrorDomain \
code:(x) \
userInfo:nil])
这样我们就可以使用 NSError *error = NIMLocalError(NIMLocalErrorCodeInvalidParam)
这种稍微简单的写法。
这是前几天我开脑洞之前云信这边的 NSError
的标准写法。
但是这样写法有个比较大的问题,当上层开发或者我们的客户在开发过程中碰到错误后,他只能看到 NSError
的 domain
和 code
信息(这也是我们在使用苹果某些 API 时常常会碰到的问题,只有 doamin
,code
和 the operation could not be completed
的描述),于是我们只能拿着 domain
和 code
去 google 一把或者查阅文档来确定真正的错误信息。
一种改进的方法是调整宏定义的写法,将宏定义调整为
#define NIMLocalError(code,reason) ([NSError errorWithDomain:NIMLocalErrorDomain \
code:(code) \
userInfo:@{NSLocalizedDescriptionKey : (reason)}])
这样我们就可以调整为 NSError *error = NIMLocalError(NIMLocalErrorCodeInvalidParam,@"参数错误")
。但这样的做法仍有两个问题
- 不同文件中相同错误需要重复填入相同的描述信息。
- 对于服务器只下发错误码而没有描述信息的情况无法处理。
一种简单解决方案是将错误码和错误信息的映射关系打包到一个 plist
文件或是写死在源码中,后续通过错误码反查描述信息并填充 NSError
。大致的代码如下
- (NSError *)error:(NSString *)domain
code:(NSInteger)code
{
NSString *message = [self.messages objectForKey:@(code)];
return message ? [NSError errorWithDomain:domain
code:code
userInfo:@{NSLocalizedDescriptionKey : message}] :
[NSError errorWithDomain:domain
code:code
userInfo:nil];
}
接下来就是苦力活:每添加一个错误码就需要同时在描述文件中添加对应的描述信息。
而实际情况是:所有错误码枚举,我们都已添加对应的注释,也就是错误码的描述信息。那么问题就来了:为什么我们还需要单独维护一份描述文件呢,直接使用注释信息不就好了么?
换而言之,我们为什么不直接把枚举定义的头文件当成一种描述文件,直接从中提取注释作为错误信息呢?似乎这是非常困难的事情,需要引入 Objective-C 语法解析库,进而提取语法树。而事实上,由于使用 VVDocument
或是 Xcode 自带生成注释方法生成的枚举注释格式非常单一,直接通过简单的行扫描就可以完成。
通过 VVDocument
生成的注释格式如下
/**
* 注释内容
*/
那么我们只需要检查当前行在 trim
后是否以 *
开头即可确定是否为注释内容行,并加以提取。
而对于枚举表达式,由于 Objective-C 的特殊性,所有枚举往往都会有自己的固定前缀,如
typedef NS_ENUM(NSInteger, NIMRemoteErrorCode) {
/**
* 客户端版本错误
*/
NIMRemoteErrorCodeInvalidVersion = 201,
/**
* 密码错误
*/
NIMRemoteErrorCodeInvalidPass = 302,
/**
* CheckSum校验失败
*/
NIMRemoteErrorCodeInvalidCRC = 402,
/**
* 非法操作或没有权限
*/
NIMRemoteErrorCodeForbidden = 403,
}
那我们就可以使用前缀进行匹配提取。具体实现如下
func parseComments(items : [String],domain :String,prefix : String) -> [String:String] {
var results = [String:String]()
var comment : String? = nil
for item in items {
if item.contains(prefix) {
let exp = item.replacingOccurrences(of: ",", with: " ")
let tokens = exp.components(separatedBy: "=")
if tokens.count == 2 {
let enumName = tokens[0].replacingOccurrences(of: " ", with: "")
let enumValue = tokens[1].replacingOccurrences(of: " ", with: "")
if let value = comment , let _ = Int(enumValue) {
results[enumName] = value
let key = domain + "." + enumValue
results[key] = enumName
}
}
}
else if item.trimmingCharacters(in: .whitespaces).hasPrefix("* ") {
let word = item.replacingOccurrences(of: "*", with: "").replacingOccurrences(of: " ", with: "")
comment = word
}
}
return results
}
通过简单的匹配(都不需要使用正则,跑)我们就获取了对应关系。最后通过在 Xcode 中设置相应的 RunScript
就可以使得生成描述源码的过程优先在编译前执行。
如图,至此整个流程就已完整:我们通过 nim_error_generator
读取并解析 NIMGlobalDefs.h
和 NIMAVChatDefs.h
中相应枚举信息,并最终将映射关系写入 NIMErrorManager.mm
,后者在生成 NSError
的时候会根据传入的 code
和 domain
查找对应的描述信息,并自动填入到 userinfo
。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK