Cocoa
Swift
cocoaswift
Cocoa練習帳

[cocoa][swift]マイDocumentクラス

一月の勉強会でMVCについてディスカッションすることになった。そこで場が盛り上がるよう、ネタとして自分がよく採用するマイDocumentクラスに発表する。

アプリケーションの設計法としてMVCが話題となることが多いが、それは、MVCはデザイン・パターンが話題になる以前のもので、今のデザイン・パターンから見ると複数のパターンが組み合わさった大きな枠組みのものだというのも理由としてあるのかな?

早速本題に入る。Appleの文書で説明されている伝統的なMVCは以下のとおり。

Traditional version of MVC as a compound pattern.png

これは、この論文でも説明されている。

そして、Cocoa版は以下となる。

Cocoa version of MVC as a compound design pattern.png

ViewとModelは直接やりとりしない。間にControllerを挟んでいるのが特徴だ。このControllerだが、一種類でない。NSApplicationDelegateだったりNSDOcumentだったりNSControllerだったりNSViewControllerだったり、アプリケーション独自のクラスだったりする。

iOS開発を始めたときから独自のDocumentクラスを用意するようにしている。

@interface Document : NSObject

@property (strong, nonatomic) NSString *version;

+ (Document *)sharedDocument;
- (void)load;
- (void)save;
@end

@interface Document ()

- (void)_clearDefaults;
- (void)_updateDefaults;
- (void)_loadDefaults;
- (NSString *)_modelDir;
- (NSString *)_modelPath;
@end

@implementation Document

@synthesize version = _version;

+ (Document *)sharedDocument;
{
static Document *_sharedInstance = nil;
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
_sharedInstance = [[Document alloc] init];
});
return _sharedInstance;
}

- (id)init
{
self = [super init];
if (self) {
_version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
}
return self;
}

- (void)dealloc
{
self.version = nil;
}

- (void)load
{
[self _loadDefaults];

NSString *modelPath = [self _modelPath];
if ((! modelPath) || (! [[NSFileManager defaultManager] fileExistsAtPath:modelPath])) {
return;
}
}

- (void)save
{
[self _updateDefaults];

NSFileManager *fileManager = [NSFileManager defaultManager];

NSString *modelDir = [self _modelDir];
if (![fileManager fileExistsAtPath:modelDir]) {
NSError *error = nil;
[fileManager createDirectoryAtPath:modelDir
withIntermediateDirectories:YES
attributes:nil
error:&error];
}

NSString *modelPath = [self _modelPath];
[NSKeyedArchiver archiveRootObject:self.indexArray toFile:modelPath];
}

- (void)_clearDefaults
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"version"];
}
}

- (void)_updateDefaults
{
NSString *versionString = nil;
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
[[NSUserDefaults standardUserDefaults] setObject:self.version forKey:@"version"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}

- (void)_loadDefaults
{
NSString *versionString = nil;
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"version"]) {
versionString = [[NSUserDefaults standardUserDefaults] objectForKey:@"version"];
}
if ((versionString == nil) || ([versionString compare:self.version] != NSOrderedSame)) {
/* バージョン不一致対応 */
}
else {
/* 読み出し */
}
}

- (NSString *)_modelDir
{
NSArray *paths;
NSString *path;
paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if ([paths count] < 1) {
return nil;
}
path = [paths objectAtIndex:0];

path = [path stringByAppendingPathComponent:@".model"];
return path;
}

- (NSString *)_modelPath
{
NSString *path;
path = [[self _modelDir] stringByAppendingPathComponent:@"model.dat"];
return path;
}
@end

macOSのDocument-Based. Applicationsについて説明する。

主要なクラスは、以下の3つ。

クラス
内容

NSDocument
データ管理。

NSWindowController
ウィンドウ管理。

NSDocumentController
ドキュメント管理。

ファイルとDocument、Modelの関係を図だ。

Document.png

macOSのDocument-Based Applicationのクラスの関係を図示する。

Document-Based Apps.png

Key objects in a document-based app.png

【関連情報】

Cocoa Advent Calendar 2018


Cocoa.swift 2019-01


Cocoa.swift


Cocoa勉強会 関東


Cocoa練習帳


Qiita