iOSアプリを作ってみる(4) : シングルトンのClassを作る

またしても適当に書いた図からスタートです。前回はデータベースにアクセスしてデータを拾ってくる部分について書きました。今回は、拾ってきたデータを加工する部分の話です。

f:id:deutschina:20130905134522j:plain

図以上に適当に書いた赤い線で囲われている部分です。一口にデータを加工すると言うけれども、実際にはもう少し大きな話で、データベースで拾ってきたデータを表示させて、さらにユーザのアクションを拾った結果の処理についてもカバーする必要があります。内容によってはデータベースに何かを書き出すことになります。

ある意味このアプリの心臓部とでも言える部分です。

具体的にどんなことをやるか

改めて頭の中でイメージしている要件を整理してみました。

  1. 出題する単語の一覧を作る。
  2. 出題にあたって、単語の詳細データを拾ってくる
  3. ユーザが解答するための正解以外の選択肢を生成する
  4. 出題内容をUIに渡す
  5. 正解・不正解の判定の結果をステータスとして更新する
  6. 現在何問目かをカウント
  7. 何問回答したか、何問正解したか、不正解だったかの集計

ざっと考えただけでも、このぐらいはありそうです。ある意味、このアプリの心臓部と言っても良いかも知れません。

ここには、もう1つ重要な考え方があります。例えば、上の要件のうち1, 7, 8については、アプリが起動している間は常にデータを保持して参照可能な状態でないといけません。間違って消去されたり、別途インスタンス化されて、そちらが参照されるようなことがあると、データの整合性に問題が出る可能性があります。

となると、アプリを起動した早い段階で1つだけインスタンス化しておいて、常に参照、またはプロパティが変更可能な状態にないといけません。いろいろ調べたところ「シングルトン」という考え方がメジャーのようだと分かりました。

シングルトンって?

ggrks --> http://ja.wikipedia.org/wiki/Singleton_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

はい、お約束なので気にしないでください。iOSにおけるObjective-Cの場合もサンプルが結構転がっています。それを拝借して、こんなコードを用意してみました。

#import "KPTAppSetting.h"
#import "KPTWIDList.h"
#import "KPTDummyOptions.h"

@interface KPTManageGame : NSObject {
}

@property (retain, nonatomic) NSString *gameMode;
@property (assign, nonatomic) NSInteger numOfWords;
@property (retain, nonatomic) NSArray *wordIDList;
@property (retain, nonatomic) NSMutableArray *quizOptions;
@property (assign, nonatomic) NSInteger rightOption;
@property (assign, nonatomic) NSInteger chosenOption;
@property (assign, nonatomic) NSInteger currentNumber;
@property (retain, nonatomic) KPTDBAccess *dbAccess;
@property (retain, nonatomic) KPTWordDetail *wordDetail;

// For Singleton purpose
+(KPTManageGame*) sharedInstance;
- (id)initSharedInstance;

// For operations
-(void)generateWIDList;
-(void)prepareQuiz:(NSInteger)qNum;
-(NSString *)generateDOption:(NSString *)eType :(NSString *)corAns :(NSString *)wrongAns :(NSInteger)pos :(NSString *)fullPin;
-(void)updateStats:(NSInteger)wordID :(NSString *)cStatus;

//for debugging only
-(void)loggingFinalCheck:(KPTWordDetail *)wordDetail :(BOOL)fullLog;

@end

他の細かい話は別途記事を書くとして、今回はシングルトンに集中しましょう。コメント行に"For Singleton purpose"となっているところに、2つのメソッドが宣言されています。最初のはクラスメソッド(+)でsharedInstance、もう1つはインスタンスメソッド(-)でinitSharedInstanceとあります。

実装部分を見た方が早いでしょう。おそらく。ということで、そのまま実装部分です。

....
// For Singleton purpose
+ (KPTManageGame*)sharedInstance {
    static KPTManageGame* sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
		sharedInstance = [[KPTManageGame alloc] initSharedInstance];
    });
    return sharedInstance;
}

- (id)initSharedInstance {
    self = [super init];
    if (self) {
	    dbAccess = [[KPTDBAccess alloc] init];
    }
    return self;
}

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

メソッドsharedInstanceは、インスタンスを呼び出すためのものです。例えば、(まだ作っていないことになっていますがw)ViewControllerなどで、こんな感じで呼び出してやります。

....
- (void)viewDidAppear:(BOOL)animated
{
    KPTManageGame *manageGame = [KPTManageGame sharedInstance];
....

まだインスタンス化されていない場合は、初期化専用メソッドinitSharedInstanceを使って初期化を行い、すでにインスタンス化されているモノがある場合は、それをそのまま返して再利用します。最後にinitというメソッドがありますが、これは間違って普通の方法(initを呼ぶ)で初期化を使用とした場合のトラップです。

これをコーディングする側から見ると、このクラスのプロパティやメソッドが必要なときに上の文を書いておけば、新規にインスタンス化するか既存のモノを参照するかはシステムが勝手に判断してくれるので楽ですね。

次は、Storyboardを使って画面(UI)を作る話をしようと思います。

(つづく)