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)によりダウンロードした内容を取得する。

以下の手順で、ダウンロードの処理を起動、終了をさせる。

    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];

使用手順(ダウンロードなどの通知を受けるがわ)

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の使用手順 - リクエストの実行

    1. サービスオブジェクト(Picasaの場合は、GDataServiceGooglePhotos)を作成。
    2. GoogleのサーバーにリクエストするURLを作成する。サービスオブジェクトのURL生成メソッド(photoFeedURLForUserIDなど)を使用して作成。または、Queryオブジェクト(Picasaでは、GDataQueryGooglePhotos)を使用しての生成を行う。Picasaの場合は、基本的なパラメータとして、ユーザIDが必須。アルバム以下の情報がほしい場合はAlbumIDを、さらに下位であるPhoto以下の情報がほしい場合は、PhotoIDを指定する。また、下位の一覧(Entries)としてどのような情報がほしいかをkindで指定できる。kindはalbum,photo,comment,tagが指定できる(指定しないとDefaultの情報???)。
    3. (必要な場合は)サービスオブジェクトに対して認証情報を設定する。[setUserCredentialsWithUsername:password]メソッドによって行う。
    4. サービスオブジェクトのメソッドによって、GoogleへのサーバーへのHttpリクエストを実行する。結果は、didFinishSelectorで指定したコールバックメソッドへ通知される。

Google Data APIの基本的なAPIの使用手順 - リクエスト結果の通知

    1. サービスオブジェクトのリクエストのさいに指定したメソッド([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のインスタンス

その他

リクエストするさいには、基本的なパラメーター(Picasaの場合は、ユーザID, AlbumID,PhotoID)のほか、さまざまなパラメーターを指定できる。今回一部しか試していないが、一応付記。

    1. kind - どういう種類のデータを取得するか。Picasaの場合は、album,photo,comment,tagなど
    2. access - 取得対象のプライバシーレベル。 all, public, privateなどを指定
    3. max-results - 結果の最大件数
    4. その他 - picasaの場合thumbsize, imgmax, q(テキスト検索)などなど..

まず、ユーザの情報の取得を開始。

/*
 ユーザーの情報の取得を開始。
 結果は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プロジェクトを開き、以下の設定変更を行う

プロジェクトの設定変更
    1. プロジェクトの情報を開く (XCode画面の左側の[グループとファイル]一覧よりプロジェクトを選択してコンテキストメニュー - [情報を見る] )
    2. ビルドタグを開く
    3. 一覧から以下の項目を設定変更
      1. ベースSDKiphoneシミュレーター X.X.X に変更
      2. アーキテクチャーをStandardに
ターゲットからGDataFrameworkを削除
    1. グループとファイル]一覧のターゲットから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リレーションに対して行った操作に加えて、上で作成したリレーション追加のメソッド呼び出しが増えることになります。

      1. NSEntitydescriptionから新規挿入用のNSManagedObjectを生成(insertNewObjectForEntityForName:inManagedObjectContext:)して、
      2. そのNSManagedObjectに各属性値を設定(valueForKey:)
      3. 上位のEntity(今回はCategory)からのリレーション追加操作を行う。
      4. 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にデータを追加する場合は、

      1. NSEntitydescriptionから新規挿入用のNSManagedObjectを生成(insertNewObjectForEntityForName:inManagedObjectContext:)して、
      2. そのNSManagedObjectに各属性値を設定(valueForKey:)
      3. 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

リレーションされている下位の情報の扱いなどについては、また今度。
セクションの扱いとかも、できえれば。。。。。