2010-11-27

Cocoa Touch では Cocoa Bindings は使えない

Interface Builder User Guide: Connections and Bindings」は「About Connections and Bindings」という節で始まっている。その冒頭では Mac OS X (と iOS) アプリで用いる「接続(connection)」の型についての説明がされている。

(「Interface Builder User Guide: Connections and Bindings」より)
There are four fundamental types of connections you can create in a Mac OS X and iOS application:

  • Outlet connections
  • Action connections (Mac OS X only)
  • Event connections (iOS only)
  • Bindings (Mac OS X only)

action と event がそれぞれ、Mac OS X 専用、iOS 専用となっているのは良い。iOS アプリで action の代わりを務めるのが event だから。気になるのは最後の Bindings が Mac OS X 専用となっていること。そう、Cocoa Bindings は iOS アプリでは使えない技術なのだ(少なくとも今のところ)。

Interface Builder で Inspector を見てみる

前回の記事(→「Cocoa Bindings の使い方 #2」)で、Cocoa Bindings を設定するための Bindings パネルのスクリーンショットを載せた。このパネルは Interface Builder でインスタンスのさまざまな設定を行うための「Inspector」とツールの一部として表示されるものだ(「Inspector」は Interface Builder のメニューから「Tools」>「Inspector」で表示できる)。この「Inspector」の中には、他にも Attributes や Size を設定するためのパネルが並んでいる。

この「Inspector」を Cocoa アプリの部品と Cocoa Touch の部品でそれぞれ表示させ、そこに並んでいるパネルの数と種類を比べてみた。改めて見ると、確かに違う。Cocoa の部品のパネルに比べて、Cocoa Touch の部品のそれは明らかに数が少ない。

Interface Builder が Inspector に表示するパネル数の差
Cocoa の Table View (NSTableView)
パネルは左から順に、Attributes、Effects、Size、Bindings、Connections、Identity となっている。
Cocoa Touch の Table View (UITableView)
パネルは左から順に、Attributes、Connections、Size、、Identity となっている。

設定するためのパネルがない以上、Cocoa Touch の部品では(つまり iOS アプリでは) Cocoa Bindings が使えないことは確かなようだ。

Cocoa アプリと Cocoa Touch アプリ

以前の記事(→「欲しいのはオフライン機能 - Cocoa アプリとして作り直す #0」)で、Mac 版と iPhone アプリでなるべく設計と実装を共通化したいと書いた。そのためにも、ビュー (V) とモデル (M) の分離が明確になる Cocoa Bindings はおもしろそうだと調べ始めたんだが、Cocoa Touch で使えないとは思わなかった (´・ω・`)。まあ、iPhone アプリのコントローラ (C) が少し大きくなる程度の差で済むと思うがね。

この制約はどうしてだろう? パフォーマンス? メモリ? KVC と KVO は iOS アプリでも使えるっぽいんだけどな(iOS Reference Library に両者の Programming Guide がある)。そう言えば、ガベージコレクションも iOS アプリでは使えないんだった。デバイスに差がある(あり過ぎる)以上、しかたがないんだろうな。

どうでもいいことだけど garbage collection のカタカナ表記としては「ガベージコレクション」と「ガーベジコレクション」のどちらが適切か? ググってみると前者は 130,000 件で後者が 67,100 件。ま、根拠にするには弱いけど、どちらかと言えば前者の方がポピュラーってことか。

関連リンク

関連記事

twitter より (2010-11-26)

Powered by twtr2src.

2010-11-26

Cocoa Bindings の使い方 #2 - Interface Builder 編

前回の記事(→「Cocoa Bindings の使い方」)では、Cocoa Bindings を使うことで、MVC のうち M (モデル) となるコードを書くだけでアプリが作れると書いた(標準部品とその振る舞いだけを使うアプリの場合)。ただし、その設定には Interface Builder に対する慣れが必要だ、とも書いた。

今回は、Interface Builder を使って Cocoa Bindings を設定する手順について書く。ネタ元は公式ドキュメント(Mac OS X Reference Library)の「Interface Builder User Guide」にある「Connections and Bindings」だ。

ヒレガス本の Chapter 8 でも、NSTableViewNSArrayController に結びつける手順が説明されている。サンプルアプリを作る手順をたどるハンズオンの資料としてはわかりやすいが、ツールについての説明が必要最小限にとどめられているため、自分でアプリを作ろうとすると情報が不足していることに気付く。上記のドキュメントはその足りない部分を補完してくれる。

「結びつき(Binding)」を作る

Connections and Bindings」の説明用に貼られているパネルのスクリーンショットは、(説明に使う)一部だけを開いてあるためダマされてしまうが、このパネルには似たような設定項目を持つセクションがいくつも並んでいる。

たとえば、右のスクリーンショットは、前回の記事で紹介したサンプルアプリの設定をしたときのものだが、複数のセクション(「Availability」とか「Controller Content Parameters」とか)に、右向きの三角が付いた項目がいくつもあることがわかる(「Availability」に「Editable」、「Content Controller Parameter」には「Filter Predicate」や「Selection Indexes」)。この右向き三角を開くとそれぞれに「Content Array」に表示されているのと同様な設定(「Bind to:」、「Controller Key」や「Model KeyPath」など)がある。

それぞれのセクションや項目がどういった「結びつき」を作るためのものなのかを知らないと、うっかり他の項目に「結びつき」を作ってしまうことになりかねない(前回のサンプルアプリでは実際そういう間違いをしてしまった)。これらの項目はオブジェクトのクラスによって変わるため、それぞれの項目がどういう振る舞いのためのものかは個別のクラスのリファレンスを参照するしかないようだ。一方で、それぞれの項目に対する設定のうち共通なものについては、「Connections and Bindings」の Table 7-1 に「Common binding attributes」としてまとめられている。

どの項目であれ、最低限、設定しなければならないのは「Bind to:」と「Model Key Path」の 2 つ。右のスクリーンショットでは、それぞれ Feed Controller、entries となっているものだ(この例では結びつける相手が NSObjectController のため「Controller Key」も指定している)。

「Bind to:」は文字通り、どのオブジェクトに結びつけるかを指定するもので、ドロップダウンリストを開くと Interface Builder のドキュメントウィンドウ(左のスクリーンショット)にあるオブジェクトの一覧が出る。先の例ではこの中の Feed Controller が選ばれている。

前回の記事に書いたとおり、このサンプルアプリでは NSArrayController が制御する配列(NSMutableArray)は別のコントローラ(NSObjectController のインスタンスで Feed Controller という名前にしてある)が制御するモデルオブジェクトが抱えるものだ。この配列に対する結びつきを指定しているのがこのパネルでの設定になる。

一方、Feed Controller 側と(配列を抱える)モデルオブジェクトとの結びつきも Interface Builder で設定している。右のスクリーンショットがその設定となる。ただ、こちらの設定は Cocoa Bindings とは異なり、NSObjectController が直接モデルオブジェクトを持つための設定だ(と思う)。ただ、そのアクセスに KVC が使われている。

「結びつき」のパターン

Connections and Bindings」では、結びつきのタイプを以下の 3 つのパターンに分類している。

  • Binding Directory to the Value (値に直接結びつける) [Figure 7-6]
  • Binding Through an Intermediate Object (中間オブジェクトを介して結びつける)
  • Binding to a Collection Objects (オブジェクトの集まりに結びつける) [Figure 7-9]

「中間オブジェクト」パターンについては、さらに 2 つに分けている。

  • Binding through another data object (別のデータオブジェクトを介して結びつける) [Figure 7-7]
  • Binding through an object controller (別のコントローラを介して結びつける) [Figure 7-8]

この分類にしたがえば、前回のサンプルアプリは、「別のコントローラを介して結びつける」と「オブジェクトの集まりに結びつける」の複合方式ということになる。

まとめ

前回のサンプルアプリはヒレガス本の手順を真似て、さらに「結びつき」を作るための試行錯誤を繰り返して作った。そうして動くモノができたものの、すっきりしない感じが残った。そのモヤモヤが「Connections and Bindings」を読むことで氷解した。とはいえ、最初にこのドキュメントに取り組んでいたとしても、すんなりと理解はできなかったに違いない。試行錯誤の後だからこその理解だと思う。

馴染みの薄い概念がふくまれていると、ドキュメントを読んでも表面的な理解で終わってしまう。そこが難しいところだよね。

ちなみに、この記事では「バインディング」「バインドする」とカタカナ表記で書くのが嫌で「結びつき」とか「結びつける」と書いてみた。Lisp 界隈で bind の訳語として定着している「束縛」を使わなかったのは、以前からなんというか語感が堅いと感じていたから。

ま、自分では、脳内で「bind」に変換して読んでいるんだから、どっちでも良いんだがね。他の人はどう感じるかな (・ω・)?

参考文献

Cocoa Programming for Mac OS X
Aaron Hillegass
Addison-Wesley Professional ( 2008-05-15 )
ISBN: 9780321503619

関連リンク

関連記事

2010-11-25

Cocoa Bindings の使い方

追記@2010-11-30

Cocoa Bindings の使い方については以下の後続記事も参照のこと。

ブログの記事一覧を表示するようなアプリでは、記事一覧のデータは、配列(のような何か)に蓄えることになるし、画面の表示は表形式(に似た何か)を使うことになるだろう。

Cocoa アプリでこれをシンプルに実現すると、配列 (NSMutableArray) に蓄えられたデータをテーブルビュー (NSTableView) に表示するものになる。MVC アーキテクチャで言うなら、配列がモデル(正確には配列に蓄えられたオブジェクトがモデル)で、テーブルビューが(その名の通り)ビューになる。

NSTableView にデータを表示させる場合、二通りの方法がある。1 つは、NSTableView のデータソースとなるクラスを定義する方法。具体的には、AppController というようなクラスを定義し(MVC の C)、そこで以下の 3 つのメソッドを定義するというもの。

  • - numberOfRowsInTableView:
  • - tableView:objectValueForTableColumn:row:
  • - tableView:setObjectValue:forTableColumn:row:

3 つ目のメソッドは表示のみのアプリなら必要ない。

この方法の欠点は、NSMutableArray の内容を NSTableView における表示と同期させるコードは、どれも良く似ているものになることだ。

良く似たものになるなら、その仕組みを共通化してフレームワークが提供してくれればありがたい。もちろん、そのためにはある種の「抽象化」が必要になり、利用に際しては一定の「作法」に従わなければならない。この「共通化された仕組み」が Cocoa Bindings であり、「抽象化」が KVC (Key-Value Coding)、「作法」が KVO (Key-Value Observing) ということになる。

サンプルアプリ

今回、サンプルとして作ったアプリのスクショがこれ(→)だ。機能的には(意匠的にも)ヒレガス本の Chapter 8 で作るもの(→ 「MVC と Cocoaバインディング」参照)と同等だが、こちらは、コードとして書いたのはモデルとなる 2 つのクラスのみで、ビューは当然として、コントローラも Cocoa フレームワークの提供するものだけで作られている。

モデル

まずはモデルを構成する 2 つのクラスを示す。これら 2 つは、Atom 形式のフィードから生成することを想定したものだ。Feed オブジェクトが Entry オブジェクトの配列を抱えるようになっている。

Entry の定義
//  Entry.h
#import <Cocoa/Cocoa.h>

@interface Entry : NSObject {
    NSString *title;
    NSDate *updated;
}

@property (readwrite, retain) NSString *title;
@property (readwrite, retain) NSDate *updated;

@end
//  Entry.m
#import "Entry.h"

@implementation Entry
- (id)init
{
    [super init];
    title = @"Hi!";
    updated = [[NSDate date] retain];
    return self;
}

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

@synthesize title, updated;

- (void)setTitle:(NSString *)newTitle
{
    if (newTitle == title) return;
    [title release];
    title = [newTitle retain];
    self.updated = [[NSDate date] retain];
}

@end
Feed の定義
//  Feed.h
#import <Cocoa/Cocoa.h>

@interface Feed : NSObject {
    NSString *title;
    NSDate *updated;
    NSMutableArray *entries;
    int count;
}

@property (readwrite, retain) NSString *title;
@property (readwrite, retain) NSDate *updated;
@property (readwrite, retain) NSMutableArray *entries;
@property (readwrite) int count;


@end
//  Feed.m
#import "Feed.h"
#import "Entry.h"

@implementation Feed
- (id)init
{
    [super init];
    title = @"My Feed";
    updated = [[NSDate date] retain];
    entries = [[NSMutableArray alloc] init];
    [entries addObject:[[Entry alloc] init]];
    count = [entries count];
    return self;
}

- (void)dealloc
{
    [entries release];
    [updated release];
    [title release];
    [super dealloc];
}

@synthesize title, updated, entries;

- (void)setEntries:(NSMutableArray *)newArray
{
    if (newArray == entries) return;
    [entries release];
    entries = [newArray retain];
    self.count = [entries count];
}

@synthesize count;
@end
コントローラ

コントローラには NSObjectControllerNSArrayController を 1 つずつ使っている。これらのコントローラは Interface Builder から NIB (今は XIB)ファイルの中にインスタンス化し、設定を変更しているだけだ。コードは一切、書いていない。

これらコントローラの設定で肝となるのは、それぞれのコントローラが保持することになるモデルクラスとの対応づけだ。NSObjectController は 上述のFeed クラスのオブジェクトを保持する。NSArrayControllerEntry クラスのオブジェクトを生成し(Add ボタンのアクション)、消滅させる(Delete ボタンのアクション)。ただし、Entry オブジェクトたちを保持するのは Feed が抱えた配列だ。このため、NSArrayControllerFeed オブジェクトに対して要素となる Entry オブジェクトの追加、編集、削除を指示する必要がある。このとき用いられるのが Cocoa Bindings だ。

NSArrayController は配列を抱えた NSObjectController にバインドされることになる。

ビュー

スクショを見ればわかるように、いくつかのテキストフィールド(いずれも編集不可にしてある)とテーブルビュー(タイトル欄のみ編集可)、および 2 つのボタンから構成されている。いずれも Cocoa フレームワーク標準のビューたちだ。

テキストフィールドおよびテーブルビュー(のセル)は、Cocoa Bindings によって、2 つのコントローラのプロパティを経由してモデルと結びつけられている。このため、モデル(のプロパティ)が変更されれば、KVO の通知により自動的に変更がビューに反映されることになり、ビューによる変更はコントローラを経由してモデルに反映されるようになっている。

まとめ

MVC のうちモデルとなるクラスを書くだけで後は、Interface Builder の操作でアプリが作れてしまう。標準の部品(ビューとコントローラ)を使うアプリであれば簡単にできる。ただし、Interface Builder を使って Cocoa Bindings の設定するのには、ちょっと慣れが必要だ。似たような設定項目が多く、設定する場所を間違えたら、まったく動かない。今日もそれでかなり苦労した。

参考文献

詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800
Cocoa Programming for Mac OS X
Aaron Hillegass
Addison-Wesley Professional ( 2008-05-15 )
ISBN: 9780321503619

関連リンク

関連記事

twitter より (2010-11-24)

  • 16:23  そうか。何気なくアップデートしたけど、tv は iOS デバイスでありながら母艦を必要としないんだ。 → http://ipodtouchlab.com/2010/11/apple-tv-os41.html
  • 16:31  インタラクティブっていうのはデジタルコンテンツの得意とするところだものな。電子書籍もこっちの方向に進むかね。ただ、子どもに使わせるなら今の iPad はちょっと重すぎるだろう。 → http://bit.ly/gMJsry
Powered by twtr2src.

2010-11-24

日付文字列の変換 - RFC3339 から NSDate へ

実装

まずは、今回作った Objective-C による実装から示す。関数の機能は単純で、RFC3399 形式で表現された日時を元に NSDate のオブジェクトを生成する。関数内で NSDate オブジェクトを alloc しているが、返す前に autorelease しているため、荻原(2.0)本 (p.96) で言う「一時的なオブジェクト」となっている。また、関数を呼び出す側で自動解放プールを用意する必要がある。通常の Cocoa アプリの場合は、NSApplication がイベントループの管理とともに自動解放プールも用意してくれる(→ 「荻原(2.0)本」p.98)。

この実装では NSString の機能のうち、以下のメソッドを多用している。

- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet*)aSet
aSet で渡された文字集合中の文字が最初に見つかった場所を返す。
- (NSString*)substringToIndex:(NSUInteger)aIndex
先頭から位置 aIndex までの部分文字列を返す。
- (NSString*)substringWithRange:(NSRange)aRange
aRange.location を開始位置とし、aRange.length を長さとする部分文字列を返す。
- (NSString*)substringFromIndex:(NSUInteger)
aIndex を開始位置としする末尾までの部分文字列を返す。

上記の substring...の戻り値はいずれも一時的なオブジェクトになっている。

参照仕様

GData API の Protocl Reference によれば、各種日付には RFC3339 フォーマットが使われる、とある。これは GData API が使うデータフォーマット(Atom 形式; RFC 4287 で定義されている)の仕様によるものだ。

(「RFC 4287」より)
A Date construct is an element whose content MUST conform to the "date-time" production in [RFC3339]. In addition, an uppercase "T" character MUST be used to separate date and time, and an uppercase "Z" character MUST be present in the absence of a numeric time zone offset.

[...snip...]

Example Date constructs:
<updated>2003-12-13T18:30:02Z</updated>
<updated>2003-12-13T18:30:02.25Z</updated>
<updated>2003-12-13T18:30:02+01:00</updated>
<updated>2003-12-13T18:30:02.25+01:00</updated>

上記の中で例として挙げられている 4 つのパターンを見れば書式のおよそは理解できる。

より詳細な 書式の定義は RFC 3339 にある。

date-fullyear   = 4DIGIT
date-month      = 2DIGIT  ; 01-12
date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                          ; month/year
time-hour       = 2DIGIT  ; 00-23
time-minute     = 2DIGIT  ; 00-59
time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
                          ; rules
time-secfrac    = "." 1*DIGIT
time-numoffset  = ("+" / "-") time-hour ":" time-minute
time-offset     = "Z" / time-numoffset

partial-time    = time-hour ":" time-minute ":" time-second
                  [time-secfrac]
full-date       = date-fullyear "-" date-month "-" date-mday
full-time       = partial-time time-offset

date-time       = full-date "T" full-time

この定義から、日時の表現は「日付」と「時刻」に二分され、その区切りが文字「T」であること、また「時刻」は「時刻本体」と「時差」に分かれることが読みとれる。

「時差」については、時刻による差分で表現したもの(「+09:00」や「-5:00」など)か、あるいは文字「Z」を用いる(時差として文字「Z」を指定した場合、その時刻は UTC を意味する)。

さらに「時刻本体」の表現については、「時」「分」「秒」をそれぞれ 2 桁の十進数で表現する(1 桁の場合は先頭に「0」を付加する)。また「秒」については 1 秒以下を小数表現で「秒」に付けても良い、となっている。

GData API での時刻の取扱い

実際に GData API が返すフィードを見ると、updated 要素等は以下のようになっていて、確かに上記の書式に従っていることがわかる。

<published>2010-10-03T21:18:00.000+09:00</published>
<updated>2010-10-03T21:18:00.840+09:00</updated>

一方、GData Objective-C Library が返す時刻データは GDataDateTime クラスのインスタンスになっていて、そこから文字列データを取り出すと RFC3339 フォーマットの文字列となる。ただし、それを作る部分は以下のようになっているため、「秒数」の小数点以下が無視されている(GData Objective-C Client ver. 1.10.0)。これは、Cocoa の NSDateComponents が小数点以下の秒数をサポートしていないことによるものだと思われる(以下のコード中の dateComponents は NSDateComponents のインスタンスを保持している)。

- (NSString *)stringValue {
  return [self RFC3339String];
}

- (NSString *)RFC3339String {
  NSDateComponents *dateComponents = [self dateComponents];
  NSInteger offset = [self offsetSeconds];

  NSString *timeString = @""; // timeString like "T15:10:46-08:00"

  if ([self hasTime]) {

    NSString *timeOffsetString; // timeOffsetString like "-08:00"

    if ([self isUniversalTime]) {
     timeOffsetString = @"Z";
    } else if (offset == NSUndefinedDateComponent) {
      // unknown offset is rendered as -00:00 per
      // http://www.ietf.org/rfc/rfc3339.txt section 4.3
      timeOffsetString = @"-00:00";
    } else {
      NSString *sign = @"+";
      if (offset < 0) {
        sign = @"-";
        offset = -offset;
      }
      timeOffsetString = [NSString stringWithFormat:@"%@%02ld:%02ld",
        sign, (long)(offset/(60*60)) % 24, (long)(offset / 60) % 60];
    }
    timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%0l2d%@",
      (long)[dateComponents hour], (long)[dateComponents minute],
      (long)[dateComponents second], timeOffsetString];
  }

  // full dateString like "2006-11-17T15:10:46-08:00"
  NSString *dateString = [NSString stringWithFormat:@"%04ld-%02ld-%02ld%@",
    (long)[dateComponents year], (long)[dateComponents month],
    (long)[dateComponents day], timeString];

  return dateString;
}

おまけ

最初に挙げたプログラムを Xcode で Command Line Tool としてビルドし、実行するとコンソールには以下のように出力される。水平線で区切られた 4 行が、testGetDateObject の一回の呼び出しによるもので、最初が引数として与えられた RFC3339 形式の日時文字列、2 行目が getDateObject による変換結果(の NSDate のインスタンス)、3 行目がそのインスタンスをデフォルトのタイムゾーン(ウチではもちろん JST)で評価した結果、最後の 4 行目は同インスタンスを UTC で評価した結果となっている。

2010-11-24 22:06:14.819 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.821 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56Z
2010-11-24 22:06:14.824 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.824 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.825 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.825 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.825 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56z
2010-11-24 22:06:14.826 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.826 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.826 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.827 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.827 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123Z
2010-11-24 22:06:14.827 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.828 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.828 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.828 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.829 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56.123z
2010-11-24 22:06:14.829 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.829 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.830 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.830 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.830 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56+09:00
2010-11-24 22:06:14.831 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.831 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.832 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.832 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.832 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56+09:00
2010-11-24 22:06:14.833 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.833 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.834 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.834 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.834 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123+09:00
2010-11-24 22:06:14.835 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.836 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.836 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.836 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.837 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12.34.56.123+09:00
2010-11-24 22:06:14.837 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.838 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.838 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.838 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.839 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56-05:00
2010-11-24 22:06:14.839 ExpApp[24929:a0b] NSDate object: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.839 ExpApp[24929:a0b] Default TZ: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.840 ExpApp[24929:a0b] UTC: 2010-11-22 17:34:56 +0000
2010-11-24 22:06:14.840 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.840 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123-05:00
2010-11-24 22:06:14.841 ExpApp[24929:a0b] NSDate object: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.841 ExpApp[24929:a0b] Default TZ: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.842 ExpApp[24929:a0b] UTC: 2010-11-22 17:34:56 +0000

参考文献

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

関連リンク

関連記事

twitter より (2010-11-23)

  • 18:13  iPad のアップデート中。アップデートが終わり、リブートした後の同期(バックアップ)に時間がかかるわ。雑誌の PDF データがでかいんだよね(Good Reader のデータになってる)。
  • 19:05  AirPlay って iPhone や iPad から tv への配信のみなのか。逆はできない。ちょっと残念だけど、これだけでもオモシロイ。iPhone や iPod touch ならリモコン感覚で使えるしな(実際リモコンにもなる)→ http://bit.ly/h97C0V
  • 19:06  AirPlay があると iPod touch に動画を溜めてみたくなる。
  • 21:17  そうだ、iPad のメールも iPhone と同じで「全受信」が使えるようになったんだね。これに慣れてしまうと、iPad でメールを開いたときにいちいちアカウントを切り替えるのがメンドウでしかたなかった。地味だけど、これも 4.2.1 になって良かったことの 1 つだな。
  • 21:34  お、いいね。.gitignore の設定集。プロジェクトごとだけじゃなく、グローバルに ignore を設定することもできるのね。OSX で git を使うときは便利だな。「.DS_Store」とか、いちいち足さなくても良くなる。→ http://bit.ly/fQxBqD
  • 21:51  今どきの若者や子どもたちの中にはダイヤル式の電話なんか見たこともなかったりするんだろうな。きっと使い方もわからないんだろうな。→ http://inspire.2ia.pl/post/35068946/dial-the-old-fashion-way
  • 21:55  ちょっと試してみたけど、おもしろかった。iPhone にも最適化されていて素敵。ただ、iPhone で開くとランダムボタンは出てこない。ちょっと残念。 → http://bit.ly/hLlA1V
Powered by twtr2src.

2010-11-23

iOS アップデート (→ 4.2.1)

一度延期になってしまったが、ようやく iOS のアップデートが来た。早速、iPad、iPhone 3GS、iPod touch (4th)、そして iPhone 3G の順にアップデートを行った。

AirPlay や AirPrint といった新機能もあるけれど、今回のアップデートの肝はなんといっても、iPad と他の iOS デバイスの OS が統合されたことだ。両方を使っていて、微妙な差異にイライラさせられることがなくなる(フォルダとかタスク切り替えとか)。アプリを開発するときにも、現行の OS のバージョンがいくつもあるというのは面倒が多い。

iOS 4.2.1 上の Safari のユーザエージェント

以前にも使った簡単な CGI プログラムで各実機が使うユーザエージェントを調べてみた。

Apple 製デバイスの OS のバージョンとユーザエージェント文字列の対応
Device OS User-Agent
Mac Snow Leopard 10.6.5 (10H574) Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; ja-jp) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4
iPhone 3G iOS 4.2.1 (8C148) Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_1 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5
iPhone 3GS iOS 4.2.1 (8C148a) Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_2_1 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148a Safari/6533.18.5
iPad iOS 4.2.1 (8C148) Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5
iPod touch (4th) iOS 4.2.1 (8C148) Mozilla/5.0 (iPod; U; CPU iPhone OS 4_2_1 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5

すぐに気付くことは、Mac 用の Safari のバージョンが一番高い(5.0.3)こと。WebKit のバージョンもやはり Mac 用のものが新しい(533.19.4)。意外だったのは、iPhone 3GS だけ OS の番号(ビルド番号かな)が異なっているということだ。3GS のみ 8C148a と末尾に a が付いている。他の iOS デバイスはすべて 8C148 だ。

ともあれ、このアップデートで、ようやく iPad の Safari と他の iOS デバイスの Safari が同じバージョンになった。これまでは、iPad の方が少し古く、CSS3 のサポート状況に若干の違いがあったのだ(→「ウェブページのスタイルを iPad に最適化する方法」参照)。今後のアップデートは足並みを揃えてもらいたいものだ。

関連リンク

関連記事

2010-11-22

Emacs のウィンドウを水平方向に分割する

横 80 桁の呪縛

Emacs との出会いはキャラクタ指向のパソコン上でのことだったこともあり、GUI ベースのものを使うようになっても、その横幅はこれまでずっと 80 桁と決めてきた。「三つ子の……」ということわざを引き合いに出すまでもなく、ごく初期に覚えたことは身に染み付いて消えないのだ。

Emacs のウィンドウを垂直方向に分割することも、ごく初期に覚えたことの 1 つ(split-window-vertically)。キャラクタ端末上の Emacs で複数のファイル(とバッファ)を切り替えて使うためには必須だったし、何より 2 つのファイルの内容を同時に表示できるというのは当時は画期的なことだったのだ。

一方で、ウィンドウの水平方向への分割(split-window-horizontally)についてはほとんど使ったことがなかった。機能としての存在は(ずいぶん前から)知っていたと思うけれど実用性を認めていなかったのだ。実際のところ、キャラクタ端末は言うに及ばず、GUI ベースでも VGA や XGA 程度の画面では 80 桁の画面を複数並べられるほどの横幅はない。

不思議なもので 30 インチのシネマを使うようになっても、その横に 27 インチの iMac を並べるようになっても、ずっと Emacs のウィンドウは 80 桁のもの 1 つだった。ずっとそうでなくてはならない、と思い込んでしまっていたのだ。

呪縛を破る

呪縛(不合理な思い込み)を打破するためには、実際に見ることが一番だ。わたしの場合、それは Emacs の活用術を特集したある雑誌のページだった(→「WEB+DB PRESS Vol.58」)。記事の内容はウィンドウのサイズとも分割とも全然関係ないものだったが、その中に水平方向に 2 分割された Emacs ウィンドウのスクショが掲載されていた。

それを見て目からウロコが落ちる気がした。「あ、そうか。横にも分割できるのか。」「今の画面サイズなら実用になるかも。」「けど、描画が遅いんじゃないかな。」「どうかな、昔とは CPU のパワーも桁違いだから。」「やってみればわかるだろう。」

やってみた。結論から言って、描画がモタつくようなことはない。スクロールして、画面が上からヌルヌルと描かれていくなんてことはない。

ちなみに、水平方向への分割は Ctrl-x 3 に割り当てられている。水平方向の分割が Ctrl-x 2 だから覚えるのにも苦労はない。

水平方向に分割した Emacs ウィンドウ
水平方向に 2 分割
ウィンドウの横幅は 80 桁 x 2。2 つのファイルが同時に表示されている。シネマの画面にはまだまだ余裕がある。
水平方向に 4 分割
ウィンドウの横幅は 80 桁 x 4。最左の画面は縦方向にも 2 分割している。都合、5 つのファイルが同時に表示されている。シネマの画面もほぼ一杯になっている。

複数ウィンドウ vs ウィンドウ分割

単に複数のファイルを同時に表示するだけなら別の方法もある。今の Emacs ならウィンドウを複数開くこともできる。複数ウィンドウに比べて、ウィンドウ分割が優れている点は、単一のキー操作(Ctrl-x o)を繰り返すだけでフォーカスするバッファを切り替えられることにある。慣れてくればほとんど自動的に指が動いてくれる。

Xcode のエディタも複数のウィンドウを開くことができるが、キー操作、それも単一の操作だけでウィンドウを切り替えることはできない。

ま、80 桁 x 4 はちょっと極端すぎるかもな。画面が一杯になって他のウィンドウが表示できなくなる。実用性を考慮すると、80 桁 x 2 ぐらいがちょうど良いのかも。

参考文献

WEB+DB PRESS Vol.58

技術評論社 ( 2010-08-24 )
ISBN: 9784774143248

関連記事

2010-11-21

記事一覧を取得する (MacBloggerGlass)

今回は、GData Objective-C Client Library (以下、GData ライブラリ) を使って、Blogger で作ったブログの記事一覧を取得するところまでを作ってみる。

サンプルプログラムがドキュメント

残念ながら公式ドキュメントは充実しているとは言い難い。参考になるのはサンプルプログラム、そしてライブラリ自体のソースコードだ。

Blogger API のサンプルは充実していて、API を使ってやれることは一通り網羅されている。Google アカウントでログインさせた後、ブログの一覧を表示する。一覧からブログを選ぶと、次は選んだブログの記事を取得し一覧で表示する。記事一覧から記事を選べば記事の内容(とコメントがあればコメントも)表示する。記事の内容は更新することも可能になっている。UI こそ単純なテーブルとテキストフィールドを組み合わせたものだが、機能的には Blogger 専用のブログエディタと言っても良いぐらい。

MacBloggerGlass として作りたいと思っているモノの骨格はこのサンプルにある。言い方を換えれば、データの取得やその保持に関してはライブラリに任せてしまえば良いということになる。アプリの作り手としては、ユーザ体験とそれを実現する UI のデザインに専念すれば良い、と。

ま、そのライブラリの使い方を解明するのが、ちょっと大変なんだけどね。Python のライブラリより複雑っていうか、癖があるっていうか。慣れていないだけかな。

MacBloggerGlass のプロトタイプ

右のスクショが今回作ったプロトタイプ(のかけら)のウィンドウだ。使い方は単純、Blog ID を入力して Read ボタンを押すだけ。Blog ID のブログに蓄えられた記事の一覧を取得してウィンドウのテーブルに表示する。使い方同様、機能も単純だ。ちなみに、スクショはこのブログの記事を取得させたときのものだ(Blog ID はダミー)。

以下が今回書いた主要部分になる(MacBloggerGlass/AppController.m より抜粋)。getFeed が Read ボタンが押されたときに実行されるアクションになる。

- (IBAction)getFeed:(id)sender
{
    NSString *blogID = [blogIdField stringValue];
    if ([blogID length] == 0) return;

    [feed removeAllObjects];

    NSString *blogPostURL = @"http://www.blogger.com/feeds/%@/posts/default";
    NSURL* feedURL = [NSURL URLWithString:[NSString stringWithFormat:blogPostURL, blogID]];
    NSLog(@"%@", feedURL);
    
    GDataServiceGoogleBlogger *service = [self bloggerService];
    GDataServiceTicket *ticket;
    
    ticket = [service fetchFeedWithURL:feedURL
                             feedClass:[GDataFeedBlogPost class]
                              delegate:self
                     didFinishSelector:@selector(blogPostsTicket:finishedWithFeed:error:)];

}

- (GDataServiceGoogleBlogger *)bloggerService {
    
    static GDataServiceGoogleBlogger* service = nil;
    
    if (!service) {
        service = [[GDataServiceGoogleBlogger alloc] init];
        
        [service setShouldCacheDatedData:YES];
        [service setServiceShouldFollowNextLinks:YES];
    }
    return service;
}

// blog feed fetch callback
- (void)blogPostsTicket:(GDataServiceTicket *)ticket
       finishedWithFeed:(GDataFeedBase *)afeed
                 error:(NSError *)error
{
    GDataFeedBase *blogFeed = [afeed retain];
    int count = [[blogFeed entries] count];

    for (int i = 0; i < count; i++) {
        GDataEntryBase *entry = [blogFeed entryAtIndex:i];
        Entry *e = [[Entry alloc] init];
        e.title = [[entry title] stringValue];
        e.date = [[entry publishedDate] stringValue];
        [self willChangeValueForKey:@"feed"];
        [feed addObject:e];
        [self didChangeValueForKey:@"feed"];
    }
}

GData ライブラリの使い方の概略は、(1) 利用するサービス(Blogger 等)に対応した GDataServiceGoogle の派生クラスをインスタンス化する、(2) API が返すデータ(Atom フィード)を受け取るクラスを指定して (1) のインスタンスに対して fetchFeedWithURL:feedClass:delegate:didFinishSelector を呼び出す、となる。このとき、didiFinishSelector として、データ取得が完了したときに呼ばれるコールバックメソッドを指定する。

上記のコードでは、65 〜 76 行目の bloggerService がサービスクラスをインスタンス化している部分で、58 行目がデータ取得のための呼び出しになっている。また、コールバックは 79 〜 95 行目の blogPostsTicket:finishedWithFeed:error を指定している。

もとのサンプルでは、Google アカウントとパスワードを入力させてユーザ認証を行っているが、今回のプロトタイプではユーザ認証は省いている。そのために fetchFeedWithURL:... にわたす URL を 51 行目で指定している。アドホックなやり方だと思うが、スマートな(というかライブラリが想定している適切な)方法を調べ切ることができなかったのでこうなった。

表示するブログの選択についてはユーザ体験の観点からどうすべきかを考える必要がある。ブログ ID 自体を指定させる方法が使い易いとは思えないから。ま、おいおい。

関連リンク

関連記事