文档章节

有关宏定义的一篇文章

Lambda8421
 Lambda8421
发布于 2015/02/10 14:36
字数 1090
阅读 7
收藏 0

Speeding up NSCoding with Macros

Remember the days where you had to implement the dealloc method for every class? Copying each instance variable from the header file and pasting it into dealloc was not only a pain the ass, it was a recipe for disaster. Forgot one instance variable? Memory leak. Had 30 or so instance variables for a class and accidentally released the same instance variable twice? Crash.

Fortunately, those days are over for memory management – but they still exist for archiving and unarchiving objects. How miserable is it writing the following code:


- (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"];
}


Not only is this code miserable to type, but it is exceptionally prone to error: what happens if you misspell a key in one of the methods? Either you don’t save some piece of data or don’t reload it, both of which are errors that won’t make themselves obvious. Instead, some other part of your application will stop working and it will take a little bit of time to trace the issue back to a simple typo in your NSCoding methods.

There is a very simple solution, though, and it relies on a relatively unknown feature of the C preprocessor: stringification. In any C file, and therefore any Objective-C file, you can create a macro that contains the # character in front of a symbol. The macro turns that symbol into a C string (char *). For example, this:


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


What’s that print? “myVariable”. Alright, what does this buy us?

Well, I can create another macro like this:


#define OBJC_STRINGIFY(x) @#x


Which means instead of getting a C string back, I get an NSString:


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


This, of course, prints out “myVariable” again. Why does this matter for NSCoding? Well, convention for encoding and decoding instance variables is to use the name of that instance variable as the key:


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


As you will notice, we are representing the same “string” in two ways in this line of code: once as a symbol for the compiler and once as an NSString. It would be much more simple and error-free to only write _myInstanceVariable once and have the compiler check it. With OBJC_STRINGIFY, that is easy:


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


Now, your NSCoding methods can look like this:


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


One of the things some folks don’t like about macros like this is that they can’t verify the code that is actually being written – false. With a .m file open, from the menubar, select Product -> Generate Output -> Preprocessed File. Xcode will generate what your implementation file looks like after all preprocessor directives have been evaluated.

Remember, though, that #import is a preprocessor directive. Therefore, a preprocessed .m file will contain every header file from Foundation, UIKit, and anything else you may have included – so be sure to scroll down to the bottom of the preprocessed result. (You may also notice that you can generate the assembly in a similar way, which is always interesting to check out.)

So, where do you define these macros like OBJC_STRINGIFY? Typically, I put them into a header file that has a lot of quick utility macros that I like to use in a lot of projects. Then, for each new project that I create, I import that header file into my pre-compiled header (the .pch file that comes with all of your projects). The pre-compiled header file for a project is transparently included in every file in a project. This means two things: anything in the .pch file is available in every file in your project and any time you change the .pch file you have to re-compile your entire project.

Now, back to NSCoding for a brief moment: remember that when archiving, you may not be encoding or decoding just objects. You may be encoding an integer, float or even a structure. So, you will probably need macros like this as well:


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


For structures, I typically decompose each member of a structure during archiving anyhow, so if I were encoding a CGPoint, for example, it would look like this:


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


Of course, don’t take my word that this code works (even though it does), write it yourself and generate the preprocessed file to see the result.

thoughts on “Speeding up NSCoding with Macros

Jon Kean September 25, 2013 at 8:38 pm

The stringify function will not generate any error if the variable name is misspelled or non existent.
To fix this, the macro can simply reference the variable directly (even if the usage can never be reached):

#define OBJC_STRINGIFY(fieldName) (true || fieldName ? (@"" #fieldName) : @"unusedDummyString")

All the various encodeType() macros can be changed into a single macro using the ‘overloadable’ specifier:

#define fieldName(fieldName) (true || fieldName ? (@"" #fieldName) : @"unusedDummyString")

#define encode(coder, fieldToEncode) __encodeWithCoder(coder, fieldToEncode, fieldName(fieldToEncode))
#define decode(decoder, fieldToDecode) __decodeWithCoder(decoder, fieldToDecode, fieldName(fieldToDecode))

static inline void __attribute__((overloadable)) __encodeWithCoder(NSCoder *coder, NSObject *obj, NSString *key) {
[coder encodeObject:obj forKey:key];
}
static inline void __attribute__((overloadable)) __encodeWithCoder(NSCoder *coder, NSInteger value, NSString *key) {
[coder encodeInteger:value forKey:key];
}
static inline void __attribute__((overloadable)) __encodeWithCoder(NSCoder *coder, double value, NSString *key) {
[coder encodeDouble:value forKey:key];
}
static inline void __attribute__((overloadable)) __encodeWithCoder(NSCoder *coder, BOOL value, NSString *key) {
[coder encodeBool:value forKey:key];
}
// dummy value is ignored, but needed for overloading
static inline id __attribute__((overloadable)) __decodeWithCoder(NSCoder *decoder, NSObject *dummyValue, NSString *key) {
return [decoder decodeObjectForKey:key];
}
static inline NSInteger __attribute__((overloadable)) __decodeWithCoder(NSCoder *decoder, NSInteger dummyValue, NSString *key) {
return [decoder decodeIntegerForKey:key];
}
static inline double __attribute__((overloadable)) __decodeWithCoder(NSCoder *decoder, double dummyValue, NSString *key) {
return [decoder decodeDoubleForKey:key];
}
static inline BOOL __attribute__((overloadable)) __decodeWithCoder(NSCoder *decoder, BOOL dummyValue, NSString *key) {
return [decoder decodeBoolForKey:key];
}

Encoding enum types will cause errors if the enum was not declared as an explicit type. To fix, change your enum declarations to use built in NS_ENUM macro:
typedef NS_ENUM(NSInteger, SomeEnumType)


© 著作权归作者所有

Lambda8421
粉丝 10
博文 121
码字总数 121640
作品 0
闸北
程序员
私信 提问
x264源代码简单分析:编码器主干部分-2

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leixiaohua1020/article/details/45719905 ===================================================== H.264源代......

雷霄骅
2015/05/14
0
0
x264源代码简单分析:宏块分析(Analysis)部分-帧内宏块(Intra)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leixiaohua1020/article/details/45917757 ===================================================== H.264源代......

雷霄骅
2015/05/22
0
0
关于 Linux 核心的10个面试问题与答案

又到了以轻松的心情来读些严肃内容的时刻了,哈!这是另一篇关于面试问题的文章,我们将展示10个Linux核心问题,毫无疑问将使你更近一层楼。 1.你需要定义一个宏,绑定一个存在的命令。你将怎...

LitStone
2014/02/21
4.5K
12
x264源代码简单分析:熵编码(Entropy Encoding)部分

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/leixiaohua1020/article/details/45944811 ===================================================== H.264源代......

雷霄骅
2015/05/24
0
0
U-boot在S3C2440上的移植详解(四)---支持DM9000EP网卡

在这一篇中,我们首先让开发板对CS8900或者DM9000X网卡的支持,然后再分析实现u-boot怎样来引导Linux内核启动。因为测试u-boot引导内核我们要用到网络下载功能。 7)u-boot对CS8900或者DM900...

宁宁爸
2015/11/27
79
0

没有更多内容

加载失败,请刷新页面

加载更多

只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
今天
58
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
今天
27
0
全世界到底有多少软件开发人员?

埃文斯数据公司(Evans Data Corporation) 2019 最新的统计数据(原文)显示,2018 年全球共有 2300 万软件开发人员,预计到 2019 年底这个数字将达到 2640万,到 2023 年达到 2770万。 而来自...

红薯
今天
61
0
Go 语言基础—— 通道(channel)

通过通信来共享内存(Java是通过共享内存来通信的) 定义 func service() string {time.Sleep(time.Millisecond * 50)return "Done"}func AsyncService() chan string {retCh := mak......

刘一草
今天
57
0
Apache Flink 零基础入门(一):基础概念解析

Apache Flink 的定义、架构及原理 Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速...

Vincent-Duan
今天
50
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部