iPhone开发中使用NSOperation实现异步下载

移动开发 iOS
iPhone开发中使用NSOperation实现异步下载是本文要介绍的内容,在iphone开发中,异步操作是一个永恒的话题,尤其当iphone手机需要和远程服务器进行交互时,使用异步请求是很普遍的做法。

iPhone开发中使用NSOperation实现异步下载是本文要介绍的内容,在iphone开发中,异步操作是一个永恒的话题,尤其当iphone手机需要和远程服务器进行交互时,使用异步请求是很普遍的做法。

通常,这需要NSURLConnection和 NSOperation结合起来使用。这方面的资料网络上自然有不少的介绍,不过要找一个能运行的代码也并不容易。许多文章介绍的并不全面,或者使用了过 时的SDK,在新IOS版本下并不适用(当前***的ios是4.2了)。这些代码很经典,但仍然很容易使人误入歧途。

本文总结了众多文档介绍的方法和代码,揭示了异步操作中的实现细节和初学者(包括笔者)易犯的错误,使后来者少走弯路。

一、使用NSOperation实现异步请求

1、新建类,继承自NSOperation。

  1. @interfaceURLOperation : NSOperation  
  2. {  
  3.     NSURLRequest*  _request;  
  4.     NSURLConnection* _connection;  
  5.     NSMutableData* _data;  
  6.     //构建gb2312的encoding  
  7.     NSStringEncodingenc;  
  8. }  
  9. - (id)initWithURLString:(NSString*)url;  
  10. @property(readonly) NSData *data;  
  11. @end 

接口部分不多做介绍,我们来看实现部分。

首先是带一个NSString参数的构造函数。在其中初始化成员变量。

其中enc是NSStringEncoding类型,因为服务器返回的字符中使用了中文 ,所以我们通过它指定了一个gb2312的字符编码。

许多资料中说,需要在NSOperation中重载一个叫做isConcurrent的函数并在其中返回YES,否则不支持异步执行。但是实际上,我们在这里注释了这个重载方法,程序也没有报任何错误,其执行方式依然是异步的。

  1. @implementationURLOperation  
  2. @synthesizedata=_data;  
  3. - (id)initWithURLString:(NSString*)url {  
  4.     if(self= [selfinit]) {  
  5.         NSLog(@"%@",url);  
  6.         _request= [[NSURLRequestalloc] initWithURL:[NSURLURLWithString:url  
  7.         //构建gb2312的encoding  
  8.         enc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);  
  9.         _data= [[NSMutableDatadata] retain];  
  10.     }  
  11.     returnself;  
  12. }  
  13. - (void)dealloc {  
  14.     [_requestrelease],_request=nil;  
  15.     [_datarelease],_data=nil;  
  16.     [_connectionrelease],_connection=nil;  
  17.     [superdealloc];  
  18. }  
  19. // 如果不重载下面的函数,异步方式调用会出错  
  20. //- (BOOL)isConcurrent {  
  21. //  return YES;//返回yes表示支持异步调用,否则为支持同步调用  
  22. //} 

整个类中最重要的方法是start方法。Start是 NSOperation类的主方法,主方法的叫法充分说明了其重要性,因为这个方法执行完后,该NSOperation的执行线程就结束了(返回调用者的 主线程),同时对象实例就会被释放,也就意味着你定义的其他代码(包括delegate方法)也不会被执行。很多资料中的start方法都只有最简单的一 句(包括“易飞扬的博客 “的博文):

  1. [NSURLConnection connectionWithRequest:_request delegate:self]; 

如果这样的话,delegate方法没有执行机会。因为start方法结束后delegate(即self对象)已经被释放了,delegate的方法也就无从执行。

所以在上面的代码中,还有一个while循环,这个while循环的退出条件是http连接终止(即请求结束)。 当循环结束,我们的工作也就完成了。

  1. // 开始处理-本类的主方法  
  2. - (void)start {  
  3.     if(![selfisCancelled]) {  
  4.         NSLog(@"start operation");  
  5.         // 以异步方式处理事件,并设置代理  
  6.         _connection=[[NSURLConnectionconnectionWithRequest:_requestdelegate:self]retain];  
  7.         //下面建立一个循环直到连接终止,使线程不离开主方法,否则connection的delegate方法不会被调用,因为主方法结束对象的生命周期即终止  
  8.         //这个问题参考http://www.cocoabuilder.com/archive/cocoa/279826-nsurlrequest-and-nsoperationqueue.html  
  9.         while(_connection!= nil) {  
  10.             [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];     
  11.         }  
  12.     }  

接下来,是NSURLConnection的 delegate方法,这部分的代码和大部分资料的介绍是一样的,你可以实现全部的delegate方法,但这里我们只实现其中3个就足够了,其余的方法 不用理会。如你所见,你可以在其中添加自己想到的任何代码,包括接收数据,进行字符编码或者做xml解析。

  1. #pragma mark NSURLConnection delegate Method  
  2. // 接收到数据(增量)时  
  3.  
  4. - (void)connection:(NSURLConnection*)connection  
  5.     didReceiveData:(NSData*)data {  
  6.     NSLog(@"connection:");  
  7.     NSLog(@"%@",[[NSStringalloc] initWithData:data encoding:enc]);  
  8.     // 添加数据  
  9.     [_dataappendData:data];  
  10. }  
  11. // HTTP请求结束时  
  12. - (void)connectionDidFinishLoading:(NSURLConnection*)connection {  
  13.     [_connectionrelease],_connection=nil;  
  14.     //NSLog(@"%@",[[NSString alloc] initWithData:_data encoding:enc]);  
  15. }  
  16. -(void)connection: (NSURLConnection*) connection didFailWithError: (NSError*) error{  
  17.     NSLog(@"connection error");  
  18. }  
  19.  
  20. @end 

到此,虽然代码还没有完成,但我们已经可以运行它了。你可以看到console输出的内容,观察程序的运行状态。

2、调用NSOperation

我们的NSOperation类可以在ViewController中调用,也可以直接放在AppDelegate中进行。

在这里,我是通过点击按钮来触发调用代码的:

  1. -(void)loginClicked{  
  2.     //构造登录请求url  
  3.     NSString* url=@”http://google.com”;  
  4.     _queue= [[NSOperationQueuealloc] init];  
  5.     URLOperation* operation=[[URLOperationalloc ]initWithURLString:url];  
  6.     // 开始处理  
  7.     [_queueaddOperation:operation];  
  8.     [operation release];//队列已对其retain,可以进行release;  

_queue是一个NSOperationQueue对象,当往其中添加 NSOperation 对象后, NSOperation 线程会被自动执行(不是立即执行,根据调度情况)。

3、KVO编程模型

我们的NSOperation完成了向服务器的请求并将 服务器数据下载到成员变量_data中了。现在的问题是,由于这一切是通过异步操作进行的,我们无法取得_data中的数据,因为我们不知道什么时候异步 操作完成,以便去访问_data属性(假设我们将_data定义为属性了),取得服务器数据。

我们需要一种机制,当NSOperation完成所有工作之后,通知调用线程。

这里我们想到了KVO编程模型(键-值观察模型)。这是cocoa绑定技术中使用的一种设计模式,它可以使一个对象在属性值发生变化时主动通知另一个对象并触发相应的方法。具体请参考cocoa参考库:

  1. http://www.apple.com.cn/developer/mac/library/documentation/Cocoa/Conceptual/CocoaBindings/index.html,  
  2. 以及   
  3. http://www.apple.com.cn/developer/mac/library/documentation/Cocoa/Conceptual/KeyValueObserving/Concepts/
  4. KVOBasics.html#//apple_ref/doc/uid/20002252  

两篇文档。

首先,我们在NSOperation的子类中添加一个BOOL变量,当这个变量变为YES时,标志异步操作已经完成:

  1. BOOL_isFinished; 

在实现中加入这个变量的访问方法:

  1. - (BOOL)isFinished  
  2. {  
  3.     return_isFinished;  

cocoa的KVO模型中,有两种通知观察者的方式,自动通知和手动通知。顾名思义,自动通知由cocoa在属性值变化时自动通知观察者,而手动通知需要在值变化时调用willChangeValueForKey:和didChangeValueForKey:方法通知调用者。 为求简便,我们一般使用自动通知。

要使用自动通知,需要在automaticallyNotifiesObserversForKey方法中明确告诉cocoa,哪些键值要使用自动通知:

  1. //重新实现NSObject类中的automaticallyNotifiesObserversForKey:方法,返回yes表示自动通知。  
  2.  
  3. + (BOOL):(NSString*)key  
  4. {  
  5.     //当这两个值改变时,使用自动通知已注册过的观察者,观察者需要实现observeValueForKeyPath:ofObject:change:context:方法  
  6.     if([key isEqualToString:@"isFinished"])  
  7.     {  
  8.         returnYES;  
  9.     }  
  10.     return[superautomaticallyNotifiesObserversForKey:key];  

然后,在需要改变_isFinished变量的地方,使用

  1. [selfsetValue:[NSNumbernumberWithBool:YES] forKey:@"isFinished"]; 

方法,而不是仅仅使用简单赋值。

我们需要在3个地方改变isFinished值为YES, 请求结束时、连接出错误,线程被cancel。请在对应的方法代码中加入上面的语句。

***,需要在观察者的代码中进行注册。打开ViewController中调用NSOperation子类的地方,加入:

  1.     //kvo注册  
  2.     [operation addObserver:selfforKeyPath:@"isFinished"  
  3.                    options:(NSKeyValueObservingOptionNew| NSKeyValueObservingOptionOld) context:operation];  
  4.                      
  5. 并实现observeValueForKeyPath方法:  
  6.  
  7. //接收变更通知  
  8. - (void)observeValueForKeyPath:(NSString*)keyPath  
  9.                       ofObject:(id)object  
  10.                         change:(NSDictionary*)change  
  11.                        context:(void*)context  
  12. {  
  13.     if([keyPath isEqual:@"isFinished"]) {  
  14.         BOOLisFinished=[[change objectForKey:NSKeyValueChangeNewKey] intValue];  
  15.         if(isFinished) {//如果服务器数据接收完毕  
  16.             [indicatorViewstopAnimating];  
  17.             URLOperation* ctx=(URLOperation*)context;  
  18.             NSStringEncodingenc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);  
  19.             NSLog(@"%@",[[NSStringalloc] initWithData:[ctx data] encoding:enc]);  
  20.             //取消kvo注册  
  21.             [ctx removeObserver:self  
  22.                      forKeyPath:@"isFinished"];  
  23.         }        
  24.     }else{  
  25.         // be sure to call the super implementation  
  26.         // if the superclass implements it  
  27.         [superobserveValueForKeyPath:keyPath  
  28.                              ofObject:object  
  29.                                change:change  
  30.                               context:context];  
  31.     }  

运行程序,查看控制台的输出。

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文件格式可能如下:

  1. <?xml version="1.0" encoding="GB2312" standalone="no" ?> 
  2. <root> 
  3. <login_info> 
  4. <login_status>true</login_status> 
  5. </login_info> 
  6. <List> 
  7.         <system Name=xxx Path=xxx ImageIndex=xxx> 
  8. ……  
  9. </List> 
  10. </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。

  1. #import <Foundation/Foundation.h> 
  2. #import <libxml/tree.h> 
  3. @interfaceBaseXmlParser : NSObject {  
  4.     NSStringEncodingenc;  
  5.     NSMutableDictionary*    _root;  
  6. }  
  7. // Property  
  8. - (void)startElementLocalName:(constxmlChar*)localname  
  9.                        prefix:(constxmlChar*)prefix  
  10.                           URI:(constxmlChar*)URI  
  11.                 nb_namespaces:(int)nb_namespaces  
  12.                    namespaces:(constxmlChar**)namespaces  
  13.                 nb_attributes:(int)nb_attributes  
  14.                  nb_defaulted:(int)nb_defaultedslo  
  15.                    attributes:(constxmlChar**)attributes;  
  16. - (void)endElementLocalName:(constxmlChar*)localname  
  17.                      prefix:(constxmlChar*)prefix URI:(constxmlChar*)URI;  
  18. - (void)charactersFound:(constxmlChar*)ch  
  19.                     len:(int)len;  
  20. -(NSDictionary*)getResult;  
  21. @end  
  22. #import "BaseXmlParser.h"  
  23. @implementationBaseXmlParser  
  24. // Property  
  25. -(id)init{  
  26.     if(self=[superinit]){  
  27.         //构建gb2312的encoding  
  28.         enc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);  
  29.         _root=[[NSMutableDictionaryalloc]init];  
  30.     }  
  31.     returnself;  
  32. }  
  33. -(void)dealloc{  
  34.     [_rootrelease],_root=nil;  
  35.     [superdealloc];  
  36. }  
  37. //--------------------------------------------------------------//  
  38. #pragma mark -- libxml handler,主要是3个回调方法--  
  39. //--------------------------------------------------------------//  
  40. //解析元素开始标记时触发,在这里取元素的属性值  
  41. - (void)startElementLocalName:(constxmlChar*)localname  
  42.                        prefix:(constxmlChar*)prefix  
  43.                           URI:(constxmlChar*)URI  
  44.                 nb_namespaces:(int)nb_namespaces  
  45.                    namespaces:(constxmlChar**)namespaces  
  46.                 nb_attributes:(int)nb_attributes  
  47.                  nb_defaulted:(int)nb_defaultedslo  
  48.                    attributes:(constxmlChar**)attributes  
  49. {    
  50. }  
  51. //解析元素结束标记时触发  
  52. - (void)endElementLocalName:(constxmlChar*)localname  
  53.                      prefix:(constxmlChar*)prefix URI:(constxmlChar*)URI  
  54. {  
  55. }  
  56. //解析元素体时触发  
  57. - (void)charactersFound:(constxmlChar*)ch  
  58.                     len:(int)len  
  59. {  
  60. }  
  61. //返回解析结果  
  62. -(NSDictionary*)getResult{  
  63.     return_root;  
  64. }  
  65. @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方法)中处理;

在“元素标记读取”事件(即endElementLocalName方法)中,则进行标志变量的改变/归零。

  1. #import <Foundation/Foundation.h> 
  2. #import <libxml/tree.h> 
  3. #import "BaseXmlParser.h"  
  4. @interfaceDLTLoginParser : BaseXmlParser {  
  5.     intflag;  
  6.     NSMutableDictionary*    _currentItem;    
  7. }  
  8. - (void)startElementLocalName:(constxmlChar*)localname  
  9.                        prefix:(constxmlChar*)prefix  
  10.                           URI:(constxmlChar*)URI  
  11.                 nb_namespaces:(int)nb_namespaces  
  12.                    namespaces:(constxmlChar**)namespaces  
  13.                 nb_attributes:(int)nb_attributes  
  14.                 nb_defaulted:(int)nb_defaultedslo  
  15.                   attributes:(constxmlChar**)attributes;  
  16. - (void):(constxmlChar*)localname  
  17.                      prefix:(constxmlChar*)prefix URI:(constxmlChar*)URI;  
  18. - (void)charactersFound:(constxmlChar*)ch  
  19.                     len:(int)len;  
  20. @end  
  21.  
  22. #import "DLTLoginParser.h"  
  23. @implementationDLTLoginParser  
  24. -(id)init{  
  25.     if(self=[superinit]){  
  26.         NSMutableArray* items=[[NSMutableArrayalloc]init];  
  27.         [_rootsetObject:items forKey:@"items"];  
  28.         [items release];//已被_root持有了,可以释放  
  29.     }  
  30.     returnself;  
  31. }  
  32. -(void)dealloc{  
  33.     [_currentItemrelease],_currentItem=nil;  
  34.     [superdealloc];  
  35. }  
  36. //--------------------------------------------------------------//  
  37. #pragma mark -- libxml handler,主要是3个回调方法--  
  38. //--------------------------------------------------------------//  
  39. //解析元素开始标记时触发,在这里取元素的属性值  
  40. - (void)startElementLocalName:(constxmlChar*)localname  
  41.                        prefix:(constxmlChar*)prefix  
  42.                           URI:(constxmlChar*)URI  
  43.                 nb_namespaces:(int)nb_namespaces  
  44.                    namespaces:(constxmlChar**)namespaces  
  45.                 nb_attributes:(int)nb_attributes  
  46.                  nb_defaulted:(int)nb_defaultedslo  
  47.                    attributes:(constxmlChar**)attributes  
  48. {  
  49.     // login_status,置标志为1  
  50.     if(strncmp((char*)localname, "login_status", sizeof("login_status")) == 0) {  
  51.         flag=1;  
  52.         return;  
  53.     }  
  54.     // system,置标志为2  
  55.     if(strncmp((char*)localname, "system", sizeof("system")) == 0) {  
  56.         flag=2;  
  57.         _currentItem= [NSMutableDictionarydictionary];  
  58.         //查找属性  
  59.         NSString*key,*val;  
  60.         for(inti=0; i<nb_attributes; i++){  
  61.             key = [NSStringstringWithCString:(constchar*)attributes[0] encoding:NSUTF8StringEncoding];  
  62.             val = [[NSStringalloc] initWithBytes:(constvoid*)attributes[3] length:(attributes[4] - attributes[3]) encoding:NSUTF8StringEncoding];  
  63.             NSLog(@"key=%@,val=%@",key,val);  
  64.             if([@"Name"isEqualToString:key]) {  
  65.                 [_currentItemsetObject:val forKey:@"name"];  
  66.                 break;  
  67.             }  
  68.             // [val release];  
  69.             attributes += 5;//指针移动5个字符串,到下一个属性  
  70.         }  
  71.         [[_rootobjectForKey:@"items"] addObject:_currentItem];  
  72.         return;  
  73.     }  

//解析元素结束标记时触发

  1. - (void)endElementLocalName:(constxmlChar*)localname  
  2.                      prefix:(constxmlChar*)prefix URI:(constxmlChar*)URI  
  3. {  
  4.     flag=0;//标志归零  

//解析元素体时触发

  1. - (void)charactersFound:(constxmlChar*)ch  
  2.                     len:(int)len  
  3. {  
  4.     // 取login_status元素体  
  5.     if(flag==1) {  
  6.         NSString*   string;  
  7.         string = [[NSStringalloc] initWithBytes:ch length:len encoding:NSUTF8StringEncoding];  
  8.         [_rootsetObject:string forKey:@"login_status"];  
  9.         NSLog(@"login_status:%@",string);  
  10.     }  
  11. }  
  12.  
  13. @end 

接下来,改造我们的异步请求操作类URLOperation。首先在interface中增加

两个变量:

  1. xmlParserCtxtPtr  _parserContext;//Xml解析器指针  
  2. BaseXmlParser* baseParser;//Xml解析器 

其中第1个变量(一个结构体)的声明显得有点奇怪,似乎是跟第2个变量混淆了。这是因为libxml是一个c函数库,其函数调用仍然使用一种面向结构的编程风格。所以我们在后面还会看到一些结构体似的变量。

另外,把_data成员的类型从NSMutableData改变为NSMutableDictionary,并把它配置为属性 ,因为我们的请求结果应当被xml解析器解析为dictionary了:

  1. @property(nonatomic,retain) NSDictionary *data; 

当然,记住为它提供访问方法:

  1. @synthesizedata=_data

然后,更改initWithURLString构造方法,为其增加一个名为xmlParser的参数:

  1. - (id)initWithURLString:(NSString*)url xmlParser:(BaseXmlParser*)parser{  
  2.     if(self= [superinit]) {  
  3.         baseParser=[parser retain];  
  4.         NSLog(@"%@",url);  
  5.         _request= [[NSURLRequestalloc] initWithURL:[NSURLURLWithString:url]];//[[NSURLRequest requestWithURL:[NSURL URLWithString:url]]retain];  
  6.         //构建gb2312的encoding  
  7.        enc=CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);  
  8.         _data= [[NSMutableDatadata] retain];  
  9.     }  
  10.     returnself;  

在start方法中,我们可以这样创建一个xml解析器指针:

  1. // 创建XML解析器指针  
  2. _parserContextxmlCreatePushParserCtxt(&_saxHandlerStruct, baseParser, NULL, 0, NULL);  
  3.           
  4. 注意第2个参数就是具体实现了sax解析的xml解析器。这个解析器对象是通过构造函数“注入”的。  
  5.  
  6. 而***个参数是一个结构体指针xmlSAXHandler 结构体,这个结构体我们定义为静态变量(注意把定义放在@implementation⋯⋯@end之外):  
  7.  
  8. //libxml的xmlSAXHandler结构体定义,凡是要实现的handler函数都写在这里,不准备实现的用null代替。一般而言,我们只实现其中3个就够了  
  9.  
  10. staticxmlSAXHandler _saxHandlerStruct = {  
  11.     NULL,            /* internalSubset */  
  12.     NULL,            /* isStandalone   */  
  13.     NULL,            /* hasInternalSubset */  
  14.     NULL,            /* hasExternalSubset */  
  15.     NULL,            /* resolveEntity */  
  16.     NULL,            /* getEntity */  
  17.     NULL,            /* entityDecl */  
  18.     NULL,            /* notationDecl */  
  19.     NULL,            /* attributeDecl */  
  20.     NULL,            /* elementDecl */  
  21.     NULL,            /* unparsedEntityDecl */  
  22.     NULL,            /* setDocumentLocator */  
  23.     NULL,            /* startDocument */  
  24.     NULL,            /* endDocument */  
  25.     NULL,            /* startElement*/  
  26.     NULL,            /* endElement */  
  27.     NULL,            /* reference */  
  28.     charactersFoundHandler, /* characters */  
  29.     NULL,            /* ignorableWhitespace */  
  30.     NULL,            /* processingInstruction */  
  31.     NULL,            /* comment */  
  32.     NULL,            /* warning */  
  33.     NULL,            /* error */  
  34.     NULL,            /* fatalError //: unused error() get all the errors */  
  35.     NULL,            /* getParameterEntity */  
  36.     NULL,            /* cdataBlock */  
  37.     NULL,            /* externalSubset */  
  38.     XML_SAX2_MAGIC,  /* initialized 特殊常量,照写*/  
  39.     NULL,            /* private */  
  40.     startElementHandler,    /* startElementNs */  
  41.     endElementHandler,      /* endElementNs */  
  42.     NULL,            /* serror */  
  43. }; 

机构体中填入了我们准备实现的3个方法句柄,因此我们还应当定义这3个方法。由于结构体是静态的,只能访问静态成员,所以这3个方法也是静态的:

  1. //3个静态方法的实现,其实是调用了参数ctx的成员方法,ctx在_parserContext初始化时传入  
  2. staticvoidstartElementHandler(  
  3.                                 void* ctx,  
  4.                                 constxmlChar* localname,  
  5.                                 constxmlChar* prefix,  
  6.                                 constxmlChar* URI,  
  7.                                 intnb_namespaces,  
  8.                                 constxmlChar** namespaces,  
  9.                                 intnb_attributes,  
  10.                                 intnb_defaulted,  
  11.                                 constxmlChar** attributes)  
  12. {  
  13.     [(BaseXmlParser*)ctx  
  14.      startElementLocalName:localname  
  15.      prefix:prefix URI:URI  
  16.      nb_namespaces:nb_namespaces  
  17.      namespaces:namespaces  
  18.      nb_attributes:nb_attributes  
  19.      nb_defaulted:nb_defaulted  
  20.      attributes:attributes];  
  21. }  
  22.  
  23. staticvoidendElementHandler(  
  24.                               void* ctx,  
  25.                               constxmlChar* localname,  
  26.                               constxmlChar* prefix,  
  27.                               constxmlChar* URI)  
  28. {  
  29.     [(BaseXmlParser*)ctx  
  30.      endElementLocalName:localname  
  31.      prefix:prefix  
  32.      URI:URI];  
  33. }  
  34.  
  35. staticvoidcharactersFoundHandler(  
  36.                                    void* ctx,  
  37.                                    constxmlChar* ch,  
  38.                                    intlen)  
  39. {  
  40.     [(BaseXmlParser*)ctx  
  41.      charactersFound:ch len:len];  

其实这3个静态方法只是调用了超类BaseXmlParser的成员方法, 他的具体类型依赖于ctx的注入类型, 也就是说,这里的ctx可以是任何BaseXmlParser的子类。 实际使用中,我们应该注入其子类, 从而可以根据不同的情况为URLOperation“注入”不同的解析器,实现解析不同的xml文件的目的 。

现在,需要把解析器应用到NSURLConnection的委托方法中(这里省略了部分代码,只列出了新增加的部分):

  1. #pragma mark NSURLConnection delegate Method  
  2.  
  3. // 接收到数据(增量)时  
  4. - (void)connection:(NSURLConnection*)connection  
  5.     didReceiveData:(NSData*)data{  
  6.     // 使用libxml解析器进行xml解析  
  7.     xmlParseChunk(_parserContext, (constchar*)[databytes], [datalength], 0);  
  8.          ⋯⋯  
  9. }  
  10.  
  11. // HTTP请求结束时  
  12.  
  13. - (void)connectionDidFinishLoading:(NSURLConnection*)connection {  
  14.              if(baseParser!=nil&& baseParser!=NULL){  
  15.         [selfsetData:[[NSDictionaryalloc] initWithDictionary:[baseParsergetResult]]];  
  16.     }else{  
  17.         NSLog(@"baseparser is nil");  
  18.     }  
  19.     // 添加解析数据(结束),注意***一个参数termindate  
  20.  
  21.     xmlParseChunk(_parserContext, NULL, 0, 1);  
  22.  
  23.    
  24.  
  25.     // 释放XML解析器  
  26.     if(_parserContext) {  
  27.         xmlFreeParserCtxt(_parserContext), _parserContextNULL;  
  28.     }  
  29.  
  30. ⋯⋯  
  31. }  
  32.  
  33. -(void)connection: (NSURLConnection*) connection didFailWithError: (NSError*) error{  
  34.  
  35.     // 释放XML解析器  
  36.     if(_parserContext) {  
  37.         xmlFreeParserCtxt(_parserContext), _parserContextNULL;  
  38.     }  
  39. }  
  40. @end 

接下来,在“登录”按钮中代码也要做相应的修改,因为URLOperation的构造函数要求传递一个具体的xml解析器对象:

  1. //构造xmlparser  
  2. DLTLoginParser* parser=[[DLTLoginParseralloc]init];  
  3. URLOperation* operation=[[URLOperationalloc ]initWithURLString:url xmlParser:parser];  
  4. [parser release]; 

然后,在接收变更通知方法中打印解析结果:

  1. URLOperation* ctx=(URLOperation*)context;  
  2. NSLog(@"%@",[ctx data]); 

后台打印结果:

  1. {  
  2.     items =     (  
  3.                 {  
  4.             name = "/U4e91/U7535/U4f01/U4fe1/U901a";  
  5.         },  
  6.                 {  
  7.             name = "/U79fb/U52a8/U8c03/U5ea6";  
  8.         },  
  9.                 {  
  10.             name = "/U79fb/U52a8/U62a2/U4fee";  
  11.         }  
  12.     );  
  13.     "login_status" = true;  

小结:iPhone开发中使用NSOperation实现异步下载的内容介绍完了,希望通过本文的学习能对你有所帮助!

责任编辑:zhaolei 来源: 论坛
相关推荐

2011-08-08 13:50:29

iPhone开发 NSOperatio 多线程

2011-08-15 15:26:20

iPhone开发CocoaXML

2011-07-20 14:53:28

iPhone NSLocalize 国际化

2011-08-08 10:42:46

iPhone UITableVie 分页

2011-08-11 13:26:30

iPhoneNSLocalized

2011-08-17 13:27:08

iPhone游戏开发objective-c

2021-03-22 08:45:30

异步编程Java

2011-08-17 14:57:31

iPhone应用视频播放

2015-06-16 11:06:42

JavaCompletable

2013-07-15 15:12:40

iOS多线程NSOperationNSOperation

2024-02-07 11:44:20

NestJSRxJS异步编程

2009-02-24 11:05:07

ibmdwiphonegoogle

2010-10-18 13:16:24

GalleryAndroid

2021-01-19 05:30:55

C# 8异步流IEnumerable

2011-07-08 15:08:16

iPhone 图片

2011-07-26 14:18:20

2011-08-15 15:44:46

iPhone开发PDF

2011-07-27 11:19:33

iPhone UITableVie

2011-07-28 10:11:54

iPhone开发 备忘

2011-08-18 16:24:44

iPhone开发图片
点赞
收藏

51CTO技术栈公众号