用宏提速NSCoding

还记得你要为每个类实现dealloc方法的日子吗?从头文件复制每一个实例变量到dealloc方法不只是很烦,简直是一场灾难。忘了一个实例变量?内存泄漏,有个含有 30 个实例变量的类,并且意外地释放了两次相同的实例变量?崩溃。

幸运的是,那种内存管理的日子已经过去了,但归档和解档(序列化和反序列化)对象仍然存在,那是有多惨地写下面的代码:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if(self) {
        _val = [aDecoder decodeIntForKey:@"_val"];
        _obj = [aDecoder decodeObjectForKey:@"_obj"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:_obj forKey:@"_obj"];
    [aCoder encodeInt:_val forKey:@"_val"];
}

这不仅是代码惨,它容易出错,如果在方法中拼错了 key 会发现什么?要么你不保存这些数据要么不重新加载它,这两种都是错误的,是不明显的。然而你的应用程序其他地方会出错,而且你会花时间来追踪问题最后发现则是你的NSCoding方法中一个简单的拼写错误。

有一个很简单的解决方法,虽然它依赖于 C 预处理器一个相对比较陌生的功能:字符串化(stringification),在任何 C 文件,当然也是 Objective-C 文件,你可以创建一个宏包含#符号在一个标识(symbol)前,这个宏就可以转换一个标识到一个 C 的字符串(char *),例如:

#define STRINGIFY(x) #x
int myVariable = 5;
NSLog(@"%s", STRINGIFY(myVariable));

打印出什么?“myVariable”

好,我还可以创建一个这样的宏:

#define OBJC_STRINGIFY(x) @#x

这意味着返回的不是一个 C 字符串,而是一个 NSString:

int myVariable = 5;
NSString *foo = OBJC_STRINGIFY(myVariable);
NSLog(@"%@", foo);

好,这样也打印出了"myVariable",那这跟NSCoding有啥关系?当然,归档中实例变量的编码和解码可以使用变量名作为 key。

[aCoder encodeObject:_myInstanceVariable forKey:@"_myInstanceVariable"];

你可能会发现,我们是代表在这行代码两种方式相同的“字符串”:曾经作为编译器的象征,曾经作为一个 NSString。它会更简单和无差错的,只写_myInstanceVariable 一次,让编译器检查。随着 OBJC_STRINGIFY,那很简单:

#define OBJC_STRINGIFY(x) @#x
#define encodeObject(x) [aCoder encodeObject:x forKey:OBJC_STRINGIFY(x)]
#define decodeObject(x) x = [aDecoder decodeObjectForKey:OBJC_STRINGIFY(x)]

现在你的NSCoding方法看起来像这样:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if(self) {
        decodeObject(_obj);
    }
    return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    encodeObject(_obj);
}

其中一件事是有些人不喜欢这样的宏,他们无法验证,实际上是正在编写的代码 - 假的。打开.m 文件,选择菜单 Product -> Generate Output -> Preprocessed File。 Xcode 中会产生你的执行文件看起来像所有的预处理器指令进行了解析之后一样。

请记住,虽然 #import 是一个预处理器指令。所以一个预处理的 .m 文件将从 Foundation 、 UIKit 中包含所有头文件,和其他任何你可能已引入的 - 所以一定要向下滚动到的预处理结果的底部。(您可能还注意到,你可以以类似的方式生成程序集,它总是有趣的。)

所以,你在哪里定义这些宏像 OBJC_STRINGIFY? 通常,我把它们放在头文件中,有许多快速实用的宏,我喜欢用在很多项目中。然后给创建的每一个新项目,导入头文件到我的预编译头文件(每个项目都有的 .pch 文件)。对一个项目的预编译头文件被包含在项目中的每个文件。这意味着两件事情:在 .pch 文件中任何事情都是可以在每个文件在您的项目,无论合适更改了 .pch 文件,你必须重新编译整个项目。

现在,回到 NSCoding:记得归档时,你可能不只是编码或解码对象。您可能会编码整数,浮点数,甚至是结构体。所以,你可能还会需要这样的宏:

#define encodeFloat(x) [aCoder encodeFloat:x forKey:OBJC_STRINGIFY(x)]

对于结构体,我通常归档无论如何都是分解结构的每个成员,所以如果我编码 CGPoint ,例如,它看起来像这样:

encodeFloat(_point.x);
encodeFloat(_point.y);

当然,如果不相信我的话代码可不会骗你,你可以自己写和生成的预处理文件中看到的结果。


原文链接:http://stablekernel.com/blog/speeding-up-nscoding-with-macros/

这翻译的真辛苦,差点看不下去,太渣了…不过确实是一篇好文

关于字符串化(stringification)的一点参考资料:http://gcc.gnu.org/onlinedocs/cpp/Stringification.html

comments powered by Disqus