- Case Study -
Sketch に見る書類の保存


■ NSDocument で書類を保存する
Cocoa の NSDocument を使ってドキュメント・ベース・アプリケーションを作ると、書類を保存するメソッドが標準で提供されるんだ。次の 3 つのメソッドのどれかを実装すればいいことになっている。
|
AppKit/NSDocument.h
|
- (NSData *)dataRepresentationOfType:(NSString *)type;
- (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type;
- (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)type;
|
引数の type に保存すべき書類のタイプが渡ってくるから、それに応じて必要な処理をすることになる。Sketch では、一番最初の dataReprecentationOfType: を使って実装しているんだ。
■ バイト列の作成に NSDictionary を使う
NSDictionary には descrption っていうメソッドがあって、これを使うと NSDictionary の中身をテキストで書き出すことができるんだ。
|
Foundation/NSDictionary.h
|
- (NSString *)description;
|
Sketch では、これを使って書類を保存している。保存するデータを、ぜんぶ NSDictionary の中に詰め込んで、description を呼んでテキストにして、はいおしまい、ってなわけだ。
■ Sketch に見る書類の保存

Sketch で書類の保存に使われるクラスは、NSDocument を継承した SKTDrawDocument だ、もちろん。
◆ 書類に保存するもの
さて、保存、保存とはいっても、いったい何を保存すればいいんだ?それを考えてみよう。
まず挙げられるのは、ドローオブジェクト。当たり前だね、Sketch はドローアプリケーションだから。画面に描かれたすべてのドローオブジェクトを保存してやる。他にバージョン情報とかも欲しいね。この書類が Sketch のどのバージョンで作られたか?っていう情報。それと、プリントするときの情報もあった方がいいかな。
と、いうわけでこの 3 つの情報を保存している。
この 3 つを詰め込んだ NSDictionary を用意してやる必要がある、ってことになる。
書類の構造を図で表すと、こんな感じかな。

◆ 保存の流れ
SKTDrawDocument では、保存するのに dataRepresentationOfType: を実装している。ここを起点として、保存の処理が行われるんだ。
流れはこうだ。
- dataRepresentationOfType: は、引数として書類のタイプを取るんで、それをチェックする
- もし Sketch format だったら、保存する情報を詰め込んだ NSDictionary を作ってやる
- そのために、まずドローオブジェクトの NSDictionary を作る
- それと、バージョン情報と、プリント情報を詰め込んだ NSDictionary を作って、description を取得する
で、完了だ。
◆ 保存するフォーマットをチェックする(dataReprensentationOfType:)
じゃ、順々に見ていこう。まずはエントリである dataRepresentationOfType: から。このメソッドは引数にフォーマットのタイプを取って、返り値として書類に保存するデータを返す。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (NSData *)dataRepresentationOfType:(NSString *)type {
if ([type isEqualToString:SKTDrawDocumentType]) {
return [self drawDocumentDataForGraphics:[self graphics]];
} else if ([type isEqualToString:NSTIFFPboardType]) {
return [self TIFFRepresentationForGraphics:[self graphics]];
} else if ([type isEqualToString:NSPDFPboardType]) {
return [self PDFRepresentationForGraphics:[self graphics]];
} else {
return nil;
}
}
|
ここではタイプに Apple sketch format type を取る場合を追いかけてみよう。引数 type が SKTDrawDocumentType であった場合は drawDocumentDataForGraphics: を呼び出すんだ。引数として、SKTGraphic が詰まった NSArray を渡している。
◆ 保存するデータを作成する(drawDocumentDataForGraphics:, drawDocumentDictionaryForGraphics:)
メソッド drawDocumentDataForGraphics: は、保存するデータを作っているんだ。データを詰め込んだ NSDictionary を作って、その description を返す。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (NSData *)drawDocumentDataForGraphics:(NSArray *)graphics {
NSDictionary *doc =
[self drawDocumentDictionaryForGraphics:graphics];
NSString *string = [doc description];
return [string dataUsingEncoding:NSASCIIStringEncoding];
}
|
保存するデータを詰め込んだ NSDictionary を作るのが、drawDocumentDictionaryForGraphics: だ。このメソッドは、引数に SKTGraphic を詰め込んだ NSArray を取る。
|
Sketch/DocumentModesl.subprj/SKTDrawDocument.m
|
- (NSDictionary *)drawDocumentDictionaryForGraphics:(NSArray *)graphics {
NSMutableDictionary *doc = [NSMutableDictionary dictionary];
unsigned i, c = [graphics count];
NSMutableArray *graphicDicts = [NSMutableArray arrayWithCapacity:c];
for (i=0; i<c; i++) {
[graphicDicts addObject:
[[graphics objectAtIndex:i] propertyListRepresentation]];
}
[doc setObject:graphicDicts forKey:SKTGraphicsListKey];
[doc setObject:[NSString stringWithFormat:@"%d",
SKTCurrentDrawDocumentVersion] forKey:SKTDrawDocumentVersionKey];
[doc setObject:[NSArchiver archivedDataWithRootObject:
[self printInfo]] forKey:SKTPrintInfoKey];
return doc;
}
|
NSMutableDictionary である doc を作って、そこに SKTGraphic の配列 graphicDicts、ドキュメントのバージョン、プリントの情報を詰め込んでいるんだ。
ただし、NSDictionary で description を適用するには、SKTGraphic のままではだめで、NSString や NSData にしてやらないといけない。そのために SKTGraphic の propertyListRepresentation を呼んでやっている。
◆ SKTGraphic の proerty list 表現を取得する(propertyListRepresentation)
propertyListRepresentation は、SKTGraphic クラスのメソッドだ。こいつは SKTGraphic を保存するのに必要な情報を NSDictionary の形で返してくれるんだ。
|
Sketch/DocumentModesl.subprj/SKTGraphic.m
|
- (NSMutableDictionary *)propertyListRepresentation {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSString *className = NSStringFromClass([self class]);
NSRange sktRange =
[className rangeOfString:@"SKT" options:NSAnchoredSearch];
// Strip SKT prefix to preserve document capatibility with
// old versions of Sketch.
if (sktRange.location != NSNotFound) {
className = [className substringFromIndex:NSMaxRange(sktRange)];
}
[dict setObject:className forKey:SKTClassKey];
[dict setObject:NSStringFromRect([self bounds]) forKey:SKTBoundsKey];
[dict setObject:
([self drawsFill] ? @"YES" : @"NO") forKey:SKTDrawsFillKey];
if ([self fillColor]) {
[dict setObject:
[NSArchiver archivedDataWithRootObject:[self fillColor]]
forKey:SKTFillColorKey];
}
[dict setObject:
([self drawsStroke] ? @"YES" : @"NO") forKey:SKTDrawsStrokeKey];
if ([self strokeColor]) {
[dict setObject:
[NSArchiver archivedDataWithRootObject:[self strokeColor]]
forKey:SKTStrokeColorKey];
}
[dict setObject:
[NSString stringWithFormat:@"%.2f", [self strokeLineWidth]]
forKey:SKTStrokeLineWidthKey];
return dict;
}
|
NSMutableDictionary である dict を作って、そこに情報を詰め込む。詰め込む情報は、
- クラスの名前(SKTClassKey)
- オブジェクトの境界(SKTBoundsKey)
- オブエジェクトを塗りつぶすか否か(SKTDrwasFillKey)
- 塗りつぶすときの色(SKTFillColorKey)
- オブエジェクトの枠を描くかいなか(SKTDrawsStrokeKey)
- 枠の色(SKTStrokeColorKey)
- 枠の線の幅(SKTStrokeLineWidthKey)
だ。インスタンス変数をすべて保存するわけではなくて、必要なものだけ抜き出しているってわけだ。
■ 違う手段による書類の保存
Sketch では、NSDictionary にいれて proeprty list 表現にする、という手段を使っているけど、別の方法もある。それは encodWithCoder: を使う方法だ。
|
Foundation/NSObject.h
|
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
...
@end
|
これを実装して、NSArchiver の archivedDataWithRootObject: を使う、という手もある。
|