2010-12-25

進捗状況を表示する (MacBloggerGlass)

フィードをネットから取得する際には、ブログの記事数にもよるが、やはり多少の時間(数秒)がかかる。その間、ユーザにフィードバックがないのはユーザ体験上の問題だ。GAE 版ではブラウザが矢印を回したり、アドレスバーを塗り潰したりして「ネットにアクセスしてますよ」と主張してくれるが、Cocoa 版ではこれも自前でどうにかしなければならない。

フィードバックの方法

今回は、進捗状況をフィードバックする仕組みとしては、最も手軽な部類の「ステータスメッセージの表示」を作り込むことにした。

メッセージを表示する場所は、Safari などでもお馴染のアプリウィンドウの最下部。ここにテキスト 1 行分の領域を空け、ラベル (NSTextField) を配置する。あとは、アプリコントローラで進捗に合わせてメッセージを setStringValue: してやれば良い。

問題は進捗状況をどうやって知るのか。実際のフィードの取得は GData ライブラリの中でアプリの実行ループとは並列に実行されている。スレッドなのか、あるいは他の仕組みなのかまではまだ調べていないが、便宜上、別タスクと呼んでおく。このフィード取得用の別タスクが、アプリに対してコールバックを呼ぶなどして途中の状況を知らせてくれれば簡単なのだが、そういう仕組みは GData ライブラリには用意されていない。データをアップロードする場合には、そういうものがあるが、取得(ダウンロード)時にはないようだ。

しばらく悩んだ後、何も難しく考えることはないと気付いた。フィードの取得が完了したことは GData ライブラリからのコールバックでわかる。ならば、取得を開始するときにタイマで定期的に進捗していることを知らせるメッセージを表示させ、完了したらそれを止めれば良い。「ネットにアクセスしているよ」を知らせるだけなら、これで十分だ。

タイマを使った遅延実行

NSObject には、指定した時間間隔の後、オブジェクトに対してメッセージを送る(つまり、そのオブジェクトでメソッドを実行する)仕組みが存在する。それが以下のメソッドだ。

- (void)performSelector:(SEL)aSelector
             withObject:(id)anArgument
             afterDelay:(NSTimeInterval)delay;

delay には遅延させる時間を秒単位で指定するが、NSTimeInterval は実は double 型なので 1.0 や 10.0 のように実数(というか浮動小数)で指定する。

他にもときどき見かけるけど、基本型を typedef しただけの型っていうのは何か落ち着かない。クラスにしてファクトリなりイニシャライザを用意して欲しいよ。即値をどう指定したものか迷うから。NSTimeInterval なら ファクトリに intervalWithSeconds:(NSUInteger)sec とかがあれば迷わずにすむ。書くのは少しメンドウだけど。

遅延実行を使った進捗メッセージの表示

  1. フィードの取得開始時にメッセージとして「Loading.」を表示
  2. 一定時間後にメッセージを更新するようにタイマをセット
  3. メッセージの更新では、「.」を追加したメッセージを表示するとともに、再び一定時間が経過したらメッセージの更新するようにタイマをセット
  4. 取得が完了したら、メッセージの更新を停止した上で、「(Complete)」を追加したメッセージを表示
  5. ある程度時間が経過したら、メッセージを消す。

つまり、最初に「Loading.」と表示され、その後時間の経過とともに「Loading...」と「.」が増えて行き、完了した時点で「Loading........(Complete)」と表示される、というものだ。

フィードの取得開始と完了を通知する

あと少し問題が残っている。それはフィードの取得開始をどうやってアプリコントローラ(AppController)に伝えるか、というもの。

実際にネットからフィードを取得するかどうかは、FeedManager だけが知っている(→「FeedManager が完成」)。アプリコントローラから「このフィードをよこせ」と言われたとき、すでにメモリ中に FeedBlogPost クラスのインスタンスがあればそれを返すし、インスタンスがないときにもすぐにはネットから取得せず、まずはローカルのストレージ中で保存されたフィードを探す。そこにもないとなって、ようやくネットから取得することになる。この動きは FeedManager 中にカプセル化されていて、アプリコントローラからは見えない。

FeedManager をアプリコントローラに依存するようにはしたくなかったので、通知(NSNotification)を使って、取得の開始と完了を知らせることにした(→「設定変更を通知する」)。

FeedManager の実装部で開始を通知する部分のコードがこれだ。

- (void)fetchPostsForReceiver:(id)feedReceiver
                         blog:(EntryBlog *)blog
                      account:(NSString *)account
                     password:(NSString *)password {
    [...snip...]
    // send notification
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    NSDictionary *userInfo = [NSDictionary dictionaryWithObject:blog
                                                         forKey:@"fetchingBlog"];
    [nc postNotificationName:BGKeyPostsLoadStartNotification
                      object:self userInfo:userInfo];
    [...snip...]
}

同様に完了を通知するコードは以下になる。

- (void)postsTicket:(GDataServiceTicket *)ticket
   finishedWithFeed:(GDataFeedBlogPost *)gfeed
              error:(NSError *)error {
    [...snip...]
    // send notification
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc postNotificationName:BGKeyPostsLoadCompleteNotification object:self];
}

実装: 進捗メッセージの表示

実際に進捗メッセージを表示するアプリコンントローラの実装を以下に示す(AppController.m より抜粋)。

- (void)awakeFromNib {
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(handleBlogIDChange:)
               name:BGKeyBlogIDChangeNotification object:nil];

    [nc addObserver:self selector:@selector(handleFeedLoadStart:)
               name:BGKeyPostsLoadStartNotification object:nil];
    [nc addObserver:self selector:@selector(handleFeedLoadComplete:)
               name:BGKeyPostsLoadCompleteNotification object:nil];

    [statusMessage setStringValue:@""];
    [...snip...]
}

- (void)handleFeedLoadStart:(NSNotification *)note {
    EntryBlog *blog = [[note userInfo] objectForKey:@"fetchingBlog"];
    [self startIndicatorWithMessage:[NSString
                                     stringWithFormat:@"%@: Loading.",
                                     blog.title]];
}

- (void)handleFeedLoadComplete:(NSNotification *)note {
    [NSObject cancelPreviousPerformRequestsWithTarget:self
                                             selector:@selector(handleUpdateIndicator:)
                                               object:nil];
    NSString *message =
    [NSString stringWithFormat:@"%@(Complete)", [statusMessage stringValue]];
    [statusMessage setStringValue:message];

    [self performSelector:@selector(handleClearIndicator:)
               withObject:nil afterDelay:BGIndicatorClearInterval];
}

- (void)startIndicatorWithMessage:(NSString *)message {
    [NSObject cancelPreviousPerformRequestsWithTarget:self
                                             selector:@selector(handleClearIndicator:)
                                               object:nil];
    
    [statusMessage setStringValue:message];
    [self performSelector:@selector(handleUpdateIndicator:)
               withObject:nil afterDelay:BGIndicatorUpdateInterval];
}

- (void)handleUpdateIndicator:(id)object {
    NSString *message = [NSString stringWithFormat:@"%@.",
                                  [statusMessage stringValue]];
    [statusMessage setStringValue:message];

    [self performSelector:@selector(handleUpdateIndicator:)
               withObject:nil afterDelay:BGIndicatorUpdateInterval];
}

- (void)handleClearIndicator:(id)object {
    [statusMessage setStringValue:@""];
}

取得開始の通知を受ける handleFeedLoadStart: で startIndicatorWithMessage: の遅延実行をセットする。startIndicatorWithMessage では開始メッセージを表示した後、handleUpdateIndicator: の遅延実行をセットする。handleUpdateIndicator: ではメッセージを更新(「.」を追加)した後、handleUpdateIndicator: (つまり自分自身)の遅延実行をセットする。こうして handleUpdateIndicator: の実行が繰り返され、取得完了通知を受けた handleFeedLoadComplete: でキャンセルされるまで続く。また、handleFeedLoadComplete: では、完了メッセージを表示した後、進捗メッセージを消去するための遅延実行をセットしている。これにより、いつまでも完了メッセージがステータス行に残ることを防いでいる。

まとめ

メンドウだと言ってしまえばその通りなんだけど、GUI っていうのはそういうものだ。それに利用している仕組みも難しいものではないし。

一方で、テキストメッセージを表示するというフィードバックは、GUI アプリのユーザ体験としてはほめられたものじゃない。グルグル回ったり、塗り潰されたりする方が見やすいし、わかりやすい。フィードバックを表示するための裏の仕掛けには、今回のように通知と遅延実行を使えば良いが、何を表示するかには工夫の余地がある。

Cocoa Touch 版(つまり iPhone アプリ)では、この方式は使えない。グラフィカルな仕掛けが必要だ。グルグル回るやつとか。

参考文献

詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ ( 2010-12-17 )
ISBN: 9784797361780

タイマを使ったメソッドの実行については「15-01 アプリケーションと実行ループ」に記述がある。とくに今回用いた遅延実行については p.351 の「メッセージの遅延実行」を参考にした。

関連リンク

関連記事

2010-12-24

PasswordManager の導入 - 非公開ブログの記事を取得する (MacBloggerGlass)

今日の作業は「いくつか気になっている細部のつめ」の一つ。非公開ブログの記事を取得できるようにすること。GAE 版では Google サービスに対する認証処理をサボったため公開ブログだけしか扱えなかった。しかし、Cocoa 版ではブログの一覧を取得するために認証付きのアクセスを利用している。記事の取得でも同様にすれば良いだけのこと。

GData ライブラリを使った記事の取得を実行している部分は、FeedManager (の一部のメソッド)に局所化されている。そこだけを変更すればできると考えた。が、この目論見は甘かった。必要な情報を伝播させるため、あちこちに手を入れる必要があった。結局、大小さまざまの変更をほぼ全体に施すことになってしまったのだ。以下で説明する PasswordManager もその一つ。

これまではアカウント(Google アカウント)のパスワードは PreferenceController だけで保管、利用してきた。取得にパスワードが必要な情報(ブログ一覧)を使うのがここだけだったから。一方、ブログ記事を取得するトリガーは AppController にある。非公開ブログの記事を取得するためには AppController から FeedManager にパスワードを知らせなければならない。

環境設定パネル(PreferenceController で制御している)で入力したパスワードを AppController でも利用できるようにするための仕組みが PasswordManger だ。パスワードの保管と読み出しを局所化することがその目的になる。インタフェース部を以下に示す。FeedManager 同様、これもシングルトンパターンを実装している(→「FeedManager の実装」)。メソッドの実装は、PreferenceController にあったキーチェーンを使った保存と読み出しをほぼそのまま流用している(「→キーチェーンサービスを使ってパスワードを保存する」)

@interface PasswordManager : NSObject {
    NSMutableDictionary *passwords;
}

+ (PasswordManager *)sharedManager;

- (NSString *)currentPassword:(NSString *)account;
- (void)updatePasswordForAccount:(NSString *)account
                        password:(NSString *)password;

@end

この他にも、GData API の認証付きサービスを利用するために必要な情報をFeedManager にわたすための仕掛けが必要になったり、と結構大掛かりな変更になった。FeedManager だけを 2、3 行変更するだけのつもりで始めた変更だっただけに、なおさら変更量が多く感じられたのかも。

関連記事

2010-12-23

ラベルによる記事の抽出 - ラベル検索の実装 #3 (MacBloggerGlass)

前回(→「ドロワー (NSDrawer) の使い方」)に続いて、ラベルの付いた記事を抽出する仕組みを作り込む。

Feed にクエリを指定して Entry を絞り込む

GAE 版では、GData API への問い合わせにクエリーを指定することで、取得するフィードデータの段階で記事を絞り込んでいた。Cocoa 版ではフィードデータをローカルストレージに保存しているため、この方式は使えない。

記事の抽出は MVC のうち、M (モデル) でも C (コントローラ) でも可能だが、今回は M (モデル) で行うことにした。この場合、記事の抽出というよりも絞り込みと言うべきだろう。

具体的には、Feed クラスに対して絞り込みのための「何か」を付与することで、Feed のプロパティ entries (配列)に収められる要素(Entry のインスタンス)を制限する。Feed が抱える要素(Entry)をフィルタにかけるようなイメージだ。

Feed に付与する「何か」をクエリー(Query)と呼ぶ。その実体は、Entry クラスの述語メソッドと引数となるオブジェクトを組み合わせたものだ。Feed が抱える Entry のインスタンスそれぞれに対して、述語メソッドを(組み合わされた引数とともに)適用し、結果が真 (BOOL の YES) のものだけを残すわけだ。

今回は、ラベルによる絞り込みなので、Entry クラスには - (BOOL)hasLabel:(NSString *)label というメソッドを定義しておく。以下にその定義を示す。

- (BOOL)hasLabel:(NSString *)aLabel {
    NSString *label;
    for (label in self.labels) {
        if ([label isEqualToString:aLabel]) {
            return YES;
        }
    }
    return NO;
}

NSInvocation を使った Query の実現

Query の定義を以下に示す。これを見てわかるように、Query の実体は NSInvocation そのものだ。Entry のインスタンスに適用するために特殊化された NSInvocation と言っても良い。

NSInvocation は言わば、オブジェクトに対するメソッド呼び出し自体を独立したオブジェクトとして扱えるようにするためのものだ。そこには、メソッドの情報(セレクタとシグナチャ)、引数、ターゲットとなるオブジェクトが収められる。もともと、分散処理を実現するための仕組みの一つのようだが、今回のような目的にも利用できる。

単にラベル(文字列)による要素の絞り込みを実現するだけなら、NSInvocation のような仕組みは必要ない。ただラベル(の配列)を Feed クラスにわたすだけで良い。単純な方法をとらなかったのは、他の種類のクエリーもサポートするためだ。たとえば、投稿日時による絞り込み等。Entry で適切な述語を定義してやれば、そういった絞り込みも可能になる。

@interface Query : NSObject {
    NSInvocation *invocation;
}

+ (Query *)queryWithPredicate:(SEL)predicate argument:(id)object;
- (id)initWithInvocation:(NSInvocation *)anInvocation;

- (BOOL)applyTo:(Entry *)target;

@end

@implementation Query

+ (Query *)queryWithPredicate:(SEL)predicate argument:(id)object {
    NSMethodSignature *signature =
    [Entry instanceMethodSignatureForSelector:predicate];

    NSInvocation *anInvocation =
    [NSInvocation invocationWithMethodSignature:signature];
    [anInvocation setSelector:predicate];
    [anInvocation setArgument:&object atIndex:2];

    Query *query = [[Query alloc] initWithInvocation:anInvocation];
    return [query autorelease];
}

- (id)initWithInvocation:(NSInvocation *)anInvocation {
    [super init];
    invocation = [anInvocation retain];
    return self;
}

- (void)dealloc {
    [invocation release];
    [super dealloc];
}

- (BOOL)applyTo:(Entry *)target {
    [invocation invokeWithTarget:target];

    BOOL result;
    [invocation getReturnValue:&result];
    return result;
}
@end

コントローラ(AppController)では、ドロワー内のテーブルビューで選択が変わったときに、Feed に対して Query を付与している。ラベルが複数選択された場合は Query も複数付けられることになる。

- (void)tableViewSelectionDidChange:(NSNotification *)notification {
    id object = [notification object];
    if (object == postTable) {
        [...snip...]
    } else if (object == labelsTable) {
        NSIndexSet *indexes = [labelsTable selectedRowIndexes];
        NSArray *selectedLabels = [feed.labels objectsAtIndexes:indexes];
        NSString *label;
        NSMutableArray *queries =
        [NSMutableArray arrayWithCapacity:[indexes count]];
        for (label in selectedLabels) {
            Query *query =
            [Query queryWithPredicate:@selector(hasLabel:) argument:label];
            [queries addObject:query];
        }
        [feed setValue:queries forKey:@"queries"];
    }
}

Feed での絞り込みは entries プロパティに対するアクセサを上書きすることで実現している。これを見てわかるように、複数の Query が付けられた場合、AND 結合による絞り込みとなる。

- (NSArray *)entries {
    if ([queries count] == 0) return entries;
    NSMutableArray *filteredEntries = [NSMutableArray array];
    Entry *entry;
    Query *query;
    for (entry in entries) {
        BOOL result = YES;
        for (query in queries) {
            result = [query applyTo:entry];
            if (! result) break;
        }
        if (result) {
            [filteredEntries addObject:entry];
        }
    }
    return filteredEntries;
}

ちなみに今回は、この絞り込みは Feed クラスではなく、Feed から派生させた FilteredFeed クラスに実装している。そして記事フィードを表現する FeedBlogPost クラスをこの FilteredFeed クラスから派生するように変更した。というのも、ブログ一覧(のフィード)を表現する FeedBlog クラスには絞り込みは必要ない。絞り込みの実装がそちらに影響を及ぼすことのないようにしておきたかったのだ。

Cocoa 版 BloggerGlass の完成

ラベル検索が実現できたことで、Mac 用アプリとして作り始めたときの目標(→「欲しいのはオフライン機能」)を達成できた。

MacBloggerGlass プロトタイプ #0
ラベル検索
スクリーンショットだけではわかりにくいが、左のドロワーで選択したラベルの付いた記事だけが一覧に現れている。

これで Cocoa Touch 版、すなわち iOS デバイス向けのアプリとして作り直すための基礎ができた。Cocoa 版との大きな違いは、GUI の部品が異なること、Cocoa Bindings が使えないことなどだろうか。UI としては、Cocoa 版よりもむしろ iPhone 向けに最適化した GAE 版に近いものになるかもしれない。Cocoa Bindings を使わないとすると、コントローラの実装も変わる(コードが増える)はず。

一方で、モデルと言うべき GData オブジェクトのアダプタ(Entry と Feed)クラスおよび FeedManager はほぼそのまま流用できるだろう。

Cocoa Touch 版にとりかかるのは 2011 年になってからになるか。2010 年の残りはいくつか気になっている細部をつめる作業をしよう。

関連リンク

関連記事

2010-12-22

ドロワー (NSDrawer) の使い方 - ラベル検索の実装 #2 (MacBloggerGlass)

ラベルの一覧を表示する UI として、ドロワーを使うことにした。ドロワーとは、アプリウィンドウの端に(大抵は左右のどちらか)付加的に表示できるウィンドウのことだ。通常のアプリウィンドウと同じく、様々なビューを置くことができる。

最近の Mac OS X 用アプリではあまり見かけなくなった(少々古臭い) UI だが、表示中はメインウィンドウにくっついており見失うこともないし、不要なときは隠しておけばメインウィンドウ上のレイアウトを圧迫することもない。

MacBloggerGlass のメインウィンドウは記事一覧表示とその内容表示で、すでに 2 分割(2 ペイン)しているが、ここにラベル一覧表示領域を追加するとさらに分割することになってしまう。その方が使いやすいならそうするが、まずは表示領域の分割ではなく、必要な時だけ領域を追加する方式で試してみることにした。

Interface Builder での操作

Cocoa アプリにドロワーを追加するには、ライブラリツールから Windows グループにある Windows and Drawer (右のスクリーンショットを参照)をドラッグしドキュメントウィンドウにドロップする。

ライブラリツール上は 1 つのオブジェクトのように置かれているが、ドキュメントウィンドウにドロップすると以下の 3 つのオブジェクトが現れる(下のスクリーンショット参照。)。

  • Window
  • Drawer Content View
  • Drawer

この 3 つのオブジェクトは、ドロップされた状態ですでに互いに関連づけられている。具体的には Drawer に備わったアウトレットのうち、parentWindow が Window に、contentView が Drawer Content View にそれぞれ結びつけられている。

大抵の場合、アプリウィンドウはプロジェクトを作ったときから (MainMenu.xib 内に) 存在しているため、上記の Window オブジェクトは不要だ。当然、削除することになるが、そのときは Drawer オブジェクトの parentWindow を適切に設定しなければならない。

Drawer オブジェクト(の parentWindow)にアプリのウィンドウを結びつけるには、Ctrl キーを押しながら Drawer オブジェクトからアプリウィンドウまでドラッグする。アプリウィンドウ上に接続するアウトレットの候補が現れるので、parentWindow を選ぶ。

この Interface Builder のドキュメントにも書いてある正式な手順(Ctrl + ドラッグ)は正直言ってわかりづらい。いつもどっちからドラッグするのかで迷ってしまう。それよりも、アウトレットを備えたオブジェクト上でポップアップメニューを出す方がずっとわかりやすい。マウスなら(特に設定を変えていない限り)右クリック、TrackPad なら 2 本の指でタップした状態でクリックでポップアップメニューが出てくる。メニューにあるアウトレットの右端の端子(○)を接続したいオブジェクトまでドラッグする。Ctrl キーを押す必要はない。

ドロワーの表示

ドロワーを表示するにはアプリコントローラからアウトレットを通して openOnEdge: メソッドを呼ぶ。このとき、親ウィンドウのどの端にドロワーを付けるかを引数として指定する。隠す際は close メソッドだ。

以下は、AppController.m からの抜粋だ。labelsDrawer は AppController クラスのアウトレットで、Interface Builder 上で Drawer オブジェクトに接続してある。

以下のコードでは、現在のドロワーの状態を取得し、それに応じて表示中なら close、非表示中なら openOnEdge: を呼んでいる。openOnEdge: にわたしている NSMinXEdge はウィンドウの左端を意味する定数だ。ドロワーを右端に表示したいなら NSMaxXEdge を指定する。

- (IBAction)toggleLabelsDrawer:(id)sender {
    NSDrawerState state = [labelsDrawer state];
    if (state == NSDrawerOpeningState || state == NSDrawerOpenState) {
        [labelsDrawer close];
        [showDrawerMenu setTitle:@"Show Labels"];
    } else {
        [labelsDrawer openOnEdge:NSMinXEdge];
        [showDrawerMenu setTitle:@"Hide Labels"];
    }
}

上記で、showDrawerMenu に対して setTitle: しているのは、ドロワーの状態によってドロワーを表示(または非表示)にするためのメニュー項目の文字列を変えるためだ。非表示状態では「Show Labels」に、表示中なら「Hide Labels」に置き換えている。showDrawerMenu も AppController のアウトレットで NSMenuItem * として宣言し、Interface Builder 上でメニュー項目と接続してある。

ラベル一覧の表示

前回(→「ラベル検索の実装 #1」)のラベル列挙と合わせると、ラベル一覧表示が可能になる。以下がそのスクリーンショットだ。

MacBloggerGlass プロトタイプ #0
ラベル一覧表示
ラベル一覧をドロワーとして実現した状態。一覧表示にふくまれているすべての記事に付いたラベルを集めてドロワー内のテーブルに表示している。

関連リンク

関連記事

2010-12-21

ラベル検索の実装 #1 (MacBloggerGlass)

ラベル検索を実現するために必要な仕組みは以下の 2 つ。

  • ラベルの列挙
  • 指定されたラベルの付いた記事の抽出

今回は、まず簡単な方、ラベルの列挙を実装してみる。

GDataCategory

GData ライブラリで Blogger の記事は GDataEntryBlogPost クラスが表現している。各記事に付けられたラベルはこのクラスの categories というプロパティに収められている(実際に categories が定義されているのは GDataEntryBlogPost の基底クラスである GDataEntryBase)。このプロパティは NSArray として取り出すことができ、その各要素は GDataCategory クラスのインスタンスになっている。

もともと、Blogger の記事データを表す Atom フィードでは、カテゴリは以下のような XML 要素として表現されている。この term 属性が Blogger の記事に付けたラベルになっている。

<category
    scheme="http://www.blogger.com/atom/ns#"
    term="1. Macをプログラムする">
</category>

GDataCategory クラスには上記の term 属性を参照するための term プロパティが定義されている。ややこしいことに label というプロパティも定義されているが、こちらは Blogger のラベルとは関係ない。

実装

Entry クラス

Entry クラスでは、内部に抱えた GDataEntryBlogPost の categories プロパティから各 GDataCategory のインスタンスの term プロパティを取り出し、NSArray の一時オブジェクトとして返すメソッド(プロパティへのアクセサ)を定義した。

- (NSArray *)labels {
    NSArray *categories = [gDataEntry categories];
    if (! categories) return nil;
    NSMutableArray *lbls = [NSMutableArray arrayWithCapacity:[categories count]];
    GDataCategory *category;
    for (category in categories) {
        [lbls addObject:[category term]];
    }
    return lbls;
}
Feed クラス

Feed クラスでは、初期化時に各 Entry からラベルを(上述の labels メソッドで)取り出し、NSMutableSet にまとめておき、labels プロパティとして保持するコードを追加した。

- (void)gatherLabels {
    labels = [[NSMutableSet alloc] init];
    Entry *entry;
    NSString *label;
    for (entry in entries) {
        for (label in entry.labels) {
            [labels addObject:label];
        }
    }
}

配列ではなく集合を使ったのは、集合は addObject 時に要素の重複を排除してくれるから。

FeedManager クラス

また、内部に抱えた Feed インスタンスの labels プロパティをそのまま返す、同名のプロパティを FeedManager クラスにも用意することにした。アプリコントローラからは FeedManager 経由でアクセスすることにしたいから。

次は?

ラベルの「見せ方」を考えているところだが、まずは単純にドロワー(NSDrawer)に NSTableView でラベルを列挙する方式にするつもり。

ドロワーは最近ではあまり見かけなくなったけど、標準で用意されている部品だし、使わないときは隠しておけるし。

関連記事

2010-12-20

Xcode プロジェクトを git で管理する

MacBloggerGlass のプロジェクトの git 管理を開始することにした。Xcode はツール自身が生成するファイルがいろいろあって、何を git 管理に任せれば良いのかが難しい。ツールのことを知るためには自分で試行錯誤を繰り返すのが一番だが、ここは素直に先人の知恵に頼ることにした。

Xcode-Git-User-Script

今回利用したのは、github に登録されている Xcode-Git-User-Script というプロジェクトのスクリプトだ。この先、何度も繰り返すことになることだから、ツールの機能として取り込む方が良いと判断した。

このプロジェクトが提供するスプリプトは Xcode のスクリプトメニュー(「ウィンドウ」メニューと「ヘルプ」メニューの間にあるスクロールっぽいアイコン)にユーザスクリプトとして追加するもので、メニューから実行することで Xcode のプロジェクトに対して .gitignore と .gitattributes を追加してくれる。

ユーザスクリプトとして追加

まず、github の Xcode-Git-User-Script プロジェクトを開き、pasteMe_into_xcode_script_menu というファイルの内容を全選択しコピーしておく。その後の手順は以下のとおり。

  1. Xcode のスクリプトメニュー(スクロールっぽいアイコン)から「ユーザスクリプトを編集…」を実行する。
  2. 「ユーザスクリプトを編集」ダイアログが現れるので、左下にある「+」ボタンを押す。
  3. (「+」ボタンを押すことで現れる)追加メニューの中から「新規シェルスクリプト」を選択する。
  4. 右側のペインにスクリプトをペーストする。
  5. 必要なら左側のリスト項目から追加したスクリプトの名前を変更しておく(ダブルクリックで編集状態になる)。

右のスクリーンショットは「Create .gitignore」という名前で追加したところ。

スクリプトを実行してみる

スクリプトメニューから追加したスクリプトの名前を選ぶ。するとフォルダを選択するダイアログが出てくる。.gitignore を追加したいプロジェクトのフォルダを選べば良い。README にも書かれているが、このダイアログで「保存」ボタンを押すと、右のスクリーンショットにあるような警告パネルが現れる。気にせず「"."を使用」を押せば良い。

また、このスクリプトでは .gitignore に加えて、.gitattributes というファイルも追加するため、スクリプトの実行中にフォルダを選ぶダイアログが 2 回現れる。当然、警告パネルも 2 回現れることになる。

あとは、対象のプロジェクトのフォルダ(ディレクトリ)で git init すればリポジトリのセットアップが完了だ。

Xcode の次のバージョンに期待

Xcode の次のバージョンでは git のサポートも入るらしい。そうなれば .gitignore のことなんかもツール自身が面倒を見てくれるはず。ま、いつ出てくるのかはわからないけど。β版を試す勇気はないしな。

関連リンク

関連記事

2010-12-19

「詳解 Objective-C 2.0」の改訂版が出た

詳解 Objective-C 2.0 改訂版
荻原 剛志
ソフトバンククリエイティブ ( 2010-12-17 )
ISBN: 9784797361780

荻原(2.0)本の改訂版が出た。「はじめに」に書かれている主な更新点は以下の 5 つ。

  • ブロックオブジェクトの章を新設 (→ CHAPTER 14)
  • 並列処理に関する記述を一新 (→ CHAPTER 19)
  • iOS に関する記述を充実 (→ 07-03 等)
  • Core Foundation の概要を追加 (→ APPENDIX B)
  • NSURL に関する記述を充実 (→ 08-07)

大きな変化は Snow Leopard で導入されたブロックオブジェクトと GCD に関する解説が追加されたことだろう。これら新しい並列処理機能に興味があるかどうかが、旧版を持っているプログラマが新版を買うかどうかの目安になるか。

目次

今後の参照のため、目次を引き写しておく。章立てレベルで目に付く旧版との差異は「CHAPTER 14 ブロックオブジェクト」が追加されたことと、旧版での「CHAPTER 18 スレッド」と「CHAPTER 19 分散オブジェクトが新版では「CHAPTER 19 並列プログラミング」に統合されたこと、そして「APPENDIX B CoreFoundation フレームワークの概要」が追加されたこと。

  • CHAPTER 01 オブジェクトに基づくソフトウェアの作成
  • CHAPTER 02 Objective-C のプログラム
  • CHAPTER 03 継承とクラス
  • CHAPTER 04 オブジェクトの型と動的結合
  • CHAPTER 05 リファレンスカウンタを用いたメモリ管理方式
  • CHAPTER 06 ガーベジコレクション
  • CHAPTER 07 NSObjectクラスとランタイムシステム
  • CHAPTER 08 Foundation フレームワークの重要なクラス
  • CHAPTER 09 カテゴリ
  • CHAPTER 10 抽象クラスとクラスクラスタ
  • CHAPTER 11 プロトコル
  • CHAPTER 12 宣言プロパティとアクセサ
  • CHAPTER 13 オブジェクトのコピーと保存
  • CHAPTER 14 ブロックオブジェクト
  • CHAPTER 15 メッセージ送信のパターン
  • CHAPTER 16 アプリケーションの構造
  • CHAPTER 17 例題: 簡易画像ビューア
  • CHAPTER 18 例外とエラー
  • CHAPTER 19 並列プログラミング
  • CHAPTER 20 キー値コーディング
  • APPENDIX A Foundationフレームワークの概要
  • APPENDIX B CoreFoundation フレームワークの概要
  • APPENDIX C コーディングの指針

ブロックオブジェクト

新設された「CHAPTER 14 ブロックオブジェクト」をざっと読んでみた。まずは章の冒頭部分から引用。

(「詳解 Objective-C 2.0 改訂版」p.322 より)
ブロックオブジェクト (block object) は、Mac OS X 10.6 (Snow Leopard) および iOS 4.0 で利用できるようになった機能で、Objective-C ではなく、C 言語の機能として実装されています。 [...snip...] 他のプログラミング言語ではクロージャ (closure) として知られている言語機能に相当します。

ブロックってクロージャだったのか。それは知らなかったよ。

(「詳解 Objective-C 2.0 改訂版」p.327 より)
どうやら、ブロックオブジェクトは、そのブロックリテラルが記述された位置での自動変数の値を保存しているようです。

なるほど、これなら確かにクロージャだ。これがなければただの関数ポインタになってしまうよな。

この章の前半は、ブロックオブジェクトの使い方と実現のための仕組みの簡単な紹介になっている。仕組みについては、ブロックオブジェクトがスタック上に確保されることの説明があるが、これはブロックを理解するめの基本と言えそうだ。「クロージャはオブジェクトの裏返し」と言われる意味が、この説明を読んで理解できた。

また、後半では、ブロックオブジェクト(という名のクロージャ)の Cocoa アプリでの使用例として、コレクションクラス(配列や辞書)に追加されたブロック対応のメソッドを使ったソートと検索の方法と、シート(パネルの一種でウィンドウにくっついているもの)のデリゲートの置き換えとして使う方法が紹介されている。前者は、ブロックを for (item in collection) の裏返しとして使う方法ってところか。ループの中に記述するコードをクロージャとしてコレクションクラスに適用するもの。

そう言えば、GData ライブラリの実装でも(利用可能なら)ブロックを使うようになっていたな。CHAPTER 19 の並列プログラミングとあわせてじっくり読んでみるか。

参考文献

詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800

こちらは旧版。これまでこのブログでは「荻原(2.0)本」として何度も参照してきた。旧版についても目次を写してある(→「荻原(2.0)本」)

関連リンク

関連記事

twitter より (2010-12-18)

  • 15:57  iphone の ipod からairplay で apple tv に音を送るとまるで iphone がリモコンのよう。実際は本体なんだけどな。
  • 16:07  apple tv って iphone (または ipod touch) の再生装置としても良いかも(・∀・)b ipod のささるスピーカーでもいいけど、あれだと iphone 自体で離れた場所から操作できない。大抵リモコンがあるけど操作性はやはり iphone そのものが上。
  • 16:10  ってか、アレだな。airplay 対応の再生装置が出てくればそれでも良いのか。
  • 20:00  iOS/Mac 向けアプリ開発者で iPad を持っているなら、おすすめです。→ http://logrepo.blogspot.com/2010/12/ibook-apple.html (iBook で Apple 提供の開発者向けドキュメントが読める)
Powered by twtr2src.