64bit版Windows 7において、Visual Studio上にSystem32のDLLをコピペすると32bit版のDLLがペーストされる問題があった。わかってから考えると当然かとは思うものの結構ハマったのでメモ。
2011年3月31日
2011年3月10日
Azure SDK 1.4がリリースされました
今回のアップデート内容は以下。(訳が間違っていたら教えてください、修正します)
- 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上のコンテンツをセキュアに配信できる
2011年3月4日
ODataObjCを使ったiOSアプリを作ってみた(4)~ソースコード公開しました
↓からダウンロードできます。
http://cloudbookshelf.codeplex.com/
Apache License 2.0で配布してますので、ご自由に使ってください。
ODataObjC(OData Client for Objective-C)に関する情報がもっともっと増えていけばいいなと思います。
ODataObjCを使ったiOSアプリを作ってみた(3)~ODataObjCによる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
エンティティの追加
そして定義したエンティティを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付属のドキュメントに記載されたコードを見つつ、とりあえず必要そうな処理だけコピってやってみたところ、うまく動かなかった。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];
しかしここで気になるのは、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上に保存)が付いてます。
本をタップすると、本の情報を編集する画面になります。
この画面で好きにタイトルとか著者、レートを書き換え、右上の保存ボタンをタップすると更新された内容でAzure Table上にデータを保存します。
一番下の[表紙を見る/変更する]をタップすると
表紙として登録された画像が表示されます。で、右上の表紙を変更をタップすると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リファレンスを置いておきます。
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バージョンだと色々問題を抱えていたので、ソースコードを取り込んで使うのが吉かも。