博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
有关宏定义的一篇文章
阅读量:6212 次
发布时间:2019-06-21

本文共 7359 字,大约阅读时间需要 24 分钟。

hot3.png

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) #xint 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

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)

转载于:https://my.oschina.net/u/1782374/blog/377288

你可能感兴趣的文章
SQL中的cast()函数介绍
查看>>
JavaScript—内置对象
查看>>
神经网络和深度学习-第二周神经网络基础-第二节:Logistic回归
查看>>
Myeclipse代码提示及如何设置自动提示
查看>>
FTP
查看>>
配置OSPF发布聚合路由
查看>>
NAT/NAPT
查看>>
迭代器 -> 固定的思路. for循环
查看>>
db2 cpu使用率高问题分析处理
查看>>
pku1944 Fiber Communications
查看>>
Play Framework 应用创建、运行及调试
查看>>
项目案例分享-华为私有云(分享1)
查看>>
Oracle数据库shutdown immediate被hang住的几个原因
查看>>
jquery实现增删改(伪)-老男孩作业day13
查看>>
RMQ 问题及解决算法
查看>>
NFS存储服务器的部署流程
查看>>
SQL函数大全
查看>>
Django中@login_required用法简介
查看>>
python 装饰器
查看>>
Shell运算符:Shell算数运算符、关系运算符、布尔运算符、字符串运算符等
查看>>