その後、URLLoaderクラスを初期化していますが、特に重要なのが、その後の部分です。
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(loadTimeLineDidEnd:) name: @"connectionDidFinishNotification" object: loder];
Cocoaフレームワークの「Cocoa Notification」という仕組みを使ってURLLoaderの通信完了通知を受け取るよう指定しています。引数objectに指定されたURLLoaderのインスタンスloaderから「connectionDidFinishNotification」という名前の通知があった場合に、「loadTimeLineDidEnd」というメソッドが実行されるようにNotificationCenterに登録しています。
ここで先ほどURLLoaderに実装したconnectionDidFinishLoadingメソッドの中身を思い出してみてください。
[[NSNotificationCenter defaultCenter] postNotificationName: @"connectionDidFinishNotification" object: self];
NSNotificationCenterの「postNotificationName :object」メソッドを呼んで、「connectionDidFinishNotification」という名前の通知を発行しています。
つまり、通信が完了するとRootViewControllerのloadTimeLineDidEndが呼ばれる仕組みです。loadTimeLineDidEndメソッドでは引数にNSNotificaionを取り、[notification object]で通知元のURLLoaderインスタンスを取り出し、受信データをログ出力しています。
Cocoa Notificationを用いると、このように異なるクラス間で通知を送りあったり、データのやりとりができます。サンプルでは、同様にCocoa Notificationを用いて、通信エラー発生時に「loadTimeLineFailed」が呼ばれるように設定しています。
次に、RootViewController.mのviewDidLoadメソッドのコメントアウトを外し、viewDidLoadメソッド内でloadTimeLineByUserNameメソッドを呼び出すように編集します。
- (void)viewDidLoad { [super viewDidLoad]; [self loadTimeLineByUserName:@"itmedia"]; }
今回はITmediaのTwitterユーザー名である「itmedia」を引数に渡してloadTimeLineByUserNameメソッドを呼び出しました。
以上でURLからデータを取得する部分が出来上がりましたので、実行結果を確認してみましょう。
今回はシミュレータの画面ではなくログを確認するため、Xcodeの[実行]→[コンソール]をクリックしてコンソールを表示しておきます。
シミュレータを起動して実行し、以下のようにコンソールにXMLが表示されればデータの取得は成功です(使用しているMac端末がインターネットに接続している必要があります)。
NSURLConnectionクラス使って取得したXMLには、多くの情報が含まれています。今回はその中から、「ユーザー名」と「つぶやき」を取得します。このようなXMLの解析とデータ変換のためにCocoa Touch フレームワークにはNSXMLParserクラスが用意されています。
一般に、XML解析にはDOM(Document Object Model)とSAX(Simple API for XML)という2種類の方式があります。NSXMLParserはSAX型のXML解析クラスです。SAXでは、XMLを上から順に読んでいき、「開始タグが見つかった」「終了タグに到達した」などのイベントごとに処理します。
それでは、実際にNSXMLParserを使ってXMLから「ユーザー名」と「つぶやき」を取得するクラスを作成しましょう。
[Classes]を右クリック→[追加]→[新規ファイル]で、[新規ファイル]ウィンドウを開いたら、[Objective-C class]、[Subclass of]に[NSObject]を選択して[次へ]をクリックします。
[ファイル名]を「StatusXMLParser.m」として[完了]をクリックします。
「StatusXMLParser.h」を以下のように編集します。
#import <Foundation/Foundation.h> @interface StatusXMLParser : NSObject <NSXMLParserDelegate> { NSMutableString *currentXpath; NSMutableArray *statuses; NSMutableDictionary *currentStatus; NSMutableString *textNodeCharacters; } @property (retain , nonatomic) NSMutableString *currentXpath; @property (retain , nonatomic) NSMutableArray *statuses; @property (retain , nonatomic) NSMutableDictionary *currentStatus; @property (retain , nonatomic) NSMutableString *textNodeCharacters; - (NSArray *) parseStatuses: (NSData *) xmlData; @end
NSXMLParserは「開始タグが見つかった」などのイベントごとにデリゲートに処理を委譲します。このためStatusXMLParser.hでは、NSXMLParserのデリゲートになれるよう<NSXMLParserDelegate>プロトコルを採用しています。
後は、実装クラスにデリゲートメソッドを実装することで好きな解析処理ができます。
続けて、「StatusXMLParser.m」を以下のように編集します。
#import "StatusXMLParser.h" @implementation StatusXMLParser @synthesize currentXpath; @synthesize statuses; @synthesize currentStatus; @synthesize textNodeCharacters; // (9) - (void) parserDidStartDocument:(NSXMLParser *)parser { self.currentXpath = [[[NSMutableString alloc]init] autorelease]; self.statuses = [[[NSMutableArray alloc] init] autorelease]; } // (10) - (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { [self.currentXpath appendString: elementName]; [self.currentXpath appendString: @"/"]; self.textNodeCharacters = [[[NSMutableString alloc] init] autorelease]; if ([self.currentXpath isEqualToString: @"statuses/status/"]) { self.currentStatus = [[[NSMutableDictionary alloc] init] autorelease]; } } // (11) - (void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { NSString *textData = [self.textNodeCharacters stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if ([self.currentXpath isEqualToString: @"statuses/status/"]) { [self.statuses addObject:self.currentStatus]; self.currentStatus = nil; } else if ([self.currentXpath isEqualToString: @"statuses/status/text/"]) { [self.currentStatus setValue:textData forKey:@"text"]; } else if ([self.currentXpath isEqualToString: @"statuses/status/user/name/"]) { [self.currentStatus setValue:textData forKey:@"name"]; } int delLength = [elementName length] + 1; int delIndex = [self.currentXpath length] - delLength; [self.currentXpath deleteCharactersInRange:NSMakeRange(delIndex,delLength)]; } // (12) - (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { [self.textNodeCharacters appendString:string]; } // (13) - (NSArray *) parseStatuses:(NSData *)xmlData { NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:xmlData] autorelease]; [parser setDelegate:self]; [parser parse]; return self.statuses; } - (void) dealloc { [currentXpath release]; [statuses release]; [currentStatus release]; [textNodeCharacters release]; [super dealloc]; } @end
(9)〜(12)がXMLParserDelegateプロトコルに準拠したデリゲートメソッドです。それぞれXML解析時に以下のタイミングで呼び出されます。
(13)のparseStatusesメソッドはStatusXMLParserを利用するクラスから呼ばれるメソッドです。NSXMLParserインスタンスのデリゲートにStatusXMLParserインスタンス自身をに設定して初期化し、解析処理をスタートします。
StatusXMLParserでは現在解析中のXML要素をパス形式の文字列としてcurrentXpathプロパティで管理しています。
要素が終了した(終了タグが見つかった)ときに、currentXpathプロパティが、「statuses/status/text/」だった場合は「つぶやき」として、「statuses/status/user/name/」だった場合は「ユーザー名」として、一時データ格納先のcurrentStatusプロパティにテキスト値であるtextNodeCharactersプロパティの値を設定しています。そして、1つの<status>要素が終了したらcurrentStatusプロパティの中身をstatusesプロパティに追加します。
これを繰り返して「ユーザー名」と「つぶやき」から成るデータの一覧を作成しています。
Copyright © ITmedia, Inc. All Rights Reserved.