深度解析Cocoa異步請求和libxml2.dylib教程
深度解析Cocoa異步請求和libxml2.dylib教程是本文要介紹的內容,不多說,直接進入話題,很早就在cocoachina上看到這個框架了,今天終于有機會來使用這個東東了.
我這里寫一下,如何往iphone項目中添加這個框架.
步驟如下:
1.下載該framework : http://github.com/pokeb/asi-http-request/tree
2.將class根目錄下的文件全拷貝到自己的項目中,另外還要在 External/Reachability/下將其中的Reachability.h/m
也拷貝到自己的項目中.
3.添加需要的framework.可以參考 http://allseeing-i.com/ASIHTTPRequest/Setup-instructions
需要額外添加的有: CFNetwork.framework, MobileCoreServices.framework,SystemConfiguration.framework,libz.1.2.3.dylib,libxml2.dylib
然后運行項目,會發現有很多xml相關的error,不用急,這時因為libxml2.dylib這個framework(這個框架不是很friendly,我們還需要做一些工作).
在xcode中project->edit project settings->然后search "search paths",然后在path中添加 /usr/include/libxml2
這樣就ok了,可以根據官方的教程來學習了.
http://allseeing-i.com/ASIHTTPRequest/How-to-use
我下了一個sample code XMLPerformance 解析xml,我建了一個工程照著上面做,但是編譯時提示錯誤,
- error libxml/tree.h: No such file or directory
我立刻想到沒有add Frameworks ,我把libsqlite3.dylib 和 libxml2.dylib都加進去了,但是還是報錯。
- error libxml/tree.h: No such file or directory
- An error on the .h is a compile-time error with your Header Search Paths, not a .dylib or a linker error.
- You have to ensure that /usr/include/libxml2 is in your Header Search Paths in your Release configuration。
在iphone開發中,異步操作是一個永恒的話題,尤其當iphone手機需要和遠程服務器進行交互時,使用異步請求是很普遍的做法。
通常,這需要NSURLConnection和NSOperation結合起來使用。這方面的資料網絡上自然有不少的介紹,不過要找一個能運行的代碼也并不容易。許多文章介紹的并不全面,或者使用了過時的SDK,在新IOS版本下并不適用(當前***的ios是4.2了)。這些代碼很經典,但仍然很容易使人誤入歧途。
本文總結了眾多文檔介紹的方法和代碼,揭示了異步操作中的實現細節和初學者(包括筆者)易犯的錯誤,使后來者少走彎路。
一、使用NSOperation實現異步請求
1、新建類,繼承自NSOperation。
- @interface URLOperation : NSOperation
- {
- NSURLRequest* _request;
- NSURLConnection* _connection;
- NSMutableData* _data;
- //構建gb2312的encoding
- NSStringEncoding enc;
- }
- - (id)initWithURLString:(NSString *)url;
- @property (readonly) NSData *data;
- @end
接口部分不多做介紹,我們來看實現部分。
首先是帶一個NSString參數的構造函數。在其中初始化成員變量。
其中enc是 NSStringEncoding 類型,因為服務器返回的字符中使用了中文,所以我們通過它指定了一個gb2312的字符編碼。
許多資料中說,需要在NSOperation中重載一個叫做isConcurrent的函數并在其中返回YES,否則不支持異步執行。但是實際上,我們在這里注釋了這個重載方法,程序也沒有報任何錯誤,其執行方式依然是異步的。
- @implementation URLOperation
- @synthesize data=_data;
- - (id)initWithURLString:(NSString *)url {
- if (self = [self init]) {
- NSLog(@"%@",url);
- _request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url
- //構建gb2312的encoding
- enc =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
- _data = [[NSMutableData data] retain];
- }
- return self;
- }
- - (void)dealloc {
- [_request release],_request=nil;
- [_data release],_data=nil;
- [_connection release],_connection=nil;
- [super dealloc];
- }
- // 如果不重載下面的函數,異步方式調用會出錯
- //- (BOOL)isConcurrent {
- // return YES;//返回yes表示支持異步調用,否則為支持同步調用
- //}
整個類中最重要的方法是start方法。Start是NSOperation類的主方法,主方法的叫法充分說明了其重要性,因為這個方法執行完后,該NSOperation的執行線程就結束了(返回調用者的主線程),同時對象實例就會被釋放,也就意味著你定義的其他代碼(包括delegate方法)也不會被執行。很多資料中的start方法都只有最簡單的一句(包括“易飛揚的博客 “的博文):
- [NSURLConnection connectionWithRequest:_request delegate:self];
如果這樣的話,delegate方法沒有執行機會。因為start方法結束后delegate(即self對象)已經被釋放了,delegate的方法也就無從執行。
所以在上面的代碼中,還有一個while循環,這個while循環的退出條件是http連接終止(即請求結束)。當循環結束,我們的工作也就完成了。
- // 開始處理-本類的主方法
- - (void)start {
- if (![self isCancelled]) {
- NSLog(@"start operation");
- // 以異步方式處理事件,并設置代理
- _connection=[[NSURLConnection connectionWithRequest:_request delegate:self]retain];
- //下面建立一個循環直到連接終止,使線程不離開主方法,否則connection的delegate方法不會被調用,因為主方法結束對象的生命周期即終止
- //這個問題參考 http://www.cocoabuilder.com/archive/cocoa/279826-nsurlrequest-and-nsoperationqueue.html
- while(_connection != nil) {
- [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- }
- }
- }
接下來,是NSURLConnection的delegate方法,這部分的代碼和大部分資料的介紹是一樣的,你可以實現全部的delegate方法,但這里我們只實現其中3個就足夠了,其余的方法不用理會。如你所見,你可以在其中添加自己想到的任何代碼,包括接收數據,進行字符編碼或者做xml解析。
- #pragma mark NSURLConnection delegate Method
- // 接收到數據(增量)時
- - (void)connection:(NSURLConnection*)connection
- didReceiveData:(NSData*)data {
- NSLog(@"connection:");
- NSLog(@"%@",[[NSString alloc] initWithData:data encoding:enc]);
- // 添加數據
- [_data appendData:data];
- }
- // HTTP請求結束時
- - (void)connectionDidFinishLoading:(NSURLConnection*)connection {
- [_connection release],_connection=nil;
- //NSLog(@"%@",[[NSString alloc] initWithData:_data encoding:enc]);
- }
- -(void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) error{
- NSLog(@"connection error");
- }
- @end
到此,雖然代碼還沒有完成,但我們已經可以運行它了。你可以看到console輸出的內容,觀察程序的運行狀態。
2、調用NSOperation
我們的NSOperation類可以在ViewController中調用,也可以直接放在AppDelegate中進行。
在這里,我是通過點擊按鈕來觸發調用代碼的:
- -(void)loginClicked{
- //構造登錄請求url
- NSString* url=@”http://google.com”;
- _queue = [[NSOperationQueue alloc] init];
- URLOperation* operation=[[URLOperation alloc ]initWithURLString:url];
- // 開始處理
- [_queue addOperation:operation];
- [operation release];//隊列已對其retain,可以進行release;
- }
_queue是一個 NSOperationQueue 對象,當往其中添加 NSOperation 對象后, NSOperation 線程會被自動執行(不是立即執行,根據調度情況)。
3、KVO編程模型
我們的NSOperation完成了向服務器的請求并將服務器數據下載到成員變量_data中了。現在的問題是,由于這一切是通過異步操作進行的,我們無法取得_data中的數據,因為我們不知道什么時候異步操作完成,以便去訪問_data屬性(假設我們將_data定義為屬性了),取得服務器數據。
我們需要一種機制,當NSOperation完成所有工作之后,通知調用線程。
這里我們想到了KVO編程模型(鍵-值觀察模型)。這是cocoa綁定技術中使用的一種設計模式,它可以使一個對象在屬性值發生變化時主動通知另一個對象并觸發相應的方法。
首先,我們在NSOperation的子類中添加一個BOOL變量,當這個變量變為YES時,標志異步操作已經完成:
- BOOL _isFinished;
在實現中加入這個變量的訪問方法:
- - (BOOL)isFinished
- {
- return _isFinished;
- }
cocoa的KVO模型中,有兩種通知觀察者的方式,自動通知和手動通知。顧名思義,自動通知由cocoa在屬性值變化時自動通知觀察者,而手動通知需要在值變化時調用 willChangeValueForKey:和didChangeValueForKey: 方法通知調用者。為求簡便,我們一般使用自動通知。
要使用自動通知,需要在 automaticallyNotifiesObserversForKey方法中明確告訴cocoa,哪些鍵值要使用自動通知:
- //重新實現NSObject類中的automaticallyNotifiesObserversForKey:方法,返回yes表示自動通知。
- + (BOOL):(NSString*)key
- {
- //當這兩個值改變時,使用自動通知已注冊過的觀察者,觀察者需要實現observeValueForKeyPath:ofObject:change:context:方法
- if ([key isEqualToString:@"isFinished"])
- {
- return YES;
- }
- return [super automaticallyNotifiesObserversForKey:key];
- }
然后,在需要改變_isFinished變量的地方,使用
- [self setValue:[NSNumber numberWithBool:YES] forKey:@"isFinished"];
方法,而不是僅僅使用簡單賦值。
我們需要在3個地方改變isFinished值為YES,請求結束時、連接出錯誤,線程被cancel。請在對應的方法代碼中加入上面的語句。
***,需要在觀察者的代碼中進行注冊。打開ViewController中調用NSOperation子類的地方,加入:
- //kvo注冊
- [operation addObserver:self forKeyPath:@"isFinished"
- options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:operation];
- 并實現 observeValueForKeyPath 方法:
- //接收變更通知
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context
- {
- if ([keyPath isEqual:@"isFinished"]) {
- BOOL isFinished=[[change objectForKey:NSKeyValueChangeNewKey] intValue];
- if (isFinished) {//如果服務器數據接收完畢
- [indicatorView stopAnimating];
- URLOperation* ctx=(URLOperation*)context;
- NSStringEncoding enc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
- NSLog(@"%@",[[NSString alloc] initWithData:[ctx data] encoding:enc]);
- //取消kvo注冊
- [ctx removeObserver:self
- forKeyPath:@"isFinished"];
- }
- }else{
- // be sure to call the super implementation
- // if the superclass implements it
- [super observeValueForKeyPath:keyPath
- ofObject:object
- change:change
- context:context];
- }
- }
運行程序,查看控制臺的輸出。
#p#
4、libxml的sax解析接口
iphone和服務器交互通常使用xml數據交換格式,因此本文中也涉及到了xml文件解析的問題。有許多有名氣的xml解析器可供我們選擇,如: BXML,TouchXML,KissXML,TinyXML的第三方庫和GDataXML。
Xml解析分為兩類,一類是DOM解析,一類為SAX解析。前者如GDataXML,解析過程中需要建立文檔樹,操作XML元素時通過樹形結構進行導航。DOM解析的特點是便于程序員理解xml文檔樹結構,API 的使用簡單;缺點是速度較SAX解析慢,且內存開銷較大。在某些情況下,比如iphone開發,受制于有限的內存空間(一個應用最多可用10幾m的內存), DOM解析無法使用(當然,在模擬器上是沒有問題的)。
libxml2的是一個開放源碼庫,默認情況下iPhone SDK 中已經包括在內。它是一個基于C的API,所以在使用上比cocoa的 NSXML要麻煩許多(一種類似c函數的使用方式),但是該庫同時支持DOM和SAX解析,其解析速度較快,而且占用內存小,是最適合使用在iphone上的解析器。從性能上講,所有知名的解析器中,TBXML最快,但在內存占用上,libxml使用的內存開銷是最小的。因此,我們決定使用libxml的sax接口。
首先,我們需要在project中導入framework:libxml2.dylib。
雖然libxml是sdk中自帶的,但它的頭文件卻未放在默認的地方,因此還需要我們設置project的build選項:HEADER_SEARCH_PATHS = /usr/include/libxml2,否則libxml庫不可用。
然后,我們就可以在源代碼中 #import <libxml/tree.h> 了。
假設我們要實現這樣的功能:有一個登錄按鈕,點擊后將用戶密碼帳號發送http請求到服務器(用上文中介紹的異步請求技術),服務器進行驗證后以xml文件方式返回驗證結果。我們要用libxml的sax方式將這個xml文件解析出來。
服務器返回的xml文件格式可能如下:
<?xml version="1.0" encoding="GB2312" standalone="no" ?>
<root>
<login_info>
<login_status>true</login_status>
</login_info>
<List>
<system Name=xxx Path=xxx ImageIndex=xxx>
……
</List>
</root>
其中有我們最關心的1個元素:login_status 。
如果login_status返回false,說明登錄驗證失敗,否則,服務器除返回login_status外,還會返回一個list元素,包含了一些用戶的數據,這些數據是<system>元素的集合。
整個實現步驟見下。
首先,實現一個超類, 這個超類是一個抽象類,許多方法都只是空的,等待subclass去實現。
其中有3個方法與libxml的sax接口相關,是sax解析過程中的3個重要事件的回調方法,分別是元素的開始標記、元素體(開始標記和結束標記之間的文本)、結束標記。Sax中有許多的事件,但絕大部分時間,我們只需要處理這3個事件。因為很多時候,我們只會對xml文件中的元素屬性和內容感興趣,而通過這3個事件已經足以使我們讀取到xml節點的屬性和內容。
而成員變量中,_root變量是比較關鍵的,它以dictionary的形式保存了解析結果,因為任何xml文檔的根節點都是root,所以無論什么樣子的xml文件,都可以放在這個_root 中。
因此我們為 _root 變量提供了一個訪問方法getResult,等xml解析結束,可以通過這個方法訪問_root。
- #import <Foundation/Foundation.h>
- #import <libxml/tree.h>
- @interface BaseXmlParser : NSObject {
- NSStringEncoding enc;
- NSMutableDictionary* _root;
- }
- // Property
- - (void)startElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix
- URI:(const xmlChar*)URI
- nb_namespaces:(int)nb_namespaces
- namespaces:(const xmlChar**)namespaces
- nb_attributes:(int)nb_attributes
- nb_defaulted:(int)nb_defaultedslo
- attributes:(const xmlChar**)attributes;
- - (void)endElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI;
- - (void)charactersFound:(const xmlChar*)ch
- len:(int)len;
- -(NSDictionary*)getResult;
- @end
- #import "BaseXmlParser.h"
- @implementation BaseXmlParser
- // Property
- -(id)init{
- if(self=[super init]){
- //構建gb2312的encoding
- enc =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
- _root=[[NSMutableDictionary alloc]init];
- }
- return self;
- }
- -(void)dealloc{
- [_root release],_root=nil;
- [super dealloc];
- }
- #pragma mark -- libxml handler,主要是3個回調方法--
- //解析元素開始標記時觸發,在這里取元素的屬性值
- - (void)startElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix
- URI:(const xmlChar*)URI
- nb_namespaces:(int)nb_namespaces
- namespaces:(const xmlChar**)namespaces
- nb_attributes:(int)nb_attributes
- nb_defaulted:(int)nb_defaultedslo
- attributes:(const xmlChar**)attributes
- {
- }
- //解析元素結束標記時觸發
- - (void)endElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI
- {
- }
- //解析元素體時觸發
- - (void)charactersFound:(const xmlChar*)ch
- len:(int)len
- {
- }
- //返回解析結果
- -(NSDictionary*)getResult{
- return _root;
- }
- @end
現在我們需要擴展這個BaseXmlParser,并重載其中的3個sax方法。
該子類除了重載父類的3個方法外,還增加了幾個成員變量。其中flag是一個int類型,用于sax解析的緣故,解析過程中需要合適的標志變量,用于標志當前處理到的元素標記。為了簡單起見,我們沒有為每一個標記都設立一個標志,而是統一使用一個int標志,比如flag為1時,表示正在處理login_status標記,為2時,表示正在處理system標記。
回顧前面的xml文件格式,我們其實只關心兩種標記,login_status標記和system標記。Login_status標記沒有屬性,但它的元素體是我們關心的;而system標記則相反,它并沒有元素體,但我們需要它的屬性值。
這是一個很好的例子。因為它同時展示了屬性的解析和元素體的解析。瀏覽整個類的代碼,我們總結出3個sax事件的使用規律是:
如果要讀取元素屬性,需要在“元素開始標記讀取”事件(即 startElementLocalName 方法)中處理;
如果要讀取元素體文本,則在“元素體讀取”事件(即 charactersFound方法)中處理;
#p#
在“元素標記讀取”事件( 即endElementLocalName 方法)中,則進行標志變量的改變/歸零。
- #import <Foundation/Foundation.h>
- #import <libxml/tree.h>
- #import "BaseXmlParser.h"
- @interface DLTLoginParser : BaseXmlParser {
- int flag;
- NSMutableDictionary* _currentItem;
- }
- - (void)startElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix
- URI:(const xmlChar*)URI
- nb_namespaces:(int)nb_namespaces
- namespaces:(const xmlChar**)namespaces
- nb_attributes:(int)nb_attributes
- nb_defaulted:(int)nb_defaultedslo
- attributes:(const xmlChar**)attributes;
- - (void):(const xmlChar*)localname
- prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI;
- - (void)charactersFound:(const xmlChar*)ch
- len:(int)len;
- @end
- #import "DLTLoginParser.h"
- @implementation DLTLoginParser
- -(id)init{
- if(self=[super init]){
- NSMutableArray* items=[[NSMutableArray alloc]init];
- [_root setObject:items forKey:@"items"];
- [items release];//已被_root持有了,可以釋放
- }
- return self;
- }
- -(void)dealloc{
- [_currentItem release],_currentItem=nil;
- [super dealloc];
- }
- //--------------------------------------------------------------//
- #pragma mark -- libxml handler,主要是3個回調方法--
- //--------------------------------------------------------------//
- //解析元素開始標記時觸發,在這里取元素的屬性值
- - (void)startElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix
- URI:(const xmlChar*)URI
- nb_namespaces:(int)nb_namespaces
- namespaces:(const xmlChar**)namespaces
- nb_attributes:(int)nb_attributes
- nb_defaulted:(int)nb_defaultedslo
- attributes:(const xmlChar**)attributes
- {
- // login_status,置標志為1
- if (strncmp((char*)localname, "login_status", sizeof("login_status")) == 0) {
- flag=1;
- return;
- }
- // system,置標志為2
- if (strncmp((char*)localname, "system", sizeof("system")) == 0) {
- flag=2;
- _currentItem = [NSMutableDictionary dictionary];
- //查找屬性
- NSString *key,*val;
- for (int i=0; i<nb_attributes; i++){
- key = [NSString stringWithCString:(const char*)attributes[0] encoding:NSUTF8StringEncoding];
- val = [[NSString alloc] initWithBytes:(const void*)attributes[3] length:(attributes[4] - attributes[3])
- encoding:NSUTF8StringEncoding];
- NSLog(@"key=%@,val=%@",key,val);
- if ([@"Name" isEqualToString:key]) {
- [_currentItem setObject:val forKey:@"name"];
- break;
- }
- // [val release];
- attributes += 5;//指針移動5個字符串,到下一個屬性
- }
- [[_root objectForKey:@"items"] addObject:_currentItem];
- return;
- }
- }
- //解析元素結束標記時觸發
- - (void)endElementLocalName:(const xmlChar*)localname
- prefix:(const xmlChar*)prefix URI:(const xmlChar*)URI
- {
- flag=0;//標志歸零
- }
- //解析元素體時觸發
- - (void)charactersFound:(const xmlChar*)ch
- len:(int)len
- {
- // 取login_status元素體
- if (flag==1) {
- NSString* string;
- string = [[NSString alloc] initWithBytes:ch length:len encoding:NSUTF8StringEncoding];
- [_root setObject:string forKey:@"login_status"];
- NSLog(@"login_status:%@",string);
- }
- }
- @end
接下來,改造我們的異步請求操作類URLOperation。首先在interface中增加
兩個變量:
- xmlParserCtxtPtr _parserContext; //Xml解析器指針
- BaseXmlParser* baseParser; //Xml解析器
其中第1個變量(一個結構體)的聲明顯得有點奇怪,似乎是跟第2個變量混淆了。這是因為libxml是一個c函數庫,其函數調用仍然使用一種面向結構的編程風格。所以我們在后面還會看到一些結構體似的變量。
另外,把_data成員的類型從NSMutableData改變為NSMutableDictionary,并把它配置為屬性,因為我們的請求結果應當被xml解析器解析為dictionary了:
- @property (nonatomic,retain) NSDictionary *data;
當然,記住為它提供訪問方法:
- @synthesize data=_data;
然后,更改 initWithURLString 構造方法,為其增加一個名為 xmlParser 的參數
- - (id)initWithURLString:(NSString *)url xmlParser:(BaseXmlParser*)parser{
- if (self = [super init]) {
- baseParser=[parser retain];
- NSLog(@"%@",url);
- _request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];//[[NSURLRequest requestWithURL:[NSURL URLWithString:url]]retain];
- //構建gb2312的encoding
- enc =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
- _data = [[NSMutableData data] retain];
- }
- return self;
- }
在start方法中,我們可以這樣創建一個xml解析器指針:
// 創建XML解析器指針
- _parserContext = xmlCreatePushParserCtxt(&_saxHandlerStruct, baseParser, NULL, 0, NULL);
注意第2個參數就是具體實現了sax解析的xml解析器。這個解析器對象是通過構造函數“注入”的。
而***個參數是一個結構體指針 xmlSAXHandler 結構體,這個結構體我們定義為靜態變量(注意把定義放在@implementation⋯⋯@end之外):
//libxml的xmlSAXHandler結構體定義,凡是要實現的handler函數都寫在這里,不準備實現的用null代替。一般而言,我們只實現其中3個就夠了
- static xmlSAXHandler _saxHandlerStruct = {
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- charactersFoundHandler,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- XML_SAX2_MAGIC,
- NULL,
- startElementHandler,
- endElementHandler,
- NULL,
- };
機構體中填入了我們準備實現的3個方法句柄,因此我們還應當定義這3個方法。由于結構體是靜態的,只能訪問靜態成員,所以這3個方法也是靜態的:
- //3個靜態方法的實現,其實是調用了參數ctx的成員方法, ctx在_parserContext初始化時傳入
- static void startElementHandler(
- void* ctx,
- const xmlChar* localname,
- const xmlChar* prefix,
- const xmlChar* URI,
- int nb_namespaces,
- const xmlChar** namespaces,
- int nb_attributes,
- int nb_defaulted,
- const xmlChar** attributes)
- {
- [(BaseXmlParser*)ctx
- startElementLocalName:localname
- prefix:prefix URI:URI
- nb_namespaces:nb_namespaces
- namespaces:namespaces
- nb_attributes:nb_attributes
- nb_defaulted:nb_defaulted
- attributes:attributes];
- }
- static void endElementHandler(
- void* ctx,
- const xmlChar* localname,
- const xmlChar* prefix,
- const xmlChar* URI)
- {
- [(BaseXmlParser*)ctx
- endElementLocalName:localname
- prefix:prefix
- URI:URI];
- }
- static void charactersFoundHandler(
- void* ctx,
- const xmlChar* ch,
- int len)
- {
- [(BaseXmlParser*)ctx
- charactersFound:ch len:len];
- }
其實這3個靜態方法只是調用了超類BaseXmlParser的成員方法,他的具體類型依賴于ctx的注入類型,也就是說,這里的ctx可以是任何BaseXmlParser的子類。 實際使用中,我們應該注入其子類,從而可以根據不同的情況為URLOperation“注入”不同的解析器,實現解析不同的xml文件的目的。
現在,需要把解析器應用到NSURLConnection的委托方法中(這里省略了部分代碼,只列出了新增加的部分):
- #pragma mark NSURLConnection delegate Method
- // 接收到數據(增量)時
- - (void)connection:(NSURLConnection*)connection
- didReceiveData:(NSData*)data {
- // 使用libxml解析器進行xml解析
- xmlParseChunk(_parserContext, (const char*)[data bytes], [data length], 0);
- ⋯⋯
- }
- // HTTP請求結束時
- - (void)connectionDidFinishLoading:(NSURLConnection*)connection {
- if(baseParser!=nil && baseParser!=NULL){
- [self setData:[[NSDictionary alloc] initWithDictionary:[baseParser getResult]]];
- }else {
- NSLog(@"baseparser is nil");
- }
- // 添加解析數據(結束),注意***一個參數termindate
- xmlParseChunk(_parserContext, NULL, 0, 1);
- // 釋放XML解析器
- if (_parserContext) {
- xmlFreeParserCtxt(_parserContext), _parserContext = NULL;
- }
- ⋯⋯
- }
- -(void)connection: (NSURLConnection *) connection didFailWithError: (NSError *) error{
- // 釋放XML解析器
- if (_parserContext) {
- xmlFreeParserCtxt(_parserContext), _parserContext = NULL;
- }
- ⋯⋯
- }
- @end
接下來,在“登錄”按鈕中代碼也要做相應的修改,因為URLOperation的構造函數要求傳遞一個具體的xml解析器對象:
- //構造xmlparser
- DLTLoginParser* parser=[[DLTLoginParser alloc]init];
- URLOperation* operation=[[URLOperation alloc ]initWithURLString:url xmlParser:parser];
- [parser release];
然后,在接收變更通知方法中打印解析結果:
- URLOperation* ctx=(URLOperation*)context;
- NSLog(@"%@",[ctx data]);
后臺打印結果:
- {
- items = (
- {
- name = "\U4e91\U7535\U4f01\U4fe1\U901a";
- },
- {
- name = "\U79fb\U52a8\U8c03\U5ea6";
- },
- {
- name = "\U79fb\U52a8\U62a2\U4fee";
- }
- );
- "login_status" = true;
- }
小結:深度解析Cocoa異步請求和libxml2.dylib教程的內容介紹完了,希望本文對你有所幫助!