ラベル パターン の投稿を表示しています。 すべての投稿を表示
ラベル パターン の投稿を表示しています。 すべての投稿を表示

OCUnit で Notification をテストする

2011年8月19日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

通知(Notification)を配信(POST)するメソッドのテストコードを考える。

ポイントは次の2つ
(1) 意図したタイミングで通知が配信されたかどうか
(2) 配信された通知は意図したものだったか

たとえば addEntryWithInfo:tagName: というメソッドを呼び出すと LKQueueDidAddEntryNotification という通知が送られることをテストする場合を書いてみる。

テスト対象のメソッドの実装イメージはこんな感じ。
- (LKQueueEntry*)addEntryWithInfo:tagName:
{
     :
    [[NSNotificationCenter defaultCenter]
        postNotificationName:LKQueueDidAddEntryNotification
                     object:self];
}
処理の最後で LKQueueDidAddEntryNotification をポストしている。objectの引数に自身のインスタンスを渡している。これは通知を受け取った時に notification.object として参照できる。

次にこれをテストするコード。
- (void)testDidAddNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(_didAdd:)
                                                 name:LKQueueDidAddEntryNotification
                                               object:self.queue];

    self.calledNotificationName = nil;
    [self.queue addEntryWithInfo:@"NOTIFY-TEST-1" tagName:nil];
    STAssertEqualObjects(self.calledNotificationName, LKQueueDidAddEntryNotification, nil); // (1)
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
自身を通知対象として _didAdd: メソッドを登録する。その後、addEntryWithInfo:tagName: を呼び出す。これで通知 LKQueueDidAddEntryNotificationが配信される。(1) はその結果の確認。_didAdd: が正しく呼ばれたら (NSString*)self.calledNotificationName に通知名が入っているのでそれを確認する。これでポイントで挙げた「(1) 意図したタイミングで通知が配信されたかどうか」がテストできる。_didAdd: が呼ばれていないと self.calledNotificationNa は nil となるので STAssertEqualsObjects() が失敗する。

次に _didAdd: の実装。
- (void)_didAdd:(NSNotification*)notification

{
    STAssertEqualObjects(notification.name, LKQueueDidAddEntryNotification, nil);
    STAssertEquals(notification.object, self.queue, nil);
    self.calledNotificationName = LKQueueDidAddEntryNotification;
}
ここではポイントで挙げた「(2) 配信された通知は意図したものだったか」を確認している。もし違う通知が届いていたら最初の STAssertEqualObjects() が失敗する。

なお通知は NSRunLoop によって管理されている。この為、通知が配信(POST)された後、通知が届くにはランループが一巡してからになる。ということはテストコードで addEntryWithInfo:tagName: を呼び出した直後の self.calledNotificationName のテストは失敗するはず(_didAdd: が呼ばれていないから)。この為、一旦ランループを一巡させる為に下記を挿入する必要がある。
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
が、実際には不要だった。動作順番を見ていると testDidAddNotification内で addEntryWithInfo:tagName呼び出し -> post LKQueueDidAddEntryNotification -> _didAdd: -> testDidAddNotificationへ復帰、という動きだった。理由は不明だが OCUnit が通知のテストを考慮してそんな作りにしているのだろうか..


関連情報



iphone - OCUnit testing NSNotification delivery - Stack Overflow
addObserver: に NSMutableArray を渡している。POST時にここへ通知が追加されるようなことが書いてあるが初耳。リファレンスには書いてないようだが。。

[iOS][Mac] dispatch_once を使ったシングルトン

2011年5月9日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[元ネタ] Cocoa Samurai: Singletons: You're doing them wrong

GCD の dispatch_once 関数を使うとスレッドセーフなシングルトンの初期化処理をこんな感じで書ける。
static LKLocationManager* sharedManager_ = nil;
+ (LKLocationManager*)sharedManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedManager_ = [[LKLocationManager alloc] init];
    });
    return sharedManager_;
}
dispatch_once() の第2引数のブロックは1回のみ実行されることが保証される。

"man dispatch_once" で見ると "Well designed code hides the use of lazy initialization." と書いてあり、まさにシングルトンの初期化にうってつけの関数。
Xcode 4 では dispatch_once の Code Snipet が用意されていて dispatch_on... と入力するとプルダウンが出て選択すると雛形が展開される。
雛形はこんな感じ。

これは便利。
今後はこれを使っていこう。

サーバ上のファイルを見るには?

2011年1月12日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

頭の整理。

UIWebView に任せるのが一番簡単。
もう一つはダウンロードを自前でやる方法。
ビューをUIWebView以外に差し替えられる。自由度を考えるとこっちかな。

とっても簡単なアーキテクチャだけど図に書きだすと頭の中がすっきりした。

UIViewController - 状態保存復元パターン

2010年8月5日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

状態の保存と復元


他の UIViewController を呼び出したり、ピッカーを表示した時にメモリ不足が発生すると呼び出し元のビューが開放される。元の画面に戻った時に自動的にビューが再作成されるのだが、この時画面遷移前の状態を保存しておく必要のあるケースが多い。例えば、一覧画面で検索を行いその詳細画面を表示するケース。詳細画面から戻った時には直前の検索条件を保っておく必要がある。また UITableViewで編集モードに入っていて別画面で項目の編集を行った後に元の画面へ戻るケース。この場合も編集モードの状態で表示されるのが望ましい。ただ、これらの状態の多くはビューが保持している情報なので、ビューが破棄・再作成されると状態が消えてしまう。直前の状態を保つには、これらの状態を UIViewController 側で保存・復元する必要がある。


イベントの流れ


メモリ不足が発生する前に状態を保存し、再びビューが再作成されたタイミングで状態を復元した。これらをどのタイミング(イベント)でやるべきか。それを考える為に前回示した図を再掲する。

MyViewController の画面が消えた後の didDisappear: で状態を保存し、再びビューが作成された後の viewDidLoad で復元するのが良さそうだ。まとめるとこう:
イベント操作
viewWillDisappear:状態の保存
viewDidLoad状態の復元

実際 Appleが提供する次のサンプルでは次のタイミングで保存と復元を行っていた。
TableSearch

このサンプルでは検索条件の保存と復元を行っている。

[参考情報] Cocoaの日々: UISearchDisplayController 調査

コードを引用してみる。まずは保存から。
- (void)viewDidDisappear:(BOOL)animated
{
    // save the state of the search UI so that it can be
    //  restored if the view is re-created
    self.searchWasActive = [self.searchDisplayController isActive];
    self.savedSearchTerm = [self.searchDisplayController.searchBar text];
    self.savedScopeButtonIndex = [self.searchDisplayController.searchBar
         selectedScopeButtonIndex];
}

次に復元。
- (void)viewDidLoad
{
     :
     :
    // restore search settings if they were saved in didReceiveMemoryWarning.
    if (self.savedSearchTerm)
    {
        [self.searchDisplayController setActive:self.searchWasActive];
        [self.searchDisplayController.searchBar
                setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
        [self.searchDisplayController.searchBar setText:savedSearchTerm];
        
        self.savedSearchTerm = nil;
    }
     :    
     :
}

viewDidDisappear で検索中であることを表すフラグや検索キーワード、スコープなどを保存し、viewDidLoad でそれらを復元している。なお viewDidLoad は一番最初の初期処理でも呼び出されるので、このコードでは self.savedSearchTerm をチェックして必要な時だけ復元を行うようにしている。

他に保存が必要なものとしては UITableView.editing などが考えられる。


動作確認の方法


別の画面を表示している時に iPhoneシミュレータの「メモリ警告をシミュレート」を使うと良い。

[参考] Cocoaの日々: 「メモリ警告をシミュレート」で didReceiveMemoryWarning を擬似的に発生

これを使うとメモリ不足がシミュレートされ、元の画面で didReceiveMemoryWarning、viewDidUnload と呼び出される。その後、元の画面へ戻ると viewDidLoad、viewWillAppear と呼び出される。

人気の投稿(過去 30日間)