exercise 1.30と1.32
SICPのexercise 1.30と1.32
Excersize 1.30
a から bまで値をnext手続きで増分していき、
それらの値にterm手続きで指定した演算を適用した値を合計する手続き.
(term, nextとも1つの引数を要求する手続き)
例で、linear recursiveの例が出ているので、それのiterative版(末尾再帰)をつくるというもの.
例のlinear recursiveは以下のとおり.
(define (sum term a next b) (if (> a b) 0 (+ (term a) (sum term (next a) next b) ) ) )
で、解答してみたのが以下のとおり.
増分値といままで加算してきた値を引数に渡しながら反復すれば、よいはず.
(define (sum term a next b) (define (iter a result) (if (> a b) result (iter (next a) (+ result (term a) ) ) ) ) (iter a 0) )
これを使って、指定した範囲の連続したで整数値の合計を算出する手続きをつくって(linear recursiveの例と同じ)
(define (sum-integers a b) (define (inc x) (+ x 1) ) (define (identity x) x) (sum identity a inc b) )
実際にあたいを求めてみました。
(sum-integers 1 10) 55 ; 1 + 2 + 3 ... 10 (sum-integers 1 100) 5050 ; 1 + 2 + 3 ... 100
Excersize 1.32
a から bまで値をnext手続きで増分していき、
それらの値にterm手続きで指定した演算を適用した値をcombinerで指定した手続きで積み上げる手続きを書く.
excersize 1.30のより抽象化(汎用化)した版で、combiner手続きとして、加算や積をとる手続きをとる.
linear recursive版
(define (accumulate combiner null-value term a next b) (if (> a b) null-value (combiner (term a) (accumulate combiner null-value term (next a) next b ))) )
末尾再帰
iterative(末尾再帰)版
(define (accumulate combiner null-value term a next b) (define (iter a result) (if (> a b) result (iter (next a) (combiner result (term a) ) ) ) ) (iter a null-value) )
以上(いずれか)を使用して、累計をよる手続きと積をとる手続きを定義
(define (sum term a next b) (accumulate + 0 term a next b) )
(define (product term a next b) (accumulate * 1 term a next b) )
さらに、それを使って、連続した整数の累計と積をとる手続きを定義
(define (sum-integers a b) (define (inc x) (+ x 1) ) (define (identity x) x) (sum identity a inc b) ) ; (define (product-integers a b) (define (inc x) (+ x 1) ) (define (identity x) x) (product identity a inc b) )
そして、実際に値を求める
gosh> (sum-integers 1 10) 55 ; 1 + 2 + 3 ... 10 gosh> (sum-integers 1 100) 5050 ; 1 + 2 + 3 ... 100 gosh> (product-integers 1 5) 120 ; 1 * 2 * 3 * 4 * 5 gosh> (product-integers 1 6) 720 ; 1 * 2 * 3 * 4 * 5 * 6 gosh>
NSURLConnectionを利用しての非同期、並行ダウンロード
iPhoneアプリの開発で、複数の画像を非同期にダウンロードしたかったので、NSURLConnectionを利用しての非同期、並行ダウンロードクラスを実装してみました。
基本的な方法
ダウンロード対象のURLはQueueのかたちに(実際はNSMutableArray)で登録されるようなかたちとする.
Queueに追加されたダウンロード対象URLの要素を監視する新規スレッドを起動させ、
NSURLConnectionにより非同期ダウンロード処理の起動を行っていく.
NSURLConnectionよるダウンロード完了などは、NSURLConnectionのDelegateである、
各ダウンロード要素管理のクラス(QueuedURLDownloaderElem)のインスタンスに通知され、さらに
このダウンローダー(QueuedURLDownloader)に設定されてるDelegate(
QueuedURLDownloaderDelegateプロトコルの実装)のメソッドに転送することにより、ダウンロード
された結果を得ることができる。
構成要素 - 以下のクラス、プロトコルで構成する
QueuedURLDownloader - ダウンローダーの本体、実行待ちキューを管理、順番に実行していく.
QueuedURLDownloaderElem - ダウンロード要素(1つのURLに対応)
QueuedURLDownloaderDelegate - ダウンロードの通知を受けるDelegateのプロトコル
Interface部
#import <Foundation/Foundation.h> /*! @class QueuedURLDownloader NSURLConnectionを利用して非同期にファイルのダウンロードを行う. 初期化時に同時にダウンロードする最大を指定, addURL:withUserInfoでダウンロード対象のURLをQueueに登録してダウンロードを行っていく。 この登録を行うことにより、非同期にダウンロードが行われる. QueuedURLDownloaderDelegateプロトコルを実装したDelegateクラスの メソッド(didFinishLoading:withUserInfo)によりダウンロードした内容を取得する。 以下の手順で、ダウンロードの処理を起動、終了をさせる。 ダウンロードの起動処理は、新規スレッドで非同期に行われる。 1.このクラスインスタンスの初期化. 2.delegateにQueuedURLDownloaderDelegateプロトコルを実装したインスタンスを設定. 3.startメソッドでダウンロード開始(URLが追加されたらダウンロードがはじめる状態にする). 4.ダウンロード対象のURLを追加していく(addURL:withUnserInfo メソッド). 5.finishメソッドでこれ以上、ダウンロードするものがないことを通知. 例. // 初期化 QueuedURLDownloader *downloader = [[QueuedURLDownloader alloc] initWithMaxAtSameTime:3]; downloader.delegate = self; // QueuedURLDownloaderDelegateプロトコルを実装したもの // 開始 [downloader start]; // NSDictionary *dict; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value1, @"key1", nil] ; [downloader addURL:[NSURL URLWithString:urlString1 ] withUserInfo:dict]; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value2, @"key2", nil] ; [downloader addURL:[NSURL URLWithString:urlString2 ] withUserInfo:dict]; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value3, @"key3", nil] ; [downloader addURL:[NSURL URLWithString:urlString3 ] withUserInfo:dict]; ... // これ以上、ダウンロードするものがないことを通知 [downloader finishQueuing]; ======== 実装について 新規スレッドにより、Queueに追加されたダウンロード対象URLの要素を監視して、順番に NSURLConnectionにより非同期ダウンロード処理の起動を行っていく. NSURLConnectionよるダウンロード完了などは、NSURLConnectionのDelegateである、 各ダウンロード要素管理のクラス(QueuedURLDownloaderElem)のインスタンスに通知され、さらに このダウンローダー(QueuedURLDownloader)に設定されてるDelegate( QueuedURLDownloaderDelegateプロトコルの実装)のメソッドに転送することにより、ダウンロード された結果を得ることができる。 */ @protocol QueuedURLDownloaderDelegate; @interface QueuedURLDownloader : NSObject { NSInteger maxAtSameTime; NSObject<QueuedURLDownloaderDelegate> *delegate; @private NSMutableArray *waitingQueue; NSMutableDictionary *runningDict; BOOL queuingFinished; BOOL completed; // 処理が開始されている? BOOL started; // 停止が要求されている? BOOL stoppingRequired; NSInteger completedCount; NSLock *lock; NSTimeInterval timeoutInterval; } @property (nonatomic, retain) NSObject<QueuedURLDownloaderDelegate> *delegate; @property (readonly) NSInteger completedCount; /*! Timeout時間,単位:秒,Default 10.0秒 */ @property (nonatomic) NSTimeInterval timeoutInterval; /*! 同時にダウンロードする最大数を指定しての初期化 */ - (id) initWithMaxAtSameTime:(NSInteger)count; /*! ダウンロードするURLを追加 */ - (void) addURL:(NSURL *)URL withUserInfo:(NSDictionary *)info; /*! ダウンロードを開始 */ - (void) start; /*! @method requireStopping @discussion ダウンロード処理を停止を要求,実際に停止されたかは、isCompletedで確認する必要がある */ - (void) requireStopping; /*! @method isCompleted @discussion Download処理が完了しているかの判定 */ - (BOOL) isCompleted; /*! @method waitCompleted @discussion Download処理が完了するまで待つ,まだ開始されていない場合は、すぐに返る。 */ - (void) waitCompleted; /*! これ以上、ダウンロードURLするものがないことを通知. この通知後、ダウンロード要素の追加は受け付けられず、現在実行待ち、実行中のダウンロードの処理が完了すれば、 ダウンロード処理のスレッドが終了する。 */ - (void) finishQueuing; /*! ダウンロード実行待ち要素数 */ - (NSInteger)waitingCount; /*! ダウンロード実行中の要素数 */ - (NSInteger)runningCount; /*! 同時にダウンロード処理を行う最大数 */ @property (nonatomic) NSInteger maxAtSameTime; /*! 現在のQueueの要素数 */ //@property (readonly) NSInteger count; @end /*! ダウンローダー(QueuedURLDownloader)のDelegate didFinishLoading:withUserInfoメソッドにより、QueuedURLDownloaderのaddURL:withUserInfo で指定したURLからのダウンロード通知を受け、ファイルの内容を得る。 */ @protocol QueuedURLDownloaderDelegate /*! @method didFinishLoading:withUserInfo: @discussion Download完了の通知. @param data ダウンロードしたデータ @param info QueuedURLDownloaderのaddURL:withUserInfoで渡した userInfo */ - (void)didFinishLoading:(NSData *)data withUserInfo:(NSDictionary *)info; /*! @method downloadDidFailWithError:withUserInfo: @discussion Download時のエラー発生通知. @param error @param info QueuedURLDownloaderのaddURL:withUserInfoで渡した userInfo */ - (void)downloadDidFailWithError:(NSError *)error withUserInfo:(NSDictionary *)info; @optional /*! @method didReceiveResponse:withUserInfo: @discussin 指定URL先からのレスポンスの通知 @param response @param info QueuedURLDownloaderのaddURL:withUserInfoで渡した userInfo */ - (void)didReceiveResponse:(NSURLResponse *)response withUserInfo:(NSDictionary *)info; /*! @method didReceiveResponse:withUserInfo: @discussin 指定URL先からのデーターの通知(部分的にデータ受信した場合も通知されることがある) @param data @param info QueuedURLDownloaderのaddURL:withUserInfoで渡した userInfo */ - (void)didReceiveData:(NSData *)data withUserInfo:(NSDictionary *)info; /*! @method didAllCompleted @discussion すべてのダウンロードが完了したときの通知 */ - (void)didAllCompleted; /*! @method dowloadCanceled @discussion ダウンロードがキャンセルされたときの通知 */ - (void)dowloadCanceled; @end
実装部
#import "QueuedURLDownloader.h" /*! @class QueuedURLDownloaderElem @discussion ダウンロード要素.ダウンロード元URLごとに生成する. URLコネクションの管理、データ取得時の通知メソッドの起動などを行う. */ @interface QueuedURLDownloaderElem : NSObject { @private NSDictionary *userInfo; NSURL *URL; NSObject<QueuedURLDownloaderDelegate> *delegate; NSURLConnection *con; QueuedURLDownloader *queuedDownloader; NSMutableData *data; } /*! ダウンロード元URL */ @property (nonatomic, readonly) NSURL *URL; /*! データ取得、接続などの通知先Delegate */ @property (nonatomic, readonly) NSObject<QueuedURLDownloaderDelegate> *delegate; /* ダウンロード元URLへのコネクション */ @property (nonatomic, retain) NSURLConnection *con; /*! これを要素として含むDownloader */ @property (nonatomic, readonly) QueuedURLDownloader *queuedDownloader; /*! 付加情報 */ @property (nonatomic, readonly) NSDictionary *userInfo; - (id) initURL:(NSURL *)URL WithUserInfo:(NSDictionary *)info withDelegate:(NSObject<QueuedURLDownloaderDelegate> *) myDelegate withQueuedDownloader: (QueuedURLDownloader *)downloader; // NSURLConnection delegate - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error; - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)fragment; - (void)connectionDidFinishLoading:(NSURLConnection *)connection; @end @interface QueuedURLDownloader(Private) /*! @method run @discussion 未実行Queueからダウンロード対象情報を取り出し、ダウンロードを起動を起動. Queueが空になるまで繰り返す. */ - (void) run; /*! @method finishDownload: @discussion 1要素のダウンロードが完了したときの処理. QueuedURLDownloaderElemから通知される. 後かたずけをする. */ - (void) finishDownload:(QueuedURLDownloaderElem *)elem; @end @implementation QueuedURLDownloaderElem @synthesize userInfo; @synthesize delegate; @synthesize URL; @synthesize con; @synthesize queuedDownloader; - (id) initURL:(NSURL *)RequestURL WithUserInfo:(NSDictionary *)info withDelegate:(NSObject<QueuedURLDownloaderDelegate> *) myDelegate withQueuedDownloader: (QueuedURLDownloader *)downloader { self = [super init]; URL = RequestURL; [URL retain]; userInfo = info; [userInfo retain]; delegate = myDelegate; [delegate retain]; con = nil; queuedDownloader = downloader; [queuedDownloader retain]; data = nil; return self; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)fragment { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"connection:didReceiveData"); if(fragment) { //NSLog(@"data length = %d", [fragment length]); } if ([delegate respondsToSelector:@selector(didReceiveData:withUserInfo:)]) [delegate didReceiveData:fragment withUserInfo:userInfo]; if(!data) { data = [[NSMutableData alloc] init]; } [data appendData:fragment]; [pool drain]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"connection:didFailWithError - %@", error); if ([delegate respondsToSelector:@selector(downloadDidFailWithError:withUserInfo:)]) [delegate downloadDidFailWithError:error withUserInfo:userInfo]; [self.queuedDownloader finishDownload:self]; [pool drain]; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"connection:didReceiveResponse "); if ([delegate respondsToSelector:@selector(didReceiveResponse:withUserInfo:)]) [delegate didReceiveResponse:response withUserInfo:userInfo]; [pool drain]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSLog(@"connection:connectionDidFinishLoading "); if(data) { NSLog(@"data length = %d", [data length]); } if(self.userInfo) { /* NSEnumerator *enumerator = [self.userInfo keyEnumerator]; id key; while ((key = [enumerator nextObject])) { // NSLog(@"key = %@, value = %@", key, [self.userInfo valueForKey:key]); } */ } if ([delegate respondsToSelector:@selector(didFinishLoading:withUserInfo:)]) [delegate didFinishLoading:data withUserInfo:userInfo]; [self.queuedDownloader finishDownload:self]; [pool drain]; NSLog(@"drain"); } - (void)dealloc { if(userInfo) [userInfo release]; if(delegate) [delegate release]; if(con) [con release]; if(URL) [URL release]; if(queuedDownloader) [queuedDownloader release]; if(data) [data release]; [super dealloc]; } @end @implementation QueuedURLDownloader @synthesize maxAtSameTime; @synthesize delegate; @synthesize completedCount; @synthesize timeoutInterval; - (id) init { [self initWithMaxAtSameTime:1]; return self; } - (id) initWithMaxAtSameTime:(NSInteger)count { self = [super init]; if(self == nil) return self; maxAtSameTime = count; waitingQueue = [[NSMutableArray alloc] init]; runningDict = [[NSMutableDictionary alloc] init]; queuingFinished = NO; completed = NO; completedCount = 0; timeoutInterval = 10.0; lock = [[NSLock alloc] init]; return self; } /* - (NSInteger) count { return [queue count]; } */ - (void) addURL:(NSURL *)URL withUserInfo:(NSDictionary *)info { QueuedURLDownloaderElem *elem = [[QueuedURLDownloaderElem alloc] initURL:URL WithUserInfo:info withDelegate:self.delegate withQueuedDownloader:self]; [lock lock]; if(!queuingFinished) { // もうURLを追加しないよう通知されている。 [waitingQueue addObject:elem]; } [lock unlock]; } - (void)finishQueuing { queuingFinished = YES; } - (void) start { [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; } - (void) requireStopping { [lock lock]; stoppingRequired = YES; [lock unlock]; } - (BOOL) isCompleted { BOOL ret; [lock lock]; ret = completed; [lock unlock]; return ret; } - (void) waitCompleted { BOOL ret = NO; while (YES) { [lock lock]; ret = completed || !started; [lock unlock]; if(ret == YES) { if(stoppingRequired && started && [delegate respondsToSelector:@selector(dowloadCanceled)] ) { [delegate dowloadCanceled]; } break; } [NSThread sleepForTimeInterval:0.01f]; } } - (void) run { [lock lock]; started = YES; [lock unlock]; while (1) { BOOL downloading = NO; [lock lock]; if([waitingQueue count] == 0 && [runningDict count] == 0 && queuingFinished || stoppingRequired == YES) { [lock unlock]; break; } if([waitingQueue count] > 0 && [runningDict count] < maxAtSameTime) { // 未実行のもののうちで一番先にQueueに登録されたものを得る // autoreleaseのObjectがLeakするので AutoReleasePool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; QueuedURLDownloaderElem *elem = [waitingQueue objectAtIndex:0]; [waitingQueue removeObjectAtIndex:0]; NSURLRequest *request = [[NSURLRequest alloc] initWithURL:elem.URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:timeoutInterval]; // DownLoadを開始 elem.con = [[NSURLConnection alloc] initWithRequest:request delegate:elem]; [request release]; [runningDict setObject:elem forKey:elem.URL]; [elem.con start]; downloading = YES; [lock unlock]; [[NSRunLoop currentRunLoop] run]; [pool drain]; } else { [lock unlock]; } if(downloading == NO) { // LoopでCPU率があがらないように少しsleep [NSThread sleepForTimeInterval:0.01f]; } } // 完了の通知 if(delegate == nil) return; if([delegate respondsToSelector:@selector(didAllCompleted)] ) { [lock lock]; if(stoppingRequired) { [lock unlock]; } else { [lock unlock]; [delegate didAllCompleted]; } } [lock lock]; completed = YES; [lock unlock]; } - (void) finishDownload:(QueuedURLDownloaderElem *)elem { NSURL *URL = elem.URL; [lock lock]; if(elem) { [runningDict removeObjectForKey:URL]; [elem release]; completedCount += 1; } [lock unlock]; } - (NSInteger)waitingCount { NSInteger result; [lock lock]; result = [waitingQueue count]; [lock unlock]; return result; } - (NSInteger)runningCount { NSInteger result; [lock lock]; result = [runningDict count]; [lock unlock]; return result; } - (void) dealloc { if(delegate) { [delegate release]; delegate = nil; } [waitingQueue release]; [runningDict release]; [lock release]; [super dealloc]; } @end
メモリリーク対応
NSURLConnectionの処理の中で、autoReleaseオブジェクトのMemory Leak があるようなので、runメソッド内にAutoReleaseプールを追加(2010.4.28)
同期処理の問題
ダウンロード中に、Viewが閉じるなどをする場合は、処理の中断の要求をしたいので、そのためのrequireStoppingメソッドを追加。
それと、中断しきれないうちにオブジェクトの破棄がされるのを防ぐため、中断or完了を待ってブロックするwaitCompletedメソッドを追加。(2010.6.2)
使用手順(ダウンロードを起動する側)
初期化時に同時にダウンロードする最大を指定,
addURL:withUserInfoでダウンロード対象のURLをQueueに登録してダウンロードを行っていく。
この登録を行うことにより、非同期にダウンロードが行われる.
QueuedURLDownloaderDelegateプロトコルを実装したDelegateクラスの
メソッド(didFinishLoading:withUserInfo)によりダウンロードした内容を取得する。
以下の手順で、ダウンロードの処理を起動、終了をさせる。
例はこんな感じ
// 初期化 QueuedURLDownloader *downloader = [[QueuedURLDownloader alloc] initWithMaxAtSameTime:3]; downloader.delegate = self; // QueuedURLDownloaderDelegateプロトコルを実装したもの // 開始 [downloader start]; // NSDictionary *dict; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value1, @"key1", nil] ; [downloader addURL:[NSURL URLWithString:urlString1 ] withUserInfo:dict]; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value2, @"key2", nil] ; [downloader addURL:[NSURL URLWithString:urlString2 ] withUserInfo:dict]; dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value3, @"key3", nil] ; [downloader addURL:[NSURL URLWithString:urlString3 ] withUserInfo:dict]; ... // これ以上、ダウンロードするものがないことを通知 [downloader finishQueuing];
使用手順(ダウンロードなどの通知を受けるがわ)
Delegateは、1要素(URL - ファイル)のダウンロードが完了したさいの通知メソッド、エラー完了した場合の通知メソッドが必須となる。
/*! ダウンロード完了時の通知 */ - (void)didFinishLoading:(NSData *)data withUserInfo:(NSDictionary *)info { // 引数dataがダウンロードされたデータなので、表示するなり、ファイル保存するなり... } /*! ダウンロードエラー時の通知 */ - (void)downloadDidFailWithError:(NSError *)error withUserInfo:(NSDictionary *)info { }
あと、UIViewControllerなどからこのダウンロード機能を使っている場合、ダウンロード中にViewが破棄されるとダウンロードオブジェクトも破棄されて不正メモリアクセスが起こったりする。
それの回避のためには、Viewが非表示になるタイミングなど(viewDidDisappear:)に、中断の要求と完待ちなどをする。
- (void)viewDidDisappear:(BOOL)animated { // Download実行中の場合,停止を要求、完了するまで待つ if(downloader) { [downloader requireStopping]; [downloader waitCompleted]; } }
exercise 1.12 - Pascalの三角形
パスカルの三角形の問題ですが、
(リストとかはまだ出てきていないので)指定した要素の値を求めよという解釈をして解く。
レベル(上から何番目)と列(左から何番目)を引数に指定しての定義。
左上(1つ下のレベルの同じ列)と右上(1つ上のレベルの1つ右の列)を足したものが、その要素の値なので、
その左上と右上を足すという操作を再帰させればよいということで以下のとおりかな。
(condで指定している条件がいい加減だが...)
(define (pascal-triangle level n ) (cond ((= n 1) 1) ; 左の辺 ((= n level) 1) ; 右の辺 ((= n 0) 0) ; ((= level 0) 0) ; (else (+ (pascal-triangle (- level 1) n ) (pascal-triangle (- level 1) (- n 1))))))
実行例..
gosh> (pascal-triangle 10 3) 36 gosh> (pascal-triangle 11 4) 120
Google Data API を iphoneで使う(Picasaの実装例)
Google Data APIを使って、まずiPhoneでのPicasaのViewerを作ってみようかとおもい調査。
準備は前回したので、実装例を作成。
Google Data APIの基本的なAPIの使用手順
Google Data APIでは、サービスオブジェクトを使用してGoogleのサーバーへ非同期でのリクエストを実行。通知用の定義したコールバックメソッドにより結果を取得するというのが、基本的な使用方法となる。
Google Data APIの基本的なAPIの使用手順 - リクエストの実行
-
- サービスオブジェクト(Picasaの場合は、GDataServiceGooglePhotos)を作成。
- GoogleのサーバーにリクエストするURLを作成する。サービスオブジェクトのURL生成メソッド(photoFeedURLForUserIDなど)を使用して作成。または、Queryオブジェクト(Picasaでは、GDataQueryGooglePhotos)を使用しての生成を行う。Picasaの場合は、基本的なパラメータとして、ユーザIDが必須。アルバム以下の情報がほしい場合はAlbumIDを、さらに下位であるPhoto以下の情報がほしい場合は、PhotoIDを指定する。また、下位の一覧(Entries)としてどのような情報がほしいかをkindで指定できる。kindはalbum,photo,comment,tagが指定できる(指定しないとDefaultの情報???)。
- (必要な場合は)サービスオブジェクトに対して認証情報を設定する。[setUserCredentialsWithUsername:password]メソッドによって行う。
- サービスオブジェクトのメソッドによって、GoogleへのサーバーへのHttpリクエストを実行する。結果は、didFinishSelectorで指定したコールバックメソッドへ通知される。
Google Data APIの基本的なAPIの使用手順 - リクエスト結果の通知
-
- サービスオブジェクトのリクエストのさいに指定したメソッド([fetchFeedWithURL:delegate:didFinishSelector:]のdidFinishedSelectorで指定したメソッド)、結果を受け付ける。このメソッドは[ticket:finishedWithFeed:error:]の形式となる。各パラメーターは以下のような内容となる。
- ticket - GDataServiceTicketインスタンス。認証情報
- finishedWithFeed - GDataFeedBaseの継承クラスのインスタンス。リクエスト時に指定したURLによって違いクラスのインスタンスとなる(Userだけを指定した場合は、GDataFeedPhotoUser, Albumまで指定した場合は、GDataFeedPhotoAlbum,写真までならGDataFeedPhoto ...)。これは、Googleサーバーから返ってきたXMLをマッピングしたもので、各要素の値が格納されている。また、下位の情報の配列(NSArray)がentriesメソッドで取得できたりする。これの要素はGDataEntryBaseの継承クラスとなっている(Userの結果のentriesであれば写真情報を格納したGDataEntryPhotoAlbum...など)。
- error - NSErrorのインスタンス
- サービスオブジェクトのリクエストのさいに指定したメソッド([fetchFeedWithURL:delegate:didFinishSelector:]のdidFinishedSelectorで指定したメソッド)、結果を受け付ける。このメソッドは[ticket:finishedWithFeed:error:]の形式となる。各パラメーターは以下のような内容となる。
その他
リクエストするさいには、基本的なパラメーター(Picasaの場合は、ユーザID, AlbumID,PhotoID)のほか、さまざまなパラメーターを指定できる。今回一部しか試していないが、一応付記。
例
まず、ユーザの情報の取得を開始。
/* ユーザーの情報の取得を開始。 結果はuserWithTicket:finishedWithUserFeed:error:コールバックで取得する。 */ - (void) queryPhotoUser:(NSString *)user { GDataServiceGooglePhotos *service = [[GDataServiceGooglePhotos alloc] init]; // アカウント設定 // [service setUserCredentialsWithUsername:@"****" password:@"****"]; NSURL *feedURL = [GDataServiceGooglePhotos photoFeedURLForUserID:user albumID:nil albumName:nil photoID:nil kind:@"album" access:nil]; NSLog(@"%@", feedURL); [service fetchFeedWithURL:feedURL delegate:self didFinishSelector:@selector(userWithTicket:finishedWithUserFeed:error:) ]; [service release]; }
ユーザー情報のレスポンス情報を取得するコールバック。下位のentryとしてアルバムの情報も取得できている。
/* ユーザ情報取得コールバック GDataFeedPhotoUserのインスタンスとして情報が得られる。このインスタンスには、Albumエントリ一覧 が含まれる。 */ - (void)userWithTicket:(GDataServiceTicket *)ticket finishedWithUserFeed:(GDataFeedPhotoUser *)feed error:(NSError *)error { if (error == nil) { NSArray *entries = [feed entries]; if ([entries count] > 0) { NSLog(@"the user has %d alblums", [[feed entries] count]); for (int i = 0; i < [entries count]; ++i) { GDataEntryPhotoAlbum *album = [entries objectAtIndex:i]; NSLog(@"album - title = %@, ident=%@, feedlink=%@", [[album title] contentStringValue], [album GPhotoID], [album feedLink]); [self queryPhotoAlbum:[album GPhotoID] user:[album username]]; } } else { NSLog(@"the user has no albums"); } } else { NSLog(@"fetch error: %@", error); } }
次に、アルバムの情報取得のリクエスト
GDataServiceGooglePhotos *service = [[GDataServiceGooglePhotos alloc] init]; // アカウント設定 // [service setUserCredentialsWithUsername:@"****" password:@"****"]; NSURL *feedURL = [GDataServiceGooglePhotos photoFeedURLForUserID:userId albumID:albumId albumName:nil photoID:nil kind:@"photo" access:nil]; GDataQueryGooglePhotos *query = [GDataQueryGooglePhotos queryWithFeedURL:feedURL]; NSLog(@"url for album %@", [query URL]); [service fetchFeedWithURL:[query URL] delegate:self didFinishSelector:@selector(albumWithTicket:finishedWithAlbumFeed:error:) ]; [service release];
アルバム情報のレスポンス情報を取得するコールバック、下位のentryとして写真の情報も取得できている。
- (void)albumWithTicket:(GDataServiceTicket *)ticket finishedWithAlbumFeed:(GDataFeedPhotoAlbum *)feed error:(NSError *)error { if (error == nil) { NSArray *entries = [feed entries]; if ([entries count] > 0) { NSLog(@"the album has %d photos", [[feed entries] count]); for (int i = 0; i < [entries count]; ++i) { GDataEntryPhoto *photo = [entries objectAtIndex:i]; NSLog(@"photo - title = %@", [[photo title] contentStringValue]); if([[[photo mediaGroup] mediaThumbnails] count] > 0) { GDataMediaThumbnail *thumbnail = [[[photo mediaGroup] mediaThumbnails] objectAtIndex:0]; NSLog(@"URL for the thumb - %@", [thumbnail URLString] ); } if([[[photo mediaGroup] mediaContents] count] > 0) { GDataMediaContent *content = [[[photo mediaGroup] mediaContents] objectAtIndex:0]; NSLog(@"URL for the photo - %@", [content URLString] ); } if(i == 0) { // [self queryPhoto:[photo GPhotoID] album:[feed GPhotoID] user:[feed username]]; } } } else { NSLog(@"the album has no photos"); } } else { NSLog(@"fetch error: %@", error); } }
Google Data API を iphoneで使う(準備)
Google Data APIを使って、まずiPhoneでのPicasaのViewerを作ってみようかとおもい、まず準備。
[GData Objective-C Client Library]のXcodeプロジェクトがコンパイルできるようにするための設定と、そのライブラリを使うためのアプリケーションのプロジェクトの作成を行う。
クライアントライブラリーのダウンロード
http://code.google.com/p/gdata-objectivec-client/downloads/list より [GData Objective-C Client Library]をダウンロードして展開。
クライアントライブラリーのXcodeプロジェクトの設定変更
[GData Objective-C Client Library]のXcodeプロジェクトを開き、以下の設定変更を行う
プロジェクトの設定変更
ターゲットからGDataFrameworkを削除
-
- グループとファイル]一覧のターゲットからGDataFrameworkを削除
アプリケーションのプロジェクトからの参照
作成するアプリケーション側からは、 [GData Objective-C Client Library]のXcodeプロジェクトの参照設定と、いくつかの設定が必要になる。
プロジェクト参照の追加
-
-
- 参照元プロジェクトの[グループとファイル]一覧への[GData Objective-C Client Library]のプロジェクトファイルの追加(適当なグループを選択してコンテキストメニュー - [既存のファイルを追加])
-
インクルードパスの追加
-
-
- [プロジェクトの情報のビルドタブの一覧] - [ユーザヘッダー検索パス]に[GData Objective-C Client Library]プロジェクトの[Source]ディレクトリを追加。再帰的(に検索パス)をONにする。
-
リンクオプションに「-all_load」を追加
-
-
- [プロジェクトの情報のビルドタブの一覧] - [その他のリンクオプション]に「-all_load」というオプションを追加
- 結果のXMLとそれを格納するObjective-Cのクラスの対応づけをクラスロード時(loadメソッド)で行っているため、こうして、あらかじめクラスがロードされるようにする必要がある。こうしないとBaseのEntryやFeedのクラスインスタンスが結果として戻されてしまう。
-
また、実装とかは、また今度書くかにゃ。
CoreDataを使う3。リレーション
先日の続き。
iphoneアプリでデータの永続化にCoreDataの使用について、整理3回目。
今回は、リレーションをつけた場合の下位のエンティティの扱い。
今回の具体例では、Categoryに属する、Topic(記事)の表示、追加。
追加、削除のためのManagedObjectクラスを定義
リレーションの上位のEntityに対する、ManagedObjectクラス(NSManagedObjectから派生)を追加、追加と削除のメソッドを作成。上位から下位のEntityの関連を登録することができるようになります。ですが、この追加、削除のメソッドは実装を行わなくても、CoreDataGeneratedAccessorsというカテゴリーでインターフェイスだけを宣言しておければ、実装は不要です(動的に生成されます。)
カテゴリーなしのインターフェイスは、Entityの属性値にアクセスするためのPropertyだけを宣言しました。
@interface Category : NSManagedObject { } @property (retain) NSString *name; @property (retain) NSDate *createdAt; @property (retain) NSDate *updatedAt; @property (nonatomic, retain) NSSet* topic; @end
追加、削除のためのCoreDataGeneratedAccessorsカテゴリーは以下のとおりにしました。
@interface Category (CoreDataGeneratedAccessors) - (void)addTopicObject:(NSManagedObject *)value; - (void)removeTopicObject:(NSManagedObject *)value; - (void)addTopic:(NSSet *)value; - (void)removeTopic:(NSSet *)value; @end
「add<下位Entity名>Object:value」,「add<下位Entitiy名>:value」、で追加、「remove<下位Entity名>Object:value」,「remove<下位Entitiy名>:value」で削除のためのメソッドを宣言します。
実装部分は、とりあえずプロパティの実装、@dynamic指定子を指定することにより、動的にメソッド定義されるようにします。
リレーション追加、削除のメソッドは、(単純な場合は)前述したように実装不要です。(モデルのエディター内のリレーション名のところで表示できるコンテキストメニューの「Objc-c 2.0 Method Implimentations をクリップボードをコピー」を選択すると、実装部分も生成してくれます。必要な場合は、これを元に追加処理を含めた実装もできるよいうことになります。)
なお、メソッドがどうやって動的につくられるかということも気になったのですが、NSObjectから定義されている「resolveInstanceMethod:(SEL)name」が、定義されたメソッドがみつからない場合に呼び出され、そこで動的にメソッド解決されているということのようです(rubyでのmetho_missingに相当?)。
追加の操作
入力されたデータをCore Dataに追加する操作は、上位のEntityリレーションに対して行った操作に加えて、上で作成したリレーション追加のメソッド呼び出しが増えることになります。
-
-
- NSEntitydescriptionから新規挿入用のNSManagedObjectを生成(insertNewObjectForEntityForName:inManagedObjectContext:)して、
- そのNSManagedObjectに各属性値を設定(valueForKey:)
- 上位のEntity(今回はCategory)からのリレーション追加操作を行う。
- NSManagedObjectを保存。
-
という流れになります。
- (IBAction) saveAction:(id)sender { NSLog(@"saveAction"); NSString *subject = [subjectField text]; NSString *contents = [contentsView text]; // Create a new instance of the entity managed by the fetched results controller. NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity]; // 新しい永続化オブジェクトを作って NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; // 値を設定(If appropriate, configure the new managed object.) [newManagedObject setValue:subject forKey:@"name"]; [newManagedObject setValue:contents forKey:@"contents"]; [newManagedObject setValue:[NSDate date] forKey:@"updatedAt"]; [newManagedObject setValue:[NSDate date] forKey:@"createdAt"]; // Save the context. NSError *error = nil; if ([category respondsToSelector:@selector(addTopicObject:) ] ) { [category addTopicObject:newManagedObject]; } // Save the context. NSError *error = nil; if (![context save:&error]) { // エラー処理 NSLog(@"Unresolved error %@, %@", error, [error userInfo]); ..... } }
TableViewへの表示
基本的には、リレーションなしの場合と同じように「NSFetchedResultsController」のオブジェクトを生成。
対応するインデックスの属性値をCellの値として指定してたればよいことになります。
上位のEntityのTableViewで選択されたCategoryのものだけが含まれるように条件を指定してやる必要があります。
NSPredicateオブジェクトで条件を生成、それをNSFetchRequestに渡してやることにより、その指定を行うことができます。
NSPredicateでは、"属性名 = 値"というような形で条件を指定しますが、リレーション の場合は"<関連名>.<属性名> = 値"というかたちでの指定になります。今回の場合は、Topic側からみて、categoryへの関連、その関連先のCategory Entityののname属性のマッチするものを得るということで、"category.name = <選択されたカテゴリー名>"という指定になります。
- (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController != nil) { return fetchedResultsController; } /* Set up the fetched results controller. */ // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Topic" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; // 条件を指定 id categoryName = [categoryObject valueForKey:@"name"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@", @"category.name", categoryName]; [fetchRequest setPredicate:predicate]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"updatedAt" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"]; aFetchedResultsController.delegate = self; NSLog(@"sectios = %d", [[aFetchedResultsController sections] count]); self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; return fetchedResultsController; }
performFetchを忘れずに
NSFetchedResultsControllerオブジェクトは初期化した段階では、データは取得されておず、
performFetchメソッドを送ることによりデータの取得がされます。今回、このperformFetchするのを忘れていて長い時間はまってしまったのでした。。。
CoreDataを使う2。
先日の続き。
iphoneアプリでデータの永続化にCoreDataの使用について、XCodeで自動生成されるコードをいじってみての整理2回目。
モデルの編集
「Use Core Data for storage」をチェックしてプロジェクトを作成していると
「xcdatamodel」という拡張子で
モデルファイル(ディレクトリ)が作られているので、これを編集します。。(XCodeでファイルを選択するとエディタが起動される。)
今回は、
Category(記事カテゴリー)とTopic(記事)という2つのEntityを定義。
CategoryとTopicは1対多となるようリレーションを設定。Category側からTopicへの関連、
TopicからCategoryの関連の両方を定義します。
それぞれのEntityは以下のように。
Category | |
---|---|
name | String |
createdAt | Date |
updatedAt | Date |
Topic | |
---|---|
name | String |
contents | String |
createdAt | Date |
updatedAt | Date |
NSFetchedResultsControllerの生成
「Use Core Data for storage」をチェックしてプロジェクトを作成していると「NSFetchedResultsController」オブジェクトにより、
TableViewデータを表示するようなコードが自動生成されます。
「NSFetchedResultsController」は、CoreDataから取得したデータの集まりを(特にTableViewで使いやすいように)管理して、データを提供する役割ももっているものです。
通常のデータベースアクセスインターフェイスではSQLの結果オブジェクトに近いものなのかな。
検索条件、並び順などを指定した「NSFetchRequest」オブジェクト(これは、SQL文と同等のものかな)、取得先の「Core Data」のデータオブジェクトの格納先である「NSManagedObjectContext」を指定することにより生成されます。
今回のプロジェクトでは、ルートのTableViewを扱う「RootTableViewController」にこの「NSFetchedResultsController」オブジェクトを生成するコードが自動生成されています。
この自動生成ソースは、EvnetというEntityを対象とする内容なので、今回作成したCategory Entityを検索対象とするように変更しました。
- (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController != nil) { return fetchedResultsController; } /* Set up the fetched results controller. */ // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"updatedAt" ascending:NO]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; return fetchedResultsController; }
以上が変更後のコードです。NSEntityDescriptionの生成時のEntity名(entityForName)やNSFetchRequestに指定するSortDescription生成時のパラメータ(ソートキー)を変更したぐらいです。
あと、補足的なことですが。
NSEntityDescriptionオブジェクトがEntityの定義を含むオブジェクトです。これはNSManagedObjectContextから取得できます(NSManagedContext <- NSPersistentStoreCoordinator <- NSManagedObjectModel という所属関連があり、元をたどれば、NSManagedObjectModelオブジェクトから取得しているのだとおもいます。)
テーブルへの表示
TableViewへの表示は、以上のNSFetchResultsControllerから情報を取得してやればよいということです。TableViewでは、UTNSTableViewDataSourceのうち以下の3つのメソッドを定義してやればよいのですが、このコードも「RootViewController」クラス内に自動生成されていてほとんど変更不要です。
numberOfSectionsInTableView:tableView - Viewのセクション数を返す。これは、NSFetchedResultsController のセクション数を返すだけ、変更なし。
tableView:numberOfRowsInSection - セクション内のデータ数を返す。これは、NSFetchedResultsController のセクション内のオブジェクト数を返すだけ、これも変更なし。
tableView:cellForRowAtIndexPath: - データセル(UITableViewCell)を返す。これはNSFetchedResultsControllerからインデックス指定で該当行のデータオブジェクト(NSManagedObject)を取得。そのオブジェクトの適当な属性値(今回はname)をセルのテキストとして指定してやればよいということになります。
ないセルオブジェクトは、1回このメソッドで返されれば(可能な限り)キャッシュされるので、キャッシュされていない場合だけ新規作成するということになります。
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[fetchedResultsController sections] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } // Configure the cell. NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath]; cell.textLabel.text = [[managedObject valueForKey:@"name"] description]; // cell.textLabel.text = @"test"; return cell; }
追加
Core Dataにデータを追加する場合は、
-
-
- NSEntitydescriptionから新規挿入用のNSManagedObjectを生成(insertNewObjectForEntityForName:inManagedObjectContext:)して、
- そのNSManagedObjectに各属性値を設定(valueForKey:)
- NSManagedObjectを保存
-
という流れになります。
これも、「RootViewController」に「insertNewObject」メソッドとしてサンプルコードが自動されいますが、対象としてるEntityの定義にあわして変更が必要となります。
NSString *name = [categoryField text]; // Create a new instance of the entity managed by the fetched results controller. NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity]; // 新しい永続化オブジェクトを作って NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; // 値を設定(If appropriate, configure the new managed object.) [newManagedObject setValue:name forKey:@"name"]; [newManagedObject setValue:[NSDate date] forKey:@"updatedAt"]; [newManagedObject setValue:[NSDate date] forKey:@"createdAt"]; // Save the context. NSError *error = nil; if (![context save:&error]) { // エラー処理 ... }
Todo
リレーションされている下位の情報の扱いなどについては、また今度。
セクションの扱いとかも、できえれば。。。。。