iOSアプリのモデル周りの構造についてのコードリーディングをしてみた

iOSアプリのモデル周りの構造についてのコードリーディングをしてみたiOSアプリのモデル周りの構造がいつもしっくりこないので、先日、目にして気になっていたスライドから「設計のコツ」の部分をやってみることにしました。「iOS開発におけるパターンによるオートマティズム」の復習もしたいところですが、今回はまだやったことのない、「novi/LTAPIRequest」に関してコードリーディングをしてみたいと思います。

参考にしたスライド

早速開始

スライドの中で紹介されている、「novi/LTAPIRequest」をcloneしました。気づいたことをメモのように書いていきます。

The Arch Way

こんな言葉が出てきました。調べてみると、「KISS (Keep It Simple, Stupid; シンプルにしとけよ、このバカチンが) という言葉に集約」される思想らしいです。

「オープンであることは、シンプルであることと切り離して考えることはできず、また、Arch Linux 開発の基本理念の一つでもあります。」という言葉が説明の中に出てきましたが、たしかに、Githubで公開するとシンプルさを意識するという力が働きますねぇ。

デザインパターンについて

構成について

同期型ではなくあくまでもクライアント型として実装した場合の例。

Request,Response,Modelの親クラスを使って、実際に利用する際はそのサブクラスを利用する。

Request(LTAPIRequest <- DEAPIRequest)

- (id)initWithAPI:(NSString*)path method:(LTAPIRequestMethod)method params:(NSDictionary*)dict;
@property (nonatomic, readonly, copy) NSString* path;
@property (nonatomic, readonly) LTAPIRequestMethod method;
@property (nonatomic, readonly, copy) NSString* methodString; // LTAPIRequestMethodGET -> "GET"... 
@property (nonatomic, readonly, copy) NSDictionary* params;
@property (nonatomic, readonly, weak) LTAPIResponse* response;

イニシャライザはよくある形。プロパティがreadonlyでいい感じ。

- (void)sendRequestWithCallback:(LTAPIRequestCallback)callback; // APIのリクエストを送信
+ (Class)APIResponseClass; // APIResponseのクラス
- (NSURLRequest*)prepareRequest; // 実際に送信するRequestを作成

リクエストを送信する部分。サブクラスでオーバーライドして利用する。APIResponseClassはサブクラスが利用したいResponseのサブクラスを返す。prepareRequestはサブクラスが実際に利用するNSURLRequestを作成して返す。親クラスはAPIResponseClassとprepareRequestを利用して通信を行う。

子はCallbackなどをオーバーライドして利用している。

内部では、NSOperationQueueをdispatch_onceで作成して、そこにaddOperationWithBlock:で一つ一つの通信を突っ込んでいく。用途に応じてNSOperationQueueを複数(この例では通常とイメージの2つ)作成している。

親クラスがstatic int networkCountを持っている。これで同時通信数を管理して、networkActivityIndicatorVisibleを正しく出し入れする。

Response(LTAPIResponse <- DEAPIResponse)

- (void)showErrorAlertというメソッドがある。エラーはResponseにコールすると呼ばれる仕組み。

- (NSError*)parseJSON __attribute__((objc_requires_super));はエラーなければnilを返す。

Model(LTModel <- DESomeModel)

- (id)attributeForKey:(NSString*)key;
- (void)setAttribute:(id)attr forKey:(NSString*)key;
- (void)replaceAttributesFromDictionary:(NSDictionary*)dict;
- (void)mergeAttributesFromDictionary:(NSDictionary*)dict;
- (void)removeAttributeForKey:(NSString*)key;
- (void)removeAllAttributes;
- (NSDictionary*)attributes;

+ (NSString*)IDKey;

親にこれらのメソッドが定義されていて、実装部分にNSMutableDictionaryを持っている。子は内部的にこれらのメソッドを利用して、プロパティ経由で利用しやすい形で処理をする。

通信が必要なModelは内部的に通信処理をする。その際に、上記RequestとResponseを利用している。値の返却はBlocksで処理している。

リテイン・サイクルに陥らないように、弱参照を正しく利用している(例: Tweetモデルに関連するUserやTimelineはTweetにとって弱参照)。

ViewControllers

必要なタイミングで通信処理をする。モデルに関するものはモデルに依頼。画像のダウンロードなどは直接上記Requestのメソッドを利用している。

その他の気付き

+(ACAccountStore *)accountStore
{
    static ACAccountStore* store;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        store = [[ACAccountStore alloc] init];
    });
    return store;
}
- (NSDictionary*)methodStringDictionary
{
    static NSDictionary* methods;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        methods = @{@(LTAPIRequestMethodPUT): @"PUT", @(LTAPIRequestMethodGET): @"GET",
                                  @(LTAPIRequestMethodPOST): @"POST", @(LTAPIRequestMethodDELETE): @"DELETE"};
    });
    return methods;
}

こういうパターンは使っていこうと思う。他にNSDateFormatterなどで利用できそう。

-(BOOL)success
{
    [[NSException exceptionWithName:NSGenericException reason:@"success: should be implemented on subclass" userInfo:nil] raise];
    return NO;
}

オーバーライドしない場合は例外を出す。

-(id)init
{
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

標準メソッドは無効にする。

参考

Pocket
LINEで送る

You may also like...