Objective-CによるOAuth2のサンプルを作ってみた

ここ最近iPhone(iPad)アプリ開発に興味を持ってます。

ちょっとPicasaの写真を表示してみたいなと思ったところで、最近対応されたGoogleのOAuth2のサンプルのようなものがあまりなさそうなので作ってみました。といってもOAuth2自体は1に比べてシンプルなプロトコルになっているので必要性は薄いかもしれませんが、ひとつの実装例として見ていただけると幸いです。

さてGoogleのOAuth2ですが、まず準備としてこちらのサイト(http://code.google.com/intl/ja/apis/accounts/docs/OAuth2.html#Registering)の説明に従い、自分のアプリの登録を行います。この操作により自分のアカウントに関連付けられたClient IDとClient Secretが発行されます。また、ここで、redirect URIにローカルアプリ用の

urn:ietf:wg:oauth:2.0:oob

という値を登録します。

アプリからの認証の流れは以下のようになります。

  1. Client ID、サービスのスコープ(一覧)などを含めたURLのページを開き、ユーザにアクセス許可を求める
  2. ユーザにOKされたらAuthorization Codeが発行されるので、そのページのタイトルなどからAuthorization Codeを取得する
  3. 上で得られたAuthorization Code、Client ID/Secretなどを用いてGoogleサーバにAccess Tokenのリクエストを送信する
  4. 上で得られたAccess Tokenを用いて各サービスにアクセスする

で、今回作成したサンプルは以下になります。私はiPadしか持っていないためiPadアプリケーションとしてつくっております。

GitHub - maneman8000/SampleOAuth: iPad sample application of google OAuth2.0

Client IDとClient Secretは入れていないので、GitHubからソースを落としたら SampleOAuthフォルダ内に OAuth2GoogleClient.h というファイルを作成し、以下のように自分のClient IDとClient Secretを定義していただく必要があります。

#define GOOGLE_OAUTH2_CLIENT_ID @"ここに Client ID を貼り付ける"
#define GOOGLE_OAUTH2_CLIENT_SECRET @"ここに Client Secret を貼り付ける"

ビルドして実行していただくとユーザに許可を求めるページが表示されます。アカウントを入力して許可すると、自分のPicasaのアルバムのXMLがそのままTextViewに表示されます。

なお、現状ではRefresh Tokenによる権限の更新は実装していません。おいおい追加する予定です。

Objective-CのMessage Forwarding

Objective-Cではオブジェクトに存在しないメッセージを送ると例外が発生します。しかし、Message Forwardingと呼ばれる機能を用いるとそのような場合に例外を出すことなくメッセージを処理することが出来るようになります。これは、PerlのAUTOLOADと同じと考えて良いようです。

で、このMessage Forwardingなんですが、恐らくフレームワークやライブラリなどを設計する場合には強力な助けとなる機能のようですが、アプリケーションを作る場合にはあまり使う場面は少なそうです。Appleドキュメントでは継承の代わりに使う例が紹介されてますが、「Modern Perl」のAUTOLOADのサンプルで出ていた「メソッド呼び出しを記録するProxyクラス」という例も面白いものなので、これをObjective-Cで書き直したものとして紹介したいと思います。

このProxyクラスの名称はLogProxyとします。LogProxyは以下のように使うことを想定してます。

// Testクラスのメソッド呼び出しを記録する
id test = [[LogProxy alloc] initWithTarget:[[Test alloc] init]];
[test someMethod];   // ログに記録される

ではLogProxyの実装を見てみましょう。まずインターフェース部分。

@interface LogProxy : NSObject {
    id proxied;
}
- (id)initWithTarget:(id)target;
- (void)dealloc;

@end

Message Forwardingの転送先としてproxiedという変数を保持するようにします。また、proxiedはコンストラクタで与えるようにしてます。

次に実装部分です。Message Forwardingを行うためには、forwardInvocation:とmethodSignatureForSelector:という2つのメソッドを実装する必要があります。forwardInvocation:は存在しないメッセージ呼び出しがあったときに代わりに呼ばれるメソッドで、引数としてNSInvocationのオブジェクトを取り、そこから呼び出されたときのセレクタの情報などを取得することができます。また、methodSignatureForSelector:はメソッドシグネチャを返すもののようです。あまりこちらは理解できてないのですが、親クラスのものか転送先オブジェクトのそれを返すことでよいようです。実装のコードは次のようになります。

@implementation LogProxy

- (id)initWithTarget:(id)target {
    if ((self = [super init]) != nil) {
        proxied = target;
    }
    return self;
}

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

// 存在しないメッセージが送られたときに呼ばれる
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 関数呼び出しのログを記録する
    NSLog(@"Called [%@]", NSStringFromSelector([anInvocation selector]));
    // メッセージを転送
    [anInvocation invokeWithTarget:proxied];
}

// こちらは決まりきったコード(?)
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {
    if ([super respondsToSelector:aSelector])
        return [super methodSignatureForSelector:aSelector];
    return [proxied methodSignatureForSelector:aSelector];
}

@end

以上。基本的には「Modern Perl」に出ていたもののマネですが、転送をプロクシ的に使うというのは何か別の応用も考えられるかもしれません。

「Modern Perl」の電子版があった

読み終わりました。自分はあまりモダンなPerlてのを知らなかったので参考になることばかりでした。

ところで、著者のブログを眺めていたら、驚いたことに電子版を無料配布されておりました。epub版もあるらしいです。変換すればkindleでも見れるかな。

Share the Modern Perl ePub - Modern Perl Programming

「Modern Perl」メモ

Modern Perl」読書メモ。「モダンPerl入門」も実はまだ読んでなくてそちらも読みたいのだけど、洋書が安いのでついこちらを買ってしまった。50ページぐらいまで読んだところで、自分的に有用だった事柄をメモ。

  • Windowsではstrawberry perlを使う
  • Modern::Perl
  • perldocで調べるのが早い
  • ハッシュのスライシング(@hash{@key} = @val みたいなやつ)
  • $_にmyをつけるとスコープ限定で使える
  • Regexp::Common
  • リスト戻り値の個数を数えるイディオム(my $count=()=get_some_list();)
  • given/when構文
  • Scalar::Util

我流だった部分を直せるのでいい感じ。特に今までActivePerlを使っていたのはイタかった。Windowsでも普通にcpanて使えたんですね・・・。

Scintillaのデータ設計

Scintillaは、Notepad++やTortoiseSVNなど、かなりいろいろなプロジェクトから利用されている、有名なテキストエディタコンポーネントである。テキストエディタ設計の中身が気になってソースを見ながら調べてたんだけど、データの構造の部分が見えてきたのでまとめておく。

一般的なテキストエディタ設計の話は、GreenPadの作者の方のページに大変いい感じでまとまっている。ポイントは、それなりに頻繁に挿入削除がある巨大なデータを、効率よく処理できるかというところにある(と思われる)。

テキストのデータ構造

Scintillaではテキストやスタイルの情報は、GapBufferと呼ばれているデータ構造に格納されている。GapBufferについてはわりと多くの解説があるのでここでは詳しくは述べない(w.l.o.g.などを参照)。ScintillaではGapBufferという名前ではなくSplitVectorという名前のクラスになっている(ソースファイルはSplitVector.h)。SplitVectorの実装は、意外だったのだけどかなりシンプルで、例えば先のリンク先で書いてあったような削除時の最適化などの工夫みたいなものはない。シンプルさを特徴にしているからというのもありそうだが、Scintillaベースのエディタで大きなファイルを編集しても、動作が引っ掛かるようなことはないので、このような実装で十分なのかもしれない。SplitVectorについてはあまり説明することもないので、主要なメンバ変数が意味しているものを把握するためのの図を作って示す。真ん中の破線部分がGapである。以下のようにデータが入っている先頭部分の長さがpart1Length、データ全体の長さがlengthBody、Gapの長さがgapLength、バッファ全体の長さがsizeとなっている。


行数の管理方法

行数の管理方法もエディタデータ設計のポイントで、Scintillaではテキストと同じようにSplitVector(GapBuffer)を用いている。ただ面白いのは、テキストの変更のたびに起こる書き換えの範囲を少なくするための工夫がなされているところである。

まず、行数管理のデータでは、行がバッファのどの範囲になっているかということを示す。図はテキストが「abcd(改行)efg」となっているときのデータバッファと行数管理バッファのイメージである。行数管理バッファの1つめは常に0が入る。次に、1行目のテキスト文字列が「abcd(改行)」となっているので、4文字+改行コード2バイトで6バイトとなり、6がバッファの2つめに入る。そして、2行目は3文字なので3バイトとなり、3がバッファの最後に入っている。また、図ではGap位置も描かれているのだが、Gap位置は編集順序によって異なるものであることに注意していだきたい。

このようなデータでは、修正があったときに変更があった行数以降の数値を全て書き換えなければいけないという問題がある。例えば、上の例では1行目に1バイト追加したときは、行数管理バッファの位置2と3両方を変更しなければならない。行数管理バッファは行数と同じ長さになるので、行数が多いファイルでは行の先頭の文字列を更新することのコストが高くなってしまう。

このような問題の対処として、Scintillaでは行数に変更があった位置と変更量をキャッシュするという手法を用いている。

Scintillaでは行数を管理するために、SplitVectorのバッファ(body)をメンバに持つPartitioningというクラスが存在する(ソースファイルはPartitioning.h)。Partitioningクラスは、bodyの他に変更をキャッシュとして、変更位置をあらわすstepPartionと変更量をあらわすstepLengthという2つの整数変数メンバを持っている。以降で、stepPartitionとstepLengthがどのように行数の更新処理を効率化してるか示す。

まず最初の挿入時。挿入前ではstepPartitionとstepLengthは両方とも0になっている。以下のように2行目に1文字追加したとすると、stepPartitionに2が代入され、stepLengthには1が代入される。

この段階ではbodyは全く書き換えず、2行目以降のバイト数を見るときに全て1足すことで、仮想的にbodyが変更されているように見なす。キャッシュがなければbodyの位置3以降を全て書き換えなかればならないところをbodyの変更に関してはゼロにすることが出来るようになっている。ここで、ただキャッシュしてるだけならば、次の変更などですぐにキャッシュの反映が必要になりあまり効果が得られないようにも見える。しかし、もちろんそうではなく、次の更新の位置によっては更新の分量を実際に少なくすることが出来る。

例えば次の変更が、4行目への1文字追加だとする。この変更がキャッシュされた結果を考えると、stepPartitionが4、つまり4行目以降がキャッシュの適用範囲になるはずである。ここで、この範囲が、今のキャッシュの適用範囲(2行目以降)の一部になっていることに注意する。つまり、同じ範囲に関しては次のstepPartitionで示されることになるので、stepLengthにさえ値を反映させれば、bodyを変更することなく済ますことが可能となる。

また、逆に、次の変更のstepPartitionで示される範囲の一部が前回の範囲になる場合は、前回と今回のstepPartitionの間のbodyの値からstepLengthを引き、次のstepLengthに反映することで、更新範囲を変えることが出来る(ソースではBackStep関数)。以下の図で示す。

ただし、これは必ずしもbodyを更新する量を減らす訳ではないためと思われるが、前回と今回のstepPartitionの位置の差が、全体の長さの10分の1以下の時だけにしてるようだ。また、以上の例は挿入の場合で書いたが、削除の場合でも同様になる(stepLengthには負の値が足される)。

まとめ

テキストエディタの実装方法の一部をscintillaを解析して見てみた。