自作クラスを自動でNSCoding対応にする便利な親クラスをruntimeを利用して作成する

自作クラスを自動でNSCoding対応にする便利な親クラスをruntimeを利用して作成する自作クラスをNSCoding対応にするのは面倒ですね。initWithCoder:encodeWithCoder:を書けばいいじゃんということではあるのですが、この繰り返し作業はできるだけ減らしたい。ということでいろいろな方法があると思うのですが、その中でruntimeを利用した親クラスを作成し、それを継承することで自作クラスをNSCoding対応にしちゃうという方法があったのでご紹介です。この方法、何度か脳裏をよぎりましたが、面倒に思って手を付けていませんでした。ただ、一度作っちゃえばあとは色々楽になりますね!

参考にしたのはこちらです。

この記事を通して学んだことをメモ的に書いていきます。詳細は参考サイトを御覧ください。

普通のやり方

ふむふむ、これですね。initWithCoder:encodeWithCoder:。面倒くさいです。おんなじようなことをプロパティの数の2倍書いていくんですね。これホント自動化できそうだと思いましたよね。Not very DRY.と記事に出てきます。DRYはDon’t repeat yourself.の略で、簡単に言うと重複はやめようねということです。ほんとにそうです。ミスの温床になります。。

1. KVCで自動化する

この記事は説明の流れに沿って実装が紹介されていてほんとに読者のことを考えたわかりやすい作りになってます。最初の1歩として、KVCを利用して自動化してみましょうということになります。

// In the initWithCoder: method
id value = [coder decodeObjectForKey:PropertyName];
[self setValue:value forKey:PropertyName];
 
// In the encodeWithCoder: method
id value = [self valueForKey:PropertyName];
[coder encodeObject:value forKey:PropertyName];

こんなかんじですね。プロパティ名を格納した配列を作成して、decodeとencodeの部分でその配列をループさせてやってしまおうということです。さらに、

KVC also has the neat feature that it can automatically box and unbox primitive values such as integers or booleans into their equivalent object type (e.g. NSNumber),

KVCあまり使わなかったので知らなかったです。IntegerやBooleanなどのプリミティブ型もオブジェクトにしてくれるんですね!これはいいな。

はい、プロパティ名を書き出すの面倒ですね。そこで。

2. runtimeを利用してプロパティ名の配列を自動出力する

#import <objc/runtime.h>を利用してプロパティ名の配列を取得する部分を作成します。これで、作成したクラスを継承することで、ただ単にプロパティを書くだけで自動的にNSCoding対応にすることができるようになりました。

これで終わり終わりかに見えたのですが、この記事更に続きます。上記の方法での問題点を書きだした上で、それを無効化する実装をしていきます。

問題点

  1. 継承関係に対応できない。上記のクラス(ここではCodableObject)を継承したAnimalがあって、それを継承したDogがあって、Dogをencodeすると、Animalに記述されたプロパティは保存されない。
  2. インスタンス変数のないreadonlyのプロパティはKVCでは対応できない(setできない。落ちる)。また、インスタンス変数があっても、プロパティ名と違っていれば同じくKVCで対応できない。
  3. structやpointerやそもそもNSCodingをサポートしていないクラスは対応できない。
  4. encodeしたくないプロパティを除外できていない。

3. その無効化

  1. NSObjectにぶち当たるまで親クラスまでさかのぼって、プロパティをencodeする。
  2. インスタンス変数のないプロパティを保存するのはナンセンス。_プロパティ名に対応できるようにする。
  3. そういうのはそのままプロパティに追加しないようにする。
  4. @synthesizeでfoo = foofoo = _fooはencode対象。encode対象にしたくない場合は、foo = barなど、インスタンス変数名を変えてしまうことでシンプルに対応する。

4. 更に、プロパティ名の配列をキャシュ化する

Associated Objectsを利用してプロパティをキャッシュ化する。そのままでも十分速いがキャッシュ化することで、更にムダを省くことができる。

僕のお試し記録

ExampleはCodableObjectを継承したクラス

ヘッダファイル

#import "CodableObject.h"

@interface Example : CodableObject
@property (copy, nonatomic) NSString *string;
@property (assign, nonatomic) NSUInteger integer;
@property (assign, nonatomic) CGFloat cgfloat;
@property (strong, nonatomic) NSArray *array;
@property (strong, nonatomic) NSDictionary *dictionary;
@property (strong, nonatomic) NSString *excludeExample;
@property (strong, nonatomic) UIColor *color;
@end

実装ファイル

#import "Example.h"

@implementation Example

@synthesize excludeExample = exclude;

@end

シンプルです。

実行コード

Example *example = [[Example alloc] init];
example.string = @"test";
example.integer = 111;
example.cgfloat = 45.6;
example.array = @[@"3",@"456", @"haichi"];
example.dictionary = @{@"someKey": @"someObject"};
example.excludeExample = @"needless";
example.color = [UIColor whiteColor];

NSData *data1 = [NSKeyedArchiver archivedDataWithRootObject:example];

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject:data1 forKey:@"exampleKey"];
[ud synchronize];

NSData *data2 = [ud objectForKey:@"exampleKey"];
Example *example2 = [NSKeyedUnarchiver unarchiveObjectWithData:data2];

// log
for (NSString *propertyName in [example2 propertyNames]) {
    NSLog(@"%@ >> %@",propertyName, [example valueForKey:propertyName]);
}

ログ

string >> test
integer >> 111
cgfloat >> 45.6
array >> (
    3,
    456,
    haichi
)
dictionary >> {
    someKey = someObject;
}
color >> UIDeviceWhiteColorSpace 1 1

いやー、ひと通り自分用にクラスを作ってみました。この記事読んで思ったのはDRYだなということ。色んな所でDRYの思想を徹底的にやっていけば、無駄なくミスの少ないクラス設計ができそう。まぁ、まだまだ上手く行かないことは続くのだろうが。。

今年の目標の一つに一年で365記事書くというものがあります。また一記事追加されました!この調子で気楽に、時にがんばっていこー。そう、最近、英語の記事を読むことが多いです。日本語の記事に比べてとても丁寧に書かれていることが多いから効率が良いのかなと思っています。「地球の歩き方」に対応する「LonelyPlanet」かななんて思ってます。もちろん、ざっと見ると日本語の記事のほうがわかりやすいのですが、英語記事のほうが深くしっかりという印象がやっぱりあります。

Pocket
LINEで送る

You may also like...