home link download back number special issue

HMDT - Special Issue / Sketch BP / Sketch に見る書類の保存


- 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: を使う、という手もある。


Home | Link | Download | Back Number | Speciall Issue

Sketch BP

mailto: mkino@xd5.so-net.ne.jp

HMDT