2011年11月28日

Azure Tableによるデータ検索 - エンティティの結合(Join)

Windows Azure Tableに対するLINQクエリおよびREST APIでは、Joinによるエンティティの結合をサポートしていません。もちろんAzure Tableを使う時には、関連するエンティティのプロパティをコピーさせるなどしてJoinする必要がないようにデータモデルの設計を行います。

ただそれでも、仕様変更や機能追加によって当初想定していなかったデータの結合が必要になる場合もあります。そして、どうしてもデータモデルを変更したくないような場合には、Join処理が必要になってくると思います。

今までシステムを実装してきた中で行ったJoinの実装パターンを紹介します。

Azure Tableによるデータ検索処理

Windows AzureのTableストレージにおけるデータ検索では、基本的にはStorage Client LibraryのLinq to Azure Tableを使って開発するものの、AzureのLinqでは一部のLinq式しかサポートしておらず大部分のLinq式がサポート外になっています。http://msdn.microsoft.com/en-us/library/windowsazure/dd135725.aspx

そのため、Azure Tableにおいてデータ検索を行う際は、Linq to Objectを並行して利用する必要が出てきます。この使い分けについての基本的な考え方は表示対象エンティティをLinq to Azure Tableで取得し、その後のデータ加工処理をLinq to Objectで行うといった雰囲気です。ただしこの2つのLinqの使い分けでは、Linq to Azure Tableにおける取得可能なエンティティ数に制限がある(1000件未満、クエリ実行時間5秒以下 参考:http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx)ので、この制限にかからないよう十分にLinq to Azure Tableで絞込みを行う必要です。

2011年6月17日

Windows AzureでLog4Net RollingFileAppenderを使う

Windows Azure上で、Log4NetのRollingFileAppenderによりBlobストレージ上にログファイルを書きだすようにしてみたので、その方法についてまとめです。

やり方自体は単純で、RollingFileAppenderのログファイルの出力先を Windows Azureの Local Storageに設定し(これが意外と面倒)、出力先のLocal Storageの内容をDiagnostics Monitorで転送させてるだけです。またlog4netの設定ファイルlog4net.configはBlobストレージ上に置かれたものを使うようにしました。

2011年5月30日

CygwinでGit/GitHubを使うための環境構築

最近Gitを使い始めたんですが、コミットメッセージが文字化けるとか色々悩まされたんで、その結論的なものをまとめます。とりあえず、これでCygwinだけを使っている分には文字化けに悩まされずに済みそうです(やってることは文字コードをUTF-8で統一するように設定してるだけですが)

以下セットアップ手順です(既にGitHub上にアカウントを持ってる前提です)。

2011年5月16日

Azure Table Storageに非サポート型のプロパティを保存する

標準のAzure Table Storageにおいては以下の型のみが保存可能な型としてサポートされており、これ以外のプロパティとして持つエンティティを保存しようとするとエラーになってしまいます。

EDM型 CLR型 詳細
Edm.Binary byte[] バイトの配列 (サイズは最大 64 KB)
Edm.Boolean bool ブール値
Edm.DateTime DateTime UTC 時刻として表現された 64 ビット値 (サポートされている値の範囲は 1/1/1601 ~ 12/31/9999)
Edm.Double double 64 ビットの浮動小数点値
Edm.Guid Guid 128 ビットのグローバル一意識別子
Edm.Int32 Int32 or int 32 ビットの整数
Edm.Int64 Int64 or long 64 ビットの整数
Edm.String String UTF-16 でエンコードされた値 (サイズは最大 64 KB)

ただ、この制限に従って実際にシステムを作ろうとすると、ビジネスロジック層がドメインモデルとして設計されている場合に、ドメインオブジェクト群をストレージへ保存する際、そのオブジェクト構造をフラットに展開してからデータを保存しなければなりません。また、そのデータを読み込む時もフラットな構造から元のドメインオブジェクトに戻す処理が必要です。

データを読み書きするためにいちいちこんなことはやってられないので、今回はStorage Client APIをカスタマイズして、オブジェクト構造をそのままTable Storageへ保存できるようにします。

2011年4月6日

TableストレージにおけるExpect100ContinueとUseNagleAlgorithmの効果(2)~EGTを用いたエンティティ追加の場合

前回の記事に引き続き、Expect100ContinueおよびUseNagleAlgorithmの無効化が与えるパフォーマンスへの影響をエンティティグループトランザクション(以下、EGT)を用いた場合について測定してみた。

2011年4月4日

TableストレージにおけるExpect100ContinueとUseNagleAlgorithmの効果(1)~1件のエンティティ追加の場合

Windows AzureのTableストレージに関するホワイトペーパー「Windows Azure Table – テーブルストレージのプログラミング」(http://www.microsoft.com/japan/windowsazure/whitepapers/)では、ストレージのパフォーマンス向上のために、Expect100ContinueおよびUseNagleAlgorithmをfalseにすることが推奨されている(p67-68あたり)。

ただ実際のところ、この設定がどの程度パフォーマンスに影響を及ぼすか不明だったため、試してみた。

Windows AzureでZeroMQを使ってみた

Windows Azureのインスタンス間通信にzeromq(http://www.zeromq.org/)をWindows Azure上で使う機会があったので、使うまでの手順とか注意点を記す。またWindows Azureプロジェクトからzeromqを利用するために、clrzmq2(http://www.zeromq.org/bindings:clr)というzeromqのC#バインディングを用いる。

2011年3月31日

Visual StudioにDLLをコピペする際の注意点

64bit版Windows 7において、Visual Studio上にSystem32のDLLをコピペすると32bit版のDLLがペーストされる問題があった。わかってから考えると当然かとは思うものの結構ハマったのでメモ。

2011年3月10日

Azure SDK 1.4がリリースされました

今日、2010年3月9日にWindows Azure SDK 1.4がリリースされました。今回のアップデートでは、Windows Azure ConnectおよびWindows Azure CDN周りが強化されたようです。http://blogs.msdn.com/b/windowsazure/archive/2011/03/09/now-available-updated-windows-azure-sdk-and-windows-azure-management-portal.aspx
今回のアップデート内容は以下。(訳が間違っていたら教えてください、修正します)
  • Windows Azure Connect:
    • 管理UIでの複数管理者サポート
    • クライアントUIの更新(ステータス通知やDiagnostic周りの機能向上)
    • 非英語版WindowsでConnect Clientがインストールできるようになった
  • Windows Azure CDN:
    • Windows Azure CDN for Hosted Services: WebRoleやVMRoleのコンテンツをWindows Azure CDN経由で配信出来るようになった。静的なコンテンツは自動的にエッジサーバ(United States, Europe, Asia, Australia, South America)でキャッシュされ最大の帯域幅と低レイテンシでユーザにコンテンツが届けられる。
    • Serve secure content from the Windows Azure CDN: Azure管理ポータル上のオプション設定を有効にすると、https経由でAzure CDN上のコンテンツをセキュアに配信できる
追記:このアップデートでSDK 1.3のIIS LogがAzure Storageに転送できない問題が解決したようです。ココを見ると他にもいくつかのバグが修正されたらしい。

2011年3月4日

ODataObjCを使ったiOSアプリを作ってみた(4)~ソースコード公開しました

3回にわたって書いてきたアプリ「Cloud BookShelf」ですが、先ほどソースコードを公開しました。
↓からダウンロードできます。
http://cloudbookshelf.codeplex.com/
Apache License 2.0で配布してますので、ご自由に使ってください。
ODataObjC(OData Client for Objective-C)に関する情報がもっともっと増えていけばいいなと思います。

ODataObjCを使ったiOSアプリを作ってみた(3)~ODataObjCによるCRUD

前回で、Azure Tableに対してODataObjCライブラリを使う準備ができました。いよいよ本番ということで、ODataObjCを用いたAzure Tableに対するCRUD処理について書いていきます。
記事中で使用しているソースコードは、作成したiOSアプリ「Cloud BookShelf」のものです。記事内ではソースコードを切りだしているため分かりにくい部分もあると思いますが、後ほどソース全部を公開しますのでそっちも合わせて参考にしてください。

エンティティの定義

まず最初に、Azure Tableに保存するエンティティを表すクラスとして、Bookクラスを作成します。
Book.h
   1: #import <Foundation/Foundation.h>
   2: #import "Azure/TableEntry.h"
   3:  
   4: @interface Book : TableEntry {
   5:     NSString *title;
   6:     NSString *author;
   7:     NSString *rate;
   8: }
   9:  
  10: @property (nonatomic, retain) NSString *title;
  11: @property (nonatomic, retain) NSString *author;
  12: @property (nonatomic, retain) NSString *rate;
  13:  
  14: @end
このように、エンティティが保持する情報をプロパティとして定義します。(Book.mはそれぞれ定義したpropertyをsynthesizeするだけ)

エンティティの追加

そして定義したエンティティをAzure Table上に追加します。
このメソッドが定義されたクラスでは、tableContextプロパティとしてObjectContextを保持していて、そのObjectContextを使ってエンティティを保存しています。(学習用のアプリなので強制的にParitionKey・RowKeyを再設定していますが、既存のエンティティをパラメータにしても新規追加されてしまうため、ちゃんと使いたいときはその辺りも考えないといけない)
   1: - (Book *) insert:(Book *)aBook {
   2:     NSLog(@"Insert");
   3:     [aBook setPartitionKey:[[UIDevice currentDevice] uniqueIdentifier]]; //PartitionKeyとしてiPhoneのUDIDを使う
   4:     [aBook setRowKey:[ODataGUID GetNewGuid]]; //RowKeyはGUIDにしておく
   5:     
   6:     [self.tableContext addObject:@BOOK_ENTITY_NAME object:aBook];
   7:     
   8:     if ( !self.enabledBatch ){
   9:         //バッチモードでない場合だけSaveChangesする
  10:         [self commit];
  11:     }
  12:     
  13:     return aBook;
  14: }

エンティティの検索

次にAzure Table上に保存されたエンティティの検索です。以前この記事で書いたとおりオリジナルのODataObjCでは日本語をパラメータに使えません。(まぁ、自力でURLエンコードさせればいいんですが。。。)
そこで、その記事中に載せた簡易クエリビルダを利用します。
   1: - (NSArray *) find:(NSString *)aQuery {
   2:     NSString *deviceId = [[UIDevice currentDevice] uniqueIdentifier];
   3:     
   4:     //デフォルトでPartitionKeyによる絞り込みを入れておく
   5:     NSString *q = [NSString stringWithFormat:@"PartitionKey eq '%@'%@%@", deviceId, ( aQuery != @"" ? @" and " : @"" ), aQuery];
   6:     
   7:     DataServiceQuery *query = [[DataServiceQuery alloc] initWithUri:@BOOK_ENTITY_NAME objectContext:self.tableContext];
   8:     [query filter:q];
   9:     
  10:     QueryOperationResponse *response = [query execute];
  11:     NSArray *result = [response getResult];
  12:     
  13:     NSLog(@"Query Result");
  14:     for(Book *r in result){
  15:         NSLog(@"Book(title=%@, author=%@, rate=%@) PK:%@ RK:%@ Timestamp:%@", r.title, r.author, r.rate, r.PartitionKey, r.RowKey, r.Timestamp);
  16:     }
  17:     
  18:     return result;
  19: }
  20:  
  21: - (NSArray *) findByQueries:(Queries *)aQueries {
  22:     return [self find:aQueries.filterString];
  23: }
呼び出し側はこんな感じ。
   1: Queries *aQueries = [Queries with:@"title" eq:text];
   2: self.books = [bookService findByQueries:aQueries];

エンティティの更新

次のエンティティの更新はエンティティを追加とほぼ同じ。PartitionKeyとRowKeyは既に付いているためセットしないのと追加するときはaddObjectだったのをupdateObjectにするだけ。
ただし要注意は、updateObjectの引数にしているエンティティはそのObjectContextで追跡されていなければならないこと。具体的には、同じObjectContextで追加したものや検索して取得したものしか更新はできない
ソースコードはこんな感じ。
   1: - (Book *) update:(Book *)aBook {
   2:     [self.tableContext updateObject:aBook];
   3:     
   4:     if ( !self.enabledBatch ){
   5:         //バッチモードでない場合だけSaveChangesする
   6:         [self commit];
   7:     }
   8:     
   9:     return aBook;
  10: }

エンティティの削除

最後にエンティティの削除ですが、これも追加や更新と同じようなコーディングでOKです。addObjectやupdateObjectをdeleteObjectに書き換えるだけ。
ただし、更新と同様にObjectContextで追跡済みのエンティティしか削除出来ません
   1: - (void) remove:(Book *)aBook {
   2:     [self.tableContext deleteObject:aBook];
   3:     if ( !self.enabledBatch ){
   4:         [self commit];
   5:     }
   6: }
こんな感じで削除できます。

2011年3月3日

ODataObjCを使ったiOSアプリを作ってみた(2)~ODataObjCの使い方

ODataObjCを使ったiOSアプリを作ってみた(1)で紹介した「Cloud BookShelf」を作った際に、ODataObjCの使い方に関して色々ハマったところがあったのでメモ書き。
まず、ODataObjC付属のドキュメントに記載されたコードを見つつ、とりあえず必要そうな処理だけコピってやってみたところ、うまく動かなかった。Fiddler使ってHTTPリクエストの内容とかを見ていたところ、Azure REST APIで必要な署名が付与されておらず、認証エラーになっていた。
以下は付属ドキュメント上でエンティティをAzure Tableに保存するサンプルですが、
#import “Context/ObjectContext.h”
#define AZURE_SERVICE_URL “http://<Account>.table.core.windows.net”
#define AZURE_ACCOUNT_NAME “specify your account name here”
#define AZURE_ACCOUNT_KEY “specify your account key here”
 
@interface Person :TableEntry
{
        NSString *m_Name;
        NSString *m_Age;
}
 
@property(nonatomic,retain,getter=getName, setter = setName)NSString *m_Name;
@property(nonatomic,retain,getter=getAge, setter = setAge)NSString *m_Age;
 
-(id) initWithUri:(NSString *)aUri;
 
@end
 
@implementation Person
@synthesize m_Name,m_Age;
 
-(id) initWithUri:(NSString *)aUri
{
       self=[super initWithUri:aUri];
       return self;
}
@end
 
@try
{     
       AzureTableCredential *cred=[[AzureTableCredential alloc]
       initWithAccountName:AZURE_ACCOUNT_NAME accountKey:AZURE_ACCOUNT_KEY userPathStyleUrl:YES];
      
       ObjectContext *proxy=[[ObjectContext alloc]
       initWithUri:AZURE_SERVICE_URL credentials:cred dataServiceVersion:@"1.0"];
      
       [proxy setODataDelegate:self];
 
       Person *tableEntry=[[Person alloc]initWithUri:@""];
       [tableEntry setPartitionKey:@"Partition1"];
       [tableEntry setRowKey:@"Row1"];
       [tableEntry setName:@"TableEntry1"];
       [tableEntry setAge:@"37"];
             
       [proxy addObject:@"Person" object:tableEntry];//Person is the table name
       [proxy saveChanges];
}
      
@catch(NSException *e)
{
       NSLog(@"Exception:%@:%@",[e name],[e reason]);
}
このうち、必要ないと思って削っていた
[proxy setODataDelegate:self];
の部分が原因で、このライブラリではODataDelegateのonBeforeSendメソッド内で署名を付与するような設計になっているようです。まぁ必要ないと思って削ってしまった自分が悪いんですが。。。
しかしここで気になるのは、Azure Table用の署名の生成処理自体はユーティリティ(AzureTableUtil)として提供されているものの、このODataDelegateのonBeforeSendメソッドは自分で実装する必要があるという点。
こんな定型処理、なぜ自分で実装しないといけないんだ~
ということで、とりあえずこのObjectContextの生成と同時にAzure Table向けの署名を付けるようなクラスを作成する使い方がベストだと思います(多分ライブラリの作者もこんな使い方を想定してるはず)。
こんな感じ。
#import "Context/ObjectContext.h"
#import "Context/DataServiceQuery.h"
#import "Common/AzureTableUtil.h"
#import "Credential/AzureTableCredential.h"

#define TABLE_URL_FORMAT "http://%@.table.core.windows.net"

@interface StorageClient : NSObject<ODataDelegate, BlobContextDelegate> {
    NSString *tableServiceURL;
    NSString *accountName;
    NSString *accountKey;
}

@property(retain, nonatomic) NSString *tableServiceURL;
@property(retain, nonatomic) NSString *accountName;
@property(retain, nonatomic) NSString *accountKey;

- (id) initWithAccountName:(NSString *)aName andKey:(NSString *)aKey;
- (ObjectContext *) createTableContext;

@end


@implementation StorageClient

@synthesize tableServiceURL, accountName, accountKey, dateFormatter;

- (id) initWithAccountName:(NSString *)aName andKey:(NSString *)aKey {
    self.tableServiceURL = [NSString stringWithFormat:@TABLE_URL_FORMAT, aName];
    self.accountName = aName;
    self.accountKey = aKey;
    
    return self;
}

- (ObjectContext *) createTableContext {
    AzureTableCredential *cred = [[AzureTableCredential alloc] initWithAccountName:self.accountName accountKey:self.accountKey userPathStyleUrl:YES];
    ObjectContext *ctx = [[ObjectContext alloc] initWithUri:self.tableServiceURL credentials:cred dataServiceVersion:@"1.0"];
    
    [ctx setODataDelegate:self];
    
    return ctx;
}

- (void) onBeforeSend:(HttpRequest*)request{
    AzureTableUtil *util=[[AzureTableUtil alloc] initWithAccountName:self.accountName accountKey:self.accountKey usePathStyleUri:NO];
    [[request getHeaders] CopyFrom:[util getSignedHeaders:[request getUri]]];
}

@end

ODataObjCを使ったiOSアプリを作ってみた(1)

ODataObjCを使ってみる練習としてiOSアプリを作ってみました。

書籍管理っぽいことを行うようなアプリで、本の登録/検索/更新/削除が行えます。ついでにAzure Blob上に本の表紙画像をアップロードする機能をつけてみました。(iOSアプリをちゃんと作るのはこれが初めてだったので、ODataObjCの使い方よりもObjective-Cに悩まされましたが。。。)

まずは簡単にアプリ紹介です。
ちなみにアプリ名は、Azure上に本棚を用意するってことでCloud BookShelfと付けてみました(安直だなー。。。)。

まず起動直後の画面ですが、こんな感じで登録された本の一覧が表示されます。また、本にはそれぞれタイトル、著者、レートといった情報(これはAzure Table上に保存)と表紙画像一枚(こっちはAzure Blob上に保存)が付いてます。

IMG_0173

本をタップすると、本の情報を編集する画面になります。

IMG_0175

この画面で好きにタイトルとか著者、レートを書き換え、右上の保存ボタンをタップすると更新された内容でAzure Table上にデータを保存します。

一番下の[表紙を見る/変更する]をタップすると

IMG_0176

表紙として登録された画像が表示されます。で、右上の表紙を変更をタップするとiPhoneのカメラが起動し、撮影した画像で表紙が置き換えらるって感じです。

簡単なアプリケーションですが、iOSプログラミングの癖とかODataObjCの使い方、Azure BlobのREST APIとかいろんなことがわかりました。これを機会にiOSアプリを作ってみようかな~と。

ODataObjCで日本語パラメータを使う方法

OData Client for Objective-C(http://odataobjc.codeplex.com/)を触っていて気づいたことが、Azure Tableストレージを対象に日本語をパラメータとして検索するフィルタクエリを実行すると、Cocoaのライブラリ側で不正なURLという例外が発生してしまう。

なぜそんなことになっているかソースコードを見ていたら、ライブラリ内部ではフィルタクエリのURLエンコードを行っていないことがわかった。また、そのソースの近くにURLエンコードを試みたらしくコメントアウトされたコードがあるものの、それを外しても動かない。

原因をよくよく考えてみると、Azure Tableストレージに対するエンティティのクエリ構文では、全体をURLエンコードするのではなく個々のフィルタパラメータをURLエンコードしなくてはならないことがわかった。

例えば、

$filter=(PartitionKey eq 'あいうえお')

こんなクエリを投げたいときは

$filter=(PartitionKey%20eq%20%27%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%27)

ではなく、

$filter=(PartitionKey%20eq%20'%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A')

のようにしなければならない(%20は+でもOK)。

しかし、このODataObjCではこの$filter=の後の文字列を直接メソッドのパラメータとして受け取るように作られているため、ライブラリ内部で対応するのは難しい感じです。(だから諦めたのかな。。。)

しかしこのままじゃ使えないため、$filter=の後の文字列を生成する簡単なクエリビルダを作成し、その中でURLエンコードするようにして対処してみました。というわけで作成したクエリビルダのソースを置いておきます。(URLエンコードの処理はODataObjCライブラリのUtilityクラスを利用してます)

Queries.h

#import <Foundation/Foundation.h>
#import "Utility.h"


@interface Queries : NSObject {
@private
NSString *filterString;
}

@property (nonatomic, retain) NSString *filterString;

- (id) initWithString:(NSString *)aFilterString;

+ (Queries *) withEmpty;
+ (Queries *) with:(NSString *)name eq:(NSString *)value;
+ (Queries *) with:(NSString *)name gt:(NSString *)value;
+ (Queries *) with:(NSString *)name ge:(NSString *)value;
+ (Queries *) with:(NSString *)name lt:(NSString *)value;
+ (Queries *) with:(NSString *)name le:(NSString *)value;
+ (Queries *) with:(NSString *)name ne:(NSString *)value;

- (Queries *) and:(Queries *)aQueries;
- (Queries *) or:(Queries *)aQueries;
- (Queries *) not;

@end

 

Queries.m

#import "Queries.h"


@implementation Queries

@synthesize filterString;

- (id) initWithString:(NSString *)aFilterString {
self.filterString = aFilterString;

return self;
}

+ (Queries *) withEmpty {
return [[[Queries alloc] initWithString:@""] autorelease];
}

+ (Queries *) with:(NSString *)name eq:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ eq '%@')", name, [Utility URLEncode:value]]] autorelease];
}

+ (Queries *) with:(NSString *)name gt:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ gt '%@')", name, [Utility URLEncode:value]]] autorelease];
}

+ (Queries *) with:(NSString *)name ge:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ ge '%@')", name, [Utility URLEncode:value]]] autorelease];
}

+ (Queries *) with:(NSString *)name lt:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ lt '%@')", name, [Utility URLEncode:value]]] autorelease];
}

+ (Queries *) with:(NSString *)name le:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ le '%@')", name, [Utility URLEncode:value]]] autorelease];
}

+ (Queries *) with:(NSString *)name ne:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ ne '%@')", name, [Utility URLEncode:value]]] autorelease];
}

- (Queries *) and:(Queries *)aQueries {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ and %@)", self.filterString, aQueries.filterString]] autorelease];
}

- (Queries *) or:(Queries *)aQueries {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ or %@)", self.filterString, aQueries.filterString]] autorelease];
}

- (Queries *) not {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(not %@)", self.filterString]] autorelease];
}

- (void) dealloc {
[self.filterString release];

[super dealloc];
}

@end

 

このクエリビルダを使うと上記のクエリは、

[Queries with:@"PartitionKey" eq:@"あいうえお"].filterString;

と書ける。

OData Client for Objective-CのAPIリファレンス

OData Client for Objective-C(http://odataobjc.codeplex.com/)であるが、配布しているものをダウンロードしてみると、ライブラリのバイナリ&ソースコードとサンプルアプリ、ユーザガイドが入っている。

このユーザガイドを見ればなんとなくの使い方はわかるものの、ユーザガイドに書いてあることとちょっとでも違うことをやろうとすると、なんと「APIリファレンスが無い」ことに気付く。

ライブラリとして配布しているのに、APIリファレンスがないとはなんということだと思うものの、ちょっと冷静になってソースコードを見ていると。。。

/**
* To add a filter query option.
*
* @param <string> expression
* @return <DataServiceQuery> Self reference that includes the requested
* filter option
* @throws DataServiceRequestException
*/
- (DataServiceQuery*) filter:(NSString*)anExpression
{
return [self addQueryOption:@"$filter" query:anExpression];
}

なんと、ソースコードの中にはちゃんとAPIリファレンスが書かれていました!しかも、この記述形式がDoxygenのものっぽいなと思い、試しにDoxygenでドキュメントを吐き出させてみると、しっかりAPIリファレンスが出力されます。

まぁ、全てのメソッドにリファレンスが付いているわけではないものの、このライブラリを使う際にコード上で触る範囲のものにはだいたい書かれている感じです。

ということで、作成したAPIリファレンスを置いておきます。

odataobjc-v1.1-apidoc.zip

XCodeでODataObjCを使う方法

http://odataobjc.codeplex.com/ で配布されているOData Client for Objective-Cだが、XCodeから使うときドキュメントに書いてある手順では問題があったのでメモ書き。

その問題とは、

配布されているバイナリがCPU依存のバイナリになっているため、動作させる環境(実機orシミュレータ)を変えるたびに参照するバイナリをいちいち手動で切替えなければならない

こと。

いちいち毎回こんなことはやってられないため、とりあえずはソースコード一式をXCodeのプロジェクトにいれてしまうのが簡単。別の方法としては、配布されているソースコード一式を元にユニバーサルバイナリ化したFrameworkを作成する方法がある。

とりあえず参考までに、iOS上で動作する自作Frameworkを作る方法が以下のページで書かれていたので紹介しておく。

http://cocoadays.blogspot.com/2010/11/ios-static-library-5-framework.html

本当はこの手順に従って、ユニバーサルバイナリ化したものを使うのが正攻法だとは思うものの、現状の1.1バージョンだと色々問題を抱えていたので、ソースコードを取り込んで使うのが吉かも。