2011-01-15

最終定理 ([著]アーサー・C・クラーク, フレデリック・ポール, [版]早川書房)

言わずと知れたクラーク最後の長編。タイトルの最終定理とはフェルマーの最終定理のこと。数百年にわたり数学者を悩ました問題の 1 つで、1995 年にワイルズによって解決されたあれだ。数学者でなくとも数学好きなら中学や高校のときに一度は目にしたことのある定理だろう。

2 つの物語

これは 2 つの物語が(ほぼ)並列に語られる作品だ。1 つは主人公となるスリランカの数学者の半生を描くもの。もう 1 つは銀河間スケールで活動する知性体による人類粛清の物語。

主人公のランジットは数学を志す学生だったが、ひょんなことから海賊の一員と間違われ投獄、拷問の憂き目に会い、友人の尽力で解放され、最終定理の「簡潔な」証明を発見したことで著名な数学者となり、美人と結婚し、娘と息子に恵まれ、幸せに暮らす。

一方、いくつもの銀河に君臨するグランド・ギャラクティクスは、核をもてあそび始めた地球人類を有害な存在とみなし、従属する種属にその粛清を命じる。が、土壇場で気を変え、人類は滅亡を免れる。

2 つの物語は終盤でランジットの娘を接点として絡み合う。この点を除いて、2 つの物語は独立していると言って良い。ランジットはグランド・ギャラクティクスの決定に対して何ら特別な影響を及ぼさないし、グランド・ギャラクティクスとその従属種属は取りたてて(最終定理をふくむ)数学に興味を示しはしない。

絡むようで絡まない 2 つの物語。それがこの作品の特徴だ。

SF らしさ

SF として見るなら、本作はこれまで SF が生み出してきた楽天的なアイデアの数々を盛り込んだ作品になっている。世界政府しかり、宇宙エレベータしかり。さまざまな銀河種族に君臨するオーバーロード(超知性)も、今では懐しく感じる。これらを題材としたクラークの他の作品を思い出すのも楽しい。

また、あわや人類滅亡かという瀬戸際まで行くものの、最後は大団円で終わるところも、読後感すっきりの古き良き SF を思い出させる。

クラークと言うよりはポールの作品

全体の語り口を通して感じられるのは、フレデリック・ポールの作風。ことに異星人の描写ではそれが顕著だ。どちらかと言うと淡々と事実を叙述するクラークのそれとは異なり、描き方に起伏が感じられる。読んでいて「ゲートウェイ」のことを思い出した。とくに終盤で主人公(をふくむ人類)がマシンの中(のシミュレートされた環境)で生き続けるという辺りではその思いを強くした。

解説によれば、本作は、クラークが基本的なアイデアと原稿の一部をポールにわたし、ポールがそれをもとに書き上げ、クラークがチェックをして完成したとのこと。ポール風味になっていて当然か。

かといって、クラークらしさが感じられないかと言うとそうではない。とくに主人公が退屈な大学の授業の中で天文学の講義に魅せられる様子や、スカイフック(宇宙エレベータ)が登場する辺りは、クラークの SF 作品に多く見られる(というよりクラーク自身の)「宇宙への憧れ」がストレートに表われている。

不満は……

唯一の不満点はタイトルになっている「最終定理」が、物語の展開上、ほとんど意味のないこと。暴虐な異星人を撃退するために使われることもなければ、異質な知性とのコミュニケーションに必要なわけでもない。ただ、その証明が主人公のキャリアの転回点になったというだけの扱い。看板に偽りありだ。

ストーリー上、主人公が数学者である必然性はまったくないし、フェルマーの最終定理を持ち出す必要も感じられない。タイトルからイーガンの「ディアスポラ」ようなものを期待していると裏切られることになる。

この点に目をつむれば、読んでいて楽しく、読み終わって爽快な良作 SF だ。

参考文献

宇宙旅行はエレベーターで
ブラッドリー C エドワーズ, フィリップ レーガン
武田ランダムハウスジャパン ( 2008-04-23 )
ISBN: 9784270003350

本作中で「スカイフック」として登場する宇宙エレベータについて知りたいならこの本を読むと良い。

楽園の泉 (ハヤカワ文庫SF)
アーサー・C. クラーク
早川書房 ( 2006-01 )
ISBN: 9784150115463

宇宙エレベータを描いた SF ならこれ。シェフィールドの「星ぼしに架ける橋」(ハヤカワ文庫 SF)も有名だけど、こちらは現在では入手困難。

ディアスポラ (ハヤカワ文庫 SF) / 山岸 真
グレッグ・イーガン
早川書房 ( 2005-09-22 )
ISBN: 9784150115319

こちらは数学が物語に織り込まれた作品。

関連リンク

関連記事

2010-12-28

ZSH のパス設定

以前、MacBook と Mac mini Server の間で git clone しようとして失敗したことがあった(→「MacBook にも Git を (あるいはリモートリポジトリの準備)」)。原因は、リモート側のシェルの PATH 設定だった。git でリモートリポジトリから SSH 経由で clone しようとする場合、リモート側で(SSH 経由で) git コマンドが実行される。リモート側のシェルのパスに git コマンドをふくむディレクトリが入っていなければ、git コマンドが見つからずエラーになる。

シェルは起動時に設定ファイルを読み込む(実行する)ことで実行環境を整える。このとき、たいていのシェルでは実行モードによって読み込む設定ファイルが変わる。実行モードというのはログインシェル、対話シェル、そしてスクリプト実行用シェルの 3 つのことで、git clone で起動されるシェルはスクリプト実行用シェルになる。このため、たとえ ssh コマンドでリモートログインした状態で対話的に git コマンドを使えたとしても、git clone しようとすると (git コマンドが見つからず)エラーになることがある。

今日も、MacBook と iMac の間で git clone しようとして同じエラーが出て失敗した。以前解決したはずの問題にまた遭遇したことになる。というのも、あれからしばらくしてシェルを zsh に変えたからだ(→「twitter より (2010-08-03)」)。最初の設定のときに、ssh コマンドでリモートログインしてローカルなシェルの場合と同様に使えることを確認して安心していた。今日まで git clone のようなリモートのコマンドを直接実行することはなかったから、この問題に気付かなかった。

では、zsh を使う場合リモート(のスクリプト実行用)のシェルが参照するパスを設定するにはどうすれば良いのか? より具体的に言えば、git のリモートリポジトリを置いたコンピュータのシェルが zsh のとき、git clone 等が失敗しないようにするにはどうすれば良いのか?

答えは ~/.zshenv

結論を先に書くと、スクリプト用シェルのために PATH を設定するには ~/.zshenv に書けば良い。書式は通常のシェルスクリプトと同じ。つまり、git clone が失敗する問題を解決するためには、リモート側(clone されるリポジトリがある方)に ~/.zshenv を作り、以下のように PATH の設定をしてやれば良い(git コマンドを MacPorts でインストールしている場合)。

export PATH=/opt/local/bin:/opt/local/sbin:$PATH

これまで、zsh のためのパスは ~/.zshrc で設定してきた。この方法どこが悪かったのだろう?

それは実行モードごとの設定ファイルの読み込みが以下のようになっているからだ(→「【コラム】漢のzsh (1) 最強のシェル、それは「zsh」 | マイコミジャーナル」参照)。一言で言えば、~/.zshrc はスクリプト実行用シェルのときは読み込まれない。

zsh の設定ファイル読み込み順序
実行モード 読み込み順
ログインシェル
  1. ~/.zshenv
  2. ~/.zprofile
  3. ~/.zshrc
  4. ~/.zlogin
対話シェル
  1. ~/.zshenv
  2. ~/.zshrc
スクリプト用シェル
  1. ~/.zshevn

これを見れば、スクリプト用シェルが参照する PATH は ~/.zshenv で設定しなければならないことがわかる。

~/.zshenv を読むより前に起きていること

さて、これで MacBook と iMac の間で git clone に失敗する問題は解決できたが、ついでに zsh における PATH の設定について調べてみた。

zsh の man ページによれば、シェルが起動時に最初に読み込むのは /etc/zshenv だとのこと。その後、上述の表のような順序で設定を読み込む。では、/etc/zshenv はどうなっているのか?

Mac OS X 10.6.5 の /etc/zshenv の内容を以下に示す。

# system-wide environment settings for zsh(1)
if [ -x /usr/libexec/path_helper ]; then
 eval `/usr/libexec/path_helper -s`
fi

ここに書いてあるのは /usr/libexec/path_helper があれば、それを実行しろというもの(バッククォート記法なので、正確には path_helper の実行結果をスクリプトとして実行しろ、となる)。

この path_helper というコマンドは PATH を設定するためのシェルスクリプトを生成するためのもので -s オプションで B-shell 用のスクリプトが作られる。man ページには直接実行するものじゃないゾ、と書いてあるが、そこをあえて実行してみた結果を以下に示す。実行の前に PATH と MANPATH の設定を消しているのは、このコマンドが現在の設定に追加するようになっているから。空にしておくことで、zsh が /etc/zshenv でこのコマンドを実行したときの結果がわかる。

[imac] mnbi% PATH=''
[imac] mnbi% MANPATH=''
[imac] mnbi% /usr/libexec/path_helper -s
PATH="/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin"; export PATH;
MANPATH="/usr/share/man:/usr/local/share/man:/usr/X11/share/man"; export MANPATH;

では、path_helper は PATH (と MANPATH) に入れるべきディレクトリをどうやって知るのだろう? man ページによれば、それは /etc/paths/etc/manpaths だとのこと。そして、ここに書かれたものに、さらに /etc/paths.d/etc/manpaths.d に置かれたファイルの内容を付け加えて、PATH と MANPATH とする。

以下に、Mac OS X 10.6.5 の /etc/paths の内容を示す。

/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/bin

同じく、Mac OS X 10.6.5 では /etc/paths.d には X11 というファイルがあり、その内容は以下のようになっている。

/usr/X11/bin

以上のことから、~/.zshenv を使ってユーザごとにパスを設定する以外にも、追加したいパスを書いたファイルを /etc/paths.d に置けば、システム全体で(正確には zsh を使っているすべてのユーザで)同様の効果が得られることがわかる。

関連リンク

関連記事

2010-12-27

NSTableView でソートしたとき、選択された要素をモデル配列から正しく取り出す方法

NSTableView では、見出し行をクリックするとカラムの内容によって行をソートすることができる。ところが、これはモデル(となっている NSMutableArray)をソートしているわけではないから、ソートしたビュー上の並び順とモデル内の要素の並び順は一致していない。

たとえば、もともと「りんご」「みかん」「ぶどう」の順にモデル配列の中に収められているとして、これをビュー上でソートして「ぶどう」「みかん」「りんご」の順に表示されているとする。ここでビュー上で「りんご」を選択すると、そのインデックスは(0 から数えるので) 2 となる。このインデックスを使い、モデル配列から objectAtIndex: で要素を取り出せば、それは「りんご」ではなく「ぶどう」になってしまう。

つまり、ビューでソートした状態でビュー上の選択(のインデックス)に合わせてモデルの要素を取り出すと、ビューの選択とは異なる要素を取り出してしまうことになるのだ。

MacBloggerGlass の場合で言うと、記事の一覧をタイトルまたは日付けでソートした状態で一覧から記事の選択を行うと、表示される記事の内容がずれてしまう。

こうしたビューとモデルにおける要素の並び順の不一致を避けるには、両者を結びつけている NSArrayController を使えば良い。

具体的には、NSArrayController から selectedObjects で要素オブジェクト(の配列)を取り出せば、ビューでソートしているかどうかに関係なく、適切なオブジェクト(つまりビュー上の選択と同じオブジェクト)が取り出せる。

MacBloggerGlass の場合、以下のようなコードを使う(AppController.m より抜粋)。

    Entry *entry = [[feedController selectedObjects] objectAtIndex:0];

feedController が記事一覧(と記事内容)用のモデル配列のための NSArrayController だ。AppController の IBOutlet として確保し、Interface Builder で NSArrayController のインスタンスと結びつけている。記事一覧では複数選択を許していないから、selectedObjects の返す配列の最初の要素を取り出している。

ちなみに、この NSArrayController を使う解決方法はググって見つけた(→Cocoaの日々: NSArrayController を使った NSTableView で選択行の情報を取得する)

NStableView と NSArrayController の関係

では、なぜ NSArrayController からは、ソートの状態に関係なく、適切なモデルオブジェクトを取り出すことができるのだろうか?

オブジェクトの配列をモデルとして、その内容を NSTableView に表示させる場合(配列の要素になっているオブジェクトのプロパティを表のカラムに表示させる、という意味)、Cocoa Bindings では両者を NSArrayController で結びつける。

これは詳細に言えば、NSTableColumn の値として、NSArrayController の arrangedObjects が返す配列の要素のプロパティを結びつける、ということだ。初期状態では、arrangedObjects はモデルの配列の並び順そのままの配列を返す。だから、モデル配列の順序がそのままビューの表示順序になる。一方、NSTableView の見出し行でソートしたときは、NSArrayController の arrangedObjects が返す配列の並び順が変わる。その結果、ビューの表示順序が変わる。

実はビュー(NSTableView)の行がソートされるのはむしろ結果で、実際にはコントローラ(NSArrayController)が返す要素の順序がソートされていたのだ。これは、NSArrayController がソートされた状態のモデル配列を保持している、と言っても良い。だから、ビュー上でソートされた状態のインデックスを使っても、適切なオブジェクトを取り出すことができるのだ。

では、なぜ NSArrayController の arrangedObjects の並び順が変わるのか? それは、NSTableView の見出し行がクリックされたときに NSArrayController に対して setSortDescriptors が呼ばれ、NSSortDescriptor がセットされることによる。これはソートの方法を指示するオブジェクトで、NSArrayController は arrangedObjects を作り出す際に参照するものだ。初期状態ではこれがセットされていないため(ソートされず)、arrangedObjects はモデルの要素順と同じ順序の配列になっている。

  • 見出しをクリックしてからの一連の動きを時系列で並べると以下のようになる。
    1. NSTableView の見出しがクリックされる。
    2. NSTableView が NSArrayController に対して、NSSortDescriptor をセットする。
    3. NSTableView (実際には NSTableColumn) が NSArrayController に対して arrangedObjects を要求する(メソッドを呼び出す)。
    4. NSArrayController は先にセットされた NSSortDescriptor にしたがってソートした配列を返す。

    まとめると、ソートはビューで起きているのではなく、コントローラの内部で起きている、ということになる。だから、コントローラはソートされた状態のインデックスから適切なオブジェクトを取り出すことができる、と。

    iOS アプリではどうする?

    Cocoa Bindings が使えない iOS アプリでは NSArrayController に相当する部分を自前で用意しなければならないはず。なんだか面倒なことになりそうな予感がする。ソートしなければ(できないようにすれば)良いんだけど。そもそも、iOS のアプリで表をソートするアプリって見かけないような……。

    関連リンク

    関連記事

    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 行変更するだけのつもりで始めた変更だっただけに、なおさら変更量が多く感じられたのかも。

    関連記事