2010年9月12日日曜日

iPhone 開発規約まとめ

あんまり iOS 上での開発規約とか見かけないので、試しに私が今個人/会社で使っている開発規約を公開してみることにしました。


■設計

設計は所謂 MVC と呼ばれる設計モデルを採用します。ただし、厳密な MVC というわけではなく、以下のような区分になっています。
  • Model
    Core Data を使用します。通常 MVC での Model というと業務ロジック等を含めた業務モデル一般すべてを含むのですが、私の場合は特に Core Data の NSManagedObject を Model として扱い、 Model 単体のみで完結するロジックのみを Model に記述します。たとえば、
    • Core Data から対象の Model とその関連 Model 取得
    • Model の新規作成
    • 新規作成時、更新時に自動的に Model のプロパティを更新する
    • Model のプロパティの値を元に幾何学計算をしたり、一時的に使うキャッシュを用意したりする
    などです。実際のコードのサンプル (ニュースサイトのニュースを表す News モデルの実装) はこんな感じになります:
    #import "News.h"

    @implementation News

    @dynamic body;
    @dynamic title;
    @dynamic imageURL;
    @dynamic objectId;
    @dynamic dateCreated;
    @dynamic dateUpdated;
    @dynamic sortOrder;

    @end

    //----------------------------------------------------------------------------------------
    // ここから上は .xcdatamodel ファイルから自動生成されたコードなので触れないようにします。
    //----------------------------------------------------------------------------------------

    #import "AppDelegate.h"

    @implementation News (NSManagedObject)
    - (void)awakeFromInsert {
    [super awakeFromInsert];
    // Insert
    self.dateCreated = [NSDate date];
    }
    - (void)willSave {
    [super willSave];
    // Update
    // DO NOT DO THIS in here because updating self.dateUpdated will call -willSave method recursively until this application crashes!
    // Use NSManagedObjectContextWillSaveNotification / NSManagedObjectContextObjectsDidChangeNotification and watch the time delta of previous change
    // If the time delta is small enough to ignore, do not update dateUpdated property to avoid infinite loop
    //self.dateUpdated = [NSDate date];
    }
    - (void)awakeFromFetch {
    [super awakeFromFetch];
    // Select
    }
    @end

    @implementation News (DBAccessors)
    + (NSArray *)all {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"allNews"];

    // Sort by sortOrder, ASC
    NSArray *sortDescriptors = [NSArray arrayWithObjects:
    [[[NSSortDescriptor alloc] initWithKey:@"sortOrder" ascending:YES] autorelease],
    nil];
    [request setSortDescriptors:sortDescriptors];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return nil;
    }
    return resultArray;
    }
    + (id)get:(NSString *)targetId {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSDictionary *substitutionVariables = [NSDictionary dictionaryWithObject:targetId
    forKey:@"objectId"];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestFromTemplateWithName:@"getNews"
    substitutionVariables:substitutionVariables];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return nil;
    }
    return [resultArray lastObject];
    }
    + (void)deleteAll {
    AppDelegate *appDelegate = [AppDelegate appDelegate];
    NSFetchRequest *request = [appDelegate.managedObjectModel fetchRequestTemplateForName:@"allNews"];

    NSError *error = nil;
    NSArray *resultArray = nil;
    if (!(resultArray = [appDelegate.managedObjectContext executeFetchRequest:request error:&error])) {
    // handle the error;
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    return;
    }

    // Delete all objects fetched
    for (NSManagedObject *obj in resultArray) {
    [appDelegate.managedObjectContext deleteObject:obj];
    }
    }
    @end
  • Operation
    本来の MVC モデルには存在しませんが、私は特別に Operation という区分をもうけています。定義は以下の通り。
    • 一つの業務モデル、業務ロジックを完結させるひとまとまりのロジックであること。要するに本来の MVC モデルの業務ロジック的要素であること。 もっと言うなら Operation だけを切り出して単体テストできること。
    • それなりに重要でかつ量があり、 Controller や Model から共有的に呼びだされること。
    • 非同期で処理ができること。
    • NSOperation クラスのサブクラスにすること。
    たとえばインターネット経由で API を実行して結果を Model に突っ込む処理などは Operation として実装しています。 NSOperation として実装することで、非同期処理が簡単にできるようになるだけではなく、 タスクの依存関係を決めて先に親タスクが実行されるのを待つようにしたり、現在の状態を厳密に判定したり、タスクをキャンセルしたりするのが容易になります。また、将来的に iPhone / iPad のCPUがマルチコアになった場合、 NSOperation を継承しておけばほとんど何もしなくてもその恩恵を受けることができるはずです。
  • View
    ビューです。主に UIKit および Three20 を用いて構築し、必要に応じてその他のビューライブラリを組み込みます。個人的には UIView のサブクラス一般をすべて View として扱っています。 View には描画ロジックと幾何学計算などの補助ロジック、およびそれに必要な最小限のデータのみを持たせるようにします。状態を持ってもかまいませんが、状態のコントロールを View 自身が行うのはできる限り避けます。タッチイベントのハンドリングは必要に応じて View で touchesBegin: などを実装して行いますが、基本的には Controller 側で UIGestureRecognizer を用いて行います。
  • Controller
    コントローラーです。私の中での定義は UIViewController のサブクラスです。アプリの内容に応じて UIKit と Three20 を使い分けます。 本来 Controller は全体の流れのみを管理し実際の処理を行ってはならないということになっていますが、 UIViewController の性質を鑑みて、私は次のように Controller の役割を定義しています。
    • View の管理。
    • 画面遷移, 要するに Controller 間の遷移の管理。
    • タッチイベント、ジェスチャイベント、加速度計などからの入力の受付と処理。
    • 各種 UI コンポーネントの Delegate 処理。 UITextView, UIPopoverController, UIActionSheet, などなどなど多岐にわたります。
    • Operation 完了時の処理など、各種 Notification を受け付けて処理。
    • 上記に必要になるデータ/メソッドの定義。 View に持たせるぐらいならこちらに持たせる。他に持たせるところがないなら Controller にすべて持たせる。
    ということで、 Controller を名乗っていますが実際にはかなり自分で処理をします。こんなのいちいち分けていたら大変ですし・・・といういいわけです。実際のコードサンプルはお見せできないのですが、実装の概要はこんな感じです



  • Application Delegate
    これも本来の MVC モデルには存在せず、本来は Controller として扱うべき物だと思いますが、私は特別に分けています。 Application Delegate は所謂アプリケーショングローバルなデータや設定、オブジェクト、ユーティリティメソッドを管理するものとして扱います。たとえば、
    • Operation 駆動用の NSOperationQueue
    • Model のための NSManagedObjectContext / NSManagedObjectModel / NSPersistentCoordinator
    • 現在通信可能か否かを判定するためのユーティリティメソッド
    • アプリケーショングローバルな Notification を受け取って Application Delegate のプロパティとしてセットする
    などです。これらはほぼすべてのアプリで絶対に必要になるのでテンプレ化しています。


■Xcodeプロジェクト

プロジェクトのグループはこんな感じで分けます。



ビルド設定については、プロジェクト全体のビルド設定は可能な限り使わないようにして、ターゲットごとのビルド設定 ( Cmd+Option+E ) を主に使います。ターゲットが例え複数に分かれても常に同じ物を使うところはプロジェクト全体の設定を変更します。


■命名規則

大体以下のような方針でやっています。
  • Foundation や UIKit など Apple の使っている命名規則に似せること。似てないシグネチャは即リファクタリング対象。
  • ミススペル、Typo、意味のわからない英単語 ( registとか ) は一文字一単語であろうとも発見した瞬間即リファクタリング対象。英語辞書のソースは http://www.alc.co.jp/ とする。
  • 名前はどれだけ長くなろうとも絶対に省略しないこと。 updUniqUsr とか発見したら即リファクタリング対象。逆に BPSuperDuperViewControllerDidFinishedUberTaskNotification とかは表彰モノ。
    • これを BPSDVCDFUTN とか略した奴が現れたら即 SATSUGAI モノです


■まとめ

ここまで書いておいて何ですが、要するに 一貫した基準があるなら 自分らの好きなようにするのが一番いい のかなと思います。