最近またiPhone触り始めて、新たに知ったことなどがあったのでメモ。
「delegateを持ち、自分で定義したコールバックを行うクラスの定義方法」について。
ヘッダでの宣言
例としてNSXMLParserをラップしてXMLからある要素を5回検出したときにコールバックを呼び出すようなクラスを考える。
こんなカンジで書く。
#import <Cocoa/Cocoa.h> @protocol MyXMLParserDelegate; - (void)found5statuses; @end @interface MyXMLParser : NSObject { int num; id <MyXMLParserDelegate> delegate; } @property (retain, nonatomic) id <MyXMLParserDelegate> delegate; - (void)parse; @end
当然だけど"@interface"の宣言と"@protocol"を上下逆に書いてしまうと、protocol宣言していないのに"@interface"の中でMyXMLParserDelegateが登場してしまいコンパイラに怒られてしまう。
実装
@implementation MyXMLParser @synthesize delegate; - (id)init { if (self = [super init]) { num = 0; } return self; } - (void)parse { NSLog(@"parse"); NSURL *url = [NSURL URLWithString:@"http://twitter.com/statuses/public_timeline.xml"]; NSXMLParser *parser = [[[NSXMLParser alloc] initWithContentsOfURL:url] autorelease]; [parser setDelegate:self]; [parser parse]; } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { if ([elementName isEqualToString:@"status"]) { num++; if (num % 5 == 0) { [delegate found5statuses]; } } } @end
呼び出す側
GUIでのイベントを受け取って動くメソッドという前提で考えておくけれども。
#import <Cocoa/Cocoa.h> #import "MyXMLParser.h" @interface Hoge : NSObject <MyXMLParserDelegate> { } - (IBAction)hoge:(id)sender; @end
#import "Hoge.h" @implementation Hoge - (IBAction)hoge:(id)sender { NSLog(@"hoge"); MyXMLParser *parser = [[[MyXMLParser alloc] init] autorelease]; [parser setDelegate:self]; [parser parse]; } - (void)found5statuses { NSLog(@"found5statuses"); } @end
setDelegateする時点で、HogeクラスがMyXMLParserDelegateを実装していないと警告を出してもらえる。ので必ずHoge.hで "
…というように、そこそこ安全にプログラミングできるようになる。
Delegateメソッドで関連するインスタンスを引数に渡す場合
上記の例はdelegateメソッドに引数が無かった。
けど場合によってはどのMyXMLParserから受けたコールバックか分かるようにそのインスタンスを引数に渡したいこともある。
その場合にどう書くか。
#import <Cocoa/Cocoa.h> @protocol MyXMLParserDelegate - (void)found5statuses:(MyXMLParser *)parser; @end @interface MyXMLParser : NSObject { int num; id <MyXMLParserDelegate> delegate; } @property (retain, nonatomic) id <MyXMLParserDelegate> delegate; - (void)parse; @end
のようにすると、MyXMLParserの@interface宣言の前なので解決できない。@interfaceと@protocolを逆にしてもダメ。
で、どうすればいいのかというと、以下のように書けば良いらしい。
#import <Cocoa/Cocoa.h> @protocol MyXMLParserDelegate; @interface MyXMLParser : NSObject { int num; id <MyXMLParserDelegate> delegate; } @property (retain, nonatomic) id <MyXMLParserDelegate> delegate; - (void)parse; @end @protocol MyXMLParserDelegate - (void)found5statuses:(MyXMLParser *)parser; @end
先に"MyXMLParserDelegate"というのがあるよ、とだけ宣言、その後でメソッド宣言を書いてやる。こうすれば目的の動作が実現できるようだ。
#import "MyXMLParser.h" @implementation MyXMLParser @synthesize delegate; - (id)init { if (self = [super init]) { num = 0; } return self; } - (void)parse { NSLog(@"parse"); NSURL *url = [NSURL URLWithString:@"http://twitter.com/statuses/public_timeline.xml"]; NSXMLParser *parser = [[[NSXMLParser alloc] initWithContentsOfURL:url] autorelease]; [parser setDelegate:self]; [parser parse]; } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { if ([elementName isEqualToString:@"status"]) { num++; if (num % 5 == 0) { [delegate found5statuses:self]; } } } @end
#import "Hoge.h" @implementation Hoge - (IBAction)hoge:(id)sender { NSLog(@"hoge"); MyXMLParser *parser1 = [[[MyXMLParser alloc] init] autorelease]; MyXMLParser *parser2 = [[[MyXMLParser alloc] init] autorelease]; [parser1 setDelegate:self]; [parser2 setDelegate:self]; [parser1 parse]; [parser2 parse]; } - (void)found5statuses:(MyXMLParser *)parser { NSLog(@"found5statuses by %@", parser); } @end