WEBアプリケーション研究室 開発ノート TOP

WEBアプリケーション研究室 開発ノート 2009年11月

スポンサーサイト

-------- --:--

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

    このエントリーをはてなブックマークに追加

例えば同時にいくつかHTTPリクエストを投げて一気に画像をとってくる等、非同期通信を使うといい時がありますが、簡単にできるようクラスが用意されてます。同時リクエスト数を簡単に制御できたりもします。Cocoaってすごいフレームワークだなあ(みつを風に)
その名もNSOperationです。


#import "MyOperation.h"
@implementation MyOperation
@synthesize number;
- (void)start
{
NSLog(@"(%@) start");
[super start];
}
- (void)main
{
[NSThread sleepForTimeInterval:0.4];
NSLog(@"(%@) end main");
}
- (void)cancel
{
NSLog(@"(%@) cancel");
[super cancel];
}
-(void) dealloc
{
[name release];
[super dealloc];
}
@end
クライアントではNSOperationQueueにNSOperationをaddOperationしてつかいます。KVCのaddObserverを使ってisFinishedを監視すると終了時に処理をすることが簡単にできます。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

MyOperation *mo = [[MyOperation alloc]init];
[mo addObserver:self
forKeyPath:@"isFinished"
options:NSKeyValueObservingOptionNew
context:queue//前リクエストの終了を待ってqueueをreleaseするために渡してみました。
];
[queue addOperation:mo];

//同じクラスのメソッド
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
change:(NSDictionary*)change context:(void*)context
{
NSLog(@"finish");
// キー値監視を解除する
MyOperation *mo = (MyOperation *) object;
[(NSOperationQueue *)context release];
[mo removeObserver:self forKeyPath:keyPath];
[mo release];
}
HTTPリクエストを使う場合はもうちょっと複雑です。NSOperationはデフォルトではmain関数の終了時にisFinishedがYESになります。ところが、HTTPリクエスト自体が非同期通信でdelegateを使うため、通信終了のdelegateで独自にisFinishedを更新してやらないとだめです。

- (id)initWithRequest: (NSURLRequest*)request
{
self = [super init];

if (self)
{
urlRequest = [request retain];
isFinished = NO;
isExecuting = NO;
isYes = [[NSNumber alloc] initWithBool:YES];
isNo = [[NSNumber alloc] initWithBool:NO];
}
return self;
}

- (BOOL)isConcurrent
{
return YES;
}

- (BOOL)isExecuting
{
return isExecuting;
}

- (BOOL)isFinished
{
return isFinished;
}

#pragma mark -- Operating --
//-------------------------------------------------------------------------------------//

- (void)start
{
// ダウンロードを開始する
if (![self isCancelled])
{
// コネクションを作成する
urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[self setValue:isYes forKey:@"isExecuting"];
}
//[super start];これを呼び出してしまうと、main関数まで行って通信終了前にisFinishedがYESになってしまいます。
}

- (void)cancel
{
[urlConnection cancel];
// 親クラスのメソッドを呼び出す
[super cancel];
}
#pragma mark -- NSURLConnection delegate --

- (void)connection:(NSURLConnection*)connection
didReceiveData:(NSData*)data
{
// データを追加する
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
// データを追加する
// 終了処理
[self setValue:isNo forKey:@"isExecuting"];
[self setValue:isYes forKey:@"isFinished"];
}

- (void)connection:(NSURLConnection*)connection
didFailWithError:(NSError*)error
{
//エラー時の処理
}
(抜粋してるのでrelease処理なんか書いてません)もう一点重要なのが

- (BOOL)isConcurrent
{
return YES;
}
です。これは並列処理をするかしないかのBOOL値でデフォルトはNOを返すのですが、非同期通信のNSURLConnectionを使う時は、YESを返すようにオーバーライドしないと、autorelease関係のエラーが出ます。

autoreleased with no pool in place - just leaking
こんなのがいっぱい出ます。これで、土曜日の半日がなくなりました・・・

    このエントリーをはてなブックマークに追加

PHPの話題というよりオブジェクト言語全般にいえる話題だと思います。下のコードを見てください。


abstract class Super
{
private $_list = array();

private function hoge()
{
echo 'hoge';
}

public function addChild($child)
{
$this->_list[] = $child;
}

public function exec()
{
foreach($this->_list as $child)
{
$child->hoge();
}
}
}

class Child extends Super
{

}

$child = new Child();
$child->addChild(new Child());
$child->addChild(new Child());

$child->exec();
Super::execの中で保持したインスタンスに対してprivateのメソッドを呼び出しています。

私はこれは呼び出せないと思ってました。自分の親クラスのメソッドではありますが、インスタンスに対して呼び出してるのでprivateのメソッドは呼べないはずだと。そもそもprivateだぞ・・・

privateは$thisに対してのみ呼び出せるのだと思ってました。

PHPのバグか!と思ったのですがJavaで試しても呼び出せました。同僚にc++とdelphiでも試してもらったけどコンパイル通ったそうです。つまり、これは一般的なことみたいですね。

    このエントリーをはてなブックマークに追加

某フライドチキンの話ではありません。CocoaにはKeyValueCoding(KVC)という機能がついていて、オブジェクトのプロパティを変更したり、その変更を監視する特別なメソッドが用意されてます。


#import

@interface HogeObject : NSObject {
@private
BOOL isChanged;
NSString *name;
}

-(id)init;
-(void)changeName: (NSString *) value;
-(NSString *)name;
-(BOOL) isChanged;
@end
+ (BOOL)automaticallyNotifiesObserversForKey:で監視するKey名を設定します。

#import "HogeObject.h"

@implementation HogeObject
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key
{
if ([key isEqualToString:@"isChanged"])
{
return YES;
}

return [super automaticallyNotifiesObserversForKey:key];
}

-(id)init
{
if (self = [super init])
{
isChanged = NO;
name = @"hoge";
}

return self;
}

-(void)changeName: (NSString *) value
{
name = value;
[self setValue:[NSNumber numberWithBool:YES] forKey:@"isChanged"];
}

-(BOOL)isChanged
{
return isChanged;
}

-(NSString *)name
{
return name;
}

-(void)dealloc
{
[name release];
[super dealloc];
}
@end


HogeObject *test = [[HogeObject alloc] init];
//オブザーバーの登録
[test addObserver:self
forKeyPath:@"isChanged"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:nil
];
NSLog(@"%@:%@", [test name], [test isChanged] ? @"YES" : @"NO");

//変更してみる
[test changeName:@"oooo"];
NSLog(@"%@:%@", [test name], [test isChanged] ? @"YES" : @"NO");

//プライベートでも変更可能。changeNameの呼び出しではないのでオブザーバーは呼ばれない
[test setValue:@"mumu" forKey:@"name"];
NSLog(@"%@:%@", [test name], [test isChanged] ? @"YES" : @"NO");

//これは当然エラー
//test->name = @"gogogo";
ここではオブザーバーにselfを指定してるのでこのコードと同じクラスに以下のメソッドを実装します。これは必須です。

- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
NSLog(@"%@", change);
}
isChangedの値が変更されるとこのメソッドが呼ばれます。changeはどのように変更されたかの値がしまわれます。オブザーバー登録時にoptionsに渡したenumによってどんなデータが渡されるか設定できます。

    このエントリーをはてなブックマークに追加

UITableViewセルをクリックして、UINavigationControllerに新しい編集用のTableViewをpush、編集後保存ボタンをイベントにして、元のリストの一覧を読み直すというのをやってたのですが、とある本によると以下のようにやっていました。


[self.navigationController popViewControllerAnimated:YES];

NSArray *allControllers = self.navigationController.viewControllers;
UITableViewController *parent = [allControllers lastObject];
[parent.tableView reloadData];
これだとpopViewControllerAnimated:YESが実行された時点で、self.navigationControllerはnullになります。Objective-cではnullにメッセージを送ることは許されてるので、エラーにならず、reloadが行われないという状態になります。

正確には

NSArray *allControllers = self.navigationController.viewControllers;
NSInteger target = [allControllers count] - 2;
UITableViewController *parent = [allControllers objectAtIndex:target];
[parent.tableView reloadData];
[self.navigationController popViewControllerAnimated:YES];
自分の一個前の物を取得するので-2・・・気持ち悪いですね。現在のViewにポインタを持たせて呼び出した方がいいかも。ただポインタといえども微々たるメモリは消費すると思うので、iPhoneではこうした方がいいのかな・・・

    このエントリーをはてなブックマークに追加

NSArrayで例えば3番目の要素を5番目に移動したいとき、ちょっと気をつけなかればならないことがあります。


NSUInteger fromRow = 3;
NSUInteger toRow = 5;

id object = [[list objectAtIndex:fromRow] retain];
[list removeObjectAtIndex:fromRow];
[list insertObject:object atIndex:toRow];
[object release];
いったん取り出して、新しい場所に挿入すればいいのですが、removeObjectAtIndexした時点でNSArrayは対象要素をreleaseしますのでretainカウントが0になる可能性があります。retainしておかなければなりません。

    このエントリーをはてなブックマークに追加
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。