ASIHTTPRequest和libxml结合,实现边请求边解析
2011-06-03 16:29
591 查看
ASIHTTPRequests
是非常强大的 http 异步请求开源框架,libxml 是非常老牌的 C 语言xml函数库。在 http + xml 文件的 javaEE-iPhone 应用中,如何把二者结合起来,实现在异步请求数据的同时,进行xml的同步解析呢?
这涉及到 3 方面的关键知识:
¥
ASIHTTPRequest
这部分的内容可以参考作者另一篇博文《ASIHTTPRequest的使用》。
¥
NSOperation 和 Libxml
这部分内容在作者的一篇博文《使用NSOperation实现异步下载》中也有介绍。
背景知识已经具备,下面让我们继续。
一、准备libxml环境
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>
了。
至于 ASIHTTPRequest 的使用环境,请参考
《ASIHTTPRequest的使用》
进行。
二、线程管理
首先,我们肯定要使用线程来进行实现。多线程的操作使用NSOperation子类。
新建 o-c class,命名为
SyncRequestParseOperation
,它必需继承NSOperation。
我们决定不使用继承而使用聚合来让它同时具有 ASIHTTPRequest 和 Xml 解析的功能,因此我们导入了libxml/tree.h 和 ASIHTTPRequest.h 。
由于服务器使用了GBK 编码,所以我们也使用了
NSStringEncoding
。
kRequestStatus
定义了一个枚举,用来表示
SyncRequestParseOperation
的不同状态:请求完毕、请求失败、收到数据包。这3种可能状态会被成员变量 status 使用,实际上它是个int。头文件定义如下:
#import
<libxml/tree.h>
#import
"BaseXmlParser.h"
#import
"ASIHTTPRequest.h"
enum
kRequestStatus
{
kRequestStatusFinished
,
kRequestStatusFailed
,
kRequestStatusDataReceived
};
@interface
SyncRequestParseOperation : NSOperation
{
NSURL
*
_url
;
NSDictionary
*
_data
;
//
构建
gb2312
的
encoding
NSStringEncoding
enc
;
//Xml
解析器指针
xmlParserCtxtPtr
_parserContext
;
BaseXmlParser
*
baseParser
;
id
delegate
,
progressDelegate
;
int
status
;
}
@property
(
nonatomic
,
retain
)
NSDictionary *data;
@property
(
nonatomic
,
retain
) NSURL
*url;
@property
(
assign
)
int
status;
- (
id
)initWithURLString:(
NSString
*)
url
xmlParser:(
BaseXmlParser
*) parser
delegate:(
id
)obj;
-(
void
)setProgressDelegate:(
id
)progress;
-(
void
)statusChangedNotify;
@end
BaseXmlParser
是一个Xml解析器的基类,我们使用它的子类来进行Xml解析,在其中定义了一些使用 libxml 时特有的结构体和函数声明。有了它,我们就可以在其子类中覆盖某些方法来解析各种不同的XML
文件。
BaseXmlParser
及其子类我们后面会介绍。
delegate和
progressDelegate 保存两个对象的 id 引用。前者是负责响应
SyncRequestParseOperation
类的一些特殊的通知消息,比如某个状态的改变;后者负责根据收到的数据实时进行进度显示。
接下来我们看实现,首先是初始化
init 方法:
initWithURLString
:xmlParser: delegate:(
id
)obj
方法是个便利的初始化方法,分别对3个成员进行初始化,而不必要对它们一一调用setter方法:http请求地址url、解析器、通知消息的委托对象。
- (
id
)initWithURLString:(
NSString
*)
url
xmlParser:(
BaseXmlParser
*)parser delegate:(
id
)obj{
if
(
self
= [
super
init
]) {
_url
=[[
NSURL
alloc
]
initWithString
:
url
];
delegate
=obj;
baseParser
=[parser
retain
];
//
构建
gb2312
的
encoding
enc
=
CFStringConvertEncodingToNSStringEncoding
(
kCFStringEncodingGB_18030_2000
);
}
return
self
;
}
除了init方法外,我们也提供了 setProgressDelegate
方法:
-(
void
)setProgressDelegate:(
id
)progress{
progressDelegate
=progress;
}
用于对 progressDelegate
进行初始化。
接下来是最主要的部分,NSOperation的生命周期方法:
#pragma
mark NSOperation
的生命周期方法
//
开始线程
-
本类的主方法
- (
void
)start {
NSLog
(
@"operation
start!"
);
if
(![
self
isCancelled
]) {
//
创建
XML
解析器指针
_parserContext
=
xmlCreatePushParserCtxt
(&
_saxHandlerStruct
,
baseParser
,
NULL
,
0
,
NULL
);
//
以异步方式处理事件,并设置代理块
__block
ASIHTTPRequest
*request =
[
ASIHTTPRequest
requestWithURL
:
_url
];
//
设置进度代理
if
(
progressDelegate
!=
nil
) {
[request
setDownloadProgressDelegate
:
progressDelegate
];
}
//
使用
complete
块,在下载完时做一些事情
[request
setCompletionBlock
:^(
void
){
[
self
setStatus
:
kRequestStatusFinished
];
NSLog
(
@"request
completed!"
);
//
添加解析数据(结束),注意最后一个参数
terminate
xmlParseChunk
(
_parserContext
,
NULL
,
0
,
1
);
//
添加解析数据(结束),
if
(
baseParser
!=
nil
){
[
self
setData
:[[
baseParser
getResult
]
copy
]];
}
else
{
NSLog
(
@"baseparser
is nil"
);
}
//
释放
XML
解析器
if
(
_parserContext
) {
xmlFreeParserCtxt
(
_parserContext
),
_parserContext
=
NULL
;
}
[
self
statusChangedNotify
];
}];
//
使用
failed
块,在下载失败时做一些事情
[request
setFailedBlock
:^(
void
){
[
self
setStatus
:
kRequestStatusFailed
];
NSLog
(
@"request
failed !"
);
//
释放
XML
解析器指针
if
(
_parserContext
) {
xmlFreeParserCtxt
(
_parserContext
),
_parserContext
=
NULL
;
}
[
self
statusChangedNotify
];
}];
//
使用
received
块,在接受到数据时做一些事情
[request
setDataReceivedBlock
:^(
NSData
*
data
){
[
self
setStatus
:
kRequestStatusDataReceived
];
NSLog
(
@"received
data:%d"
,
data
.
length
);
//
添加解析数据(结束),注意最后一个参数
terminate
if
(
baseParser
!=
nil
&&
baseParser
!=
NULL
){
[
self
setData
:[[
baseParser
getResult
]
copy
]];
}
else
{
NSLog
(
@"baseparser
is nil"
);
}
//
使用
libxml
解析器进行
xml
解析
xmlParseChunk
(
_parserContext
, (
const
char
*)[
data
bytes], [
data
length
],
0
);
[
self
statusChangedNotify
];
}];
[request
startAsynchronous
];
}
}
//
停止线程
- (
void
)cancel
{
[
super
cancel
];
}
对于一个NSOperation 来说,最主要的是start 方法,因为线程在这里启动。由于使用了
ASIHTTPRequest 的异步方式,所以在start方法中我们没有使用
NSRunLoop
循环(
这个问题参考 http://www.cocoabuilder.com/archive/cocoa/279826-nsurlrequest-and-nsoperationqueue.html )。因为 ASIHTTPRequest 的startAsynchronous 方法提供了额外的线程。我们在 start 方法中使用了一个ASIHTTPRequest
,利用 BaseXmlParser 解析器来提供一系列符合 libxml 规范的回调函数,以响应 sax 解析事件。当然,由于我们要实现“边接收数据,边解析Xml”的目的,我们在
ASIHTTPRequest 的三个委托块中,就对数据进行了处理(使用 libxml 的函数)。
比较怪异的是对 ASIHTTPRequest 的3个事件委托中使用了块语法,块语法介绍可以参考作者另一篇(翻译)博文《块编程指南》。
为了把3个委托事件通知给delegate,我们需要在3个事件委托块中调用delegate
的相应方法:
// status
状态变化通知
-(
void
)statusChangedNotify{
if
(
delegate
!=
nil
) {
SEL
sel=
NSSelectorFromString
(
@"syncRequestParseStatusNofity:"
);
if
([
delegate
respondsToSelector
:sel]){
[
delegate
performSelector
:sel
withObject
:
self
];
//
注意冒号说明带
1
个参数
}
}
}
为了简便,我没有定义新的协议,而只是使用方法名
syncRequestParseStatusNofity:
作为内部协议。如果delegate要想接收通知,就必需实现该方法。作为一种技巧,其中使用了反射机制,避免运行时错误。
三、Sax 异步解析
libxml 是C函数库,其中很多函数需要使用令人生畏的结构体定义。为了便于扩展,这些定义被放到了
BaseXmlParser 类中:
#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
*)getAtributes:(
const
xmlChar
**)attributes withSize:(
int
)nb_attributes;
-(
NSDictionary
*)getResult;
@end
//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];
}
//libxml
的
xmlSAXHandler
结构体定义,凡是要实现的
handler
函数都写在这里,不准备实现的用
null
代替。一般而言,我们只实现其中
3
个就够了
static
xmlSAXHandler
_saxHandlerStruct
= {
NULL
,
/* internalSubset */
NULL
,
/* isStandalone
*/
NULL
,
/* hasInternalSubset
*/
NULL
,
/* hasExternalSubset
*/
NULL
,
/* resolveEntity
*/
NULL
,
/* getEntity
*/
NULL
,
/* entityDecl
*/
NULL
,
/* notationDecl
*/
NULL
,
/* attributeDecl
*/
NULL
,
/* elementDecl
*/
NULL
,
/* unparsedEntityDecl
*/
NULL
,
/* setDocumentLocator
*/
NULL
,
/* startDocument
*/
NULL
,
/* endDocument
*/
NULL
,
/* startElement*/
NULL
,
/*
endElement */
NULL
,
/*
reference */
charactersFoundHandler,
/*
characters */
NULL
,
/* ignorableWhitespace */
NULL
,
/* processingInstruction
*/
NULL
,
/* comment
*/
NULL
,
/* warning
*/
NULL
,
/* error
*/
NULL
,
/* fatalError //: unused
error() get all the errors */
NULL
,
/* getParameterEntity */
NULL
,
/* cdataBlock
*/
NULL
,
/* externalSubset
*/
XML_SAX2_MAGIC
,
/* initialized
特殊常量,照写
*/
NULL
,
/* private
*/
startElementHandler,
/*
startElementNs */
endElementHandler,
/*
endElementNs */
NULL
,
/* serror */
};
在 BaseXmlParser 类的头文件中,可以分为两部分。
1.
第一部分是 interface 定义,定义了BaseXmlParser类的成员,包括:
¥
成员变量
enc:基于和前面同样的原因,用于定义GBK编码。
_root:一个Dictionary,用于保存解析后Xml对象,一个xml文档只有一个root 元素,因此用一个Dictionary对象即可。
¥
成员方法
libxml 回调方法:前3个很像是C语言函数的方法其实都是被libxml回调的,它们会在3个静态函数(在第二部分)中调用。
getAttributes方法:这个是一个方便的获取 xml 元素属性的方法。由于本例中的 XML 文档大量使用了属性,所以这个方法很实用。
getResult方法:用于获得 XML 文档解析结果,即 _root 对象。
2.
第二部分是 libxml 回调函数和结构体定义,包括:
¥
回调函数
本例我们决定实现3个回调函数,分别用于响应 Sax 解析中的3个事件:
处理 XML 元素开始标记、处理 XML 元素结束标记、处理 XML
元素体。
为了更 OO 一些,我们没有直接在这 3 个函数中写对应的 XML 解析代码,而是调用了类的成员方法进行处理。这样,我们可以在
implement 部分写入具体的代码。
¥
结构体
只需要填充一个结构体
xmlSAXHandler
即可。这个结构成员数量众多(31个),但我们只需填充你要实现的几个。例如,我们要实现3个回调函数,那么只消在对应的地方填充这3个函数名即可(此外有一个特殊的成员叫
XML_SAX2_MAGIC
,你照填就是了)。为了便于大家理解这些成员所代表的意义,我们也在旁边做了注释,你可以对照着看。
接下来是implement (实现)。
#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
];
}
//
一个便利方法,用于获取元素的属性值
-(
NSDictionary
*)getAtributes:(
const
xmlChar
**)attributes
withSize:(
int
)nb_attributes{
NSMutableDictionary
* atts=[[
NSMutableDictionary
alloc
]
init
];
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
];
[atts
setObject
:val
forKey
:key];
[key
release
],[val
release
];
attributes
+=
5
;
//
指针移动
5
个字符串,到下一个属性
}
return
atts;
}
//--------------------------------------------------------------//
#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
可以看到,除了 getAttributes 和
getResult 方法外,我们都没有进行其它方法的实现。这是因为 Sax 解析跟
Dom 解析不同,针对不同的 XML 文档很难使用相同的逻辑解析,因此我们准备把剩下的内容留给子类来实现,这样不同的XML 文档可以通过不同的子类来进行解析,而不用在每个子类中都写一遍那些怪异的
C 回调函数和结构体声明。
我们要解析的 XML 文档可能是这样的:
<root>
<List Name="同事">
<user name="t2" phone="13884831140"/>
<user name="t3" phone="15877103548"/>
<user name="t1" phone="13399459990"/>
</List>
<List Name="好友">
<user name="f2" phone="13828831140"/>
<user name="f3" phone="15886103548"/>
<user name="f1" phone="13019459990"/>
</List>
</root>
也就是说,这是一个通讯录类似的东西。通讯录把电话号码按性质分成不同的组,就像Windows mobile智能手机上的的通讯录,把电话号码按“家庭”、“好友”、“同事”等进行划分。
我们新建一个 BaseXmlParser的子类
TelNoXmlParser ,让这个 TelNoXmlParser 去实现 3 个回调方法:
#import
<Foundation/Foundation.h>
#import
<libxml/tree.h>
#import
"BaseXmlParser.h"
@interface
TelNoXmlParser : BaseXmlParser {
BOOL
loginSuccess
;
NSMutableArray
*
groups
,*
members
;
NSMutableDictionary
*
_group
;
NSDictionary
*
_user
;
}
@end
#import
"TelNoXmlParser.h"
@implementation
TelNoXmlParser
-(
id
)init{
if
(
self
=[
super
init
]) {
//
一个
groups
数组,代表了所有
List
groups
=[[
NSMutableArray
alloc
]
init
];
[
_root
setObject
:
groups
forKey
:
@"items"
];
loginSuccess
=
YES
;
}
return
self
;
}
-(
void
)dealloc{
[
_group
release
],
_group
=
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
{
//
我们关心
8
个元素标签,所以设置了
8
个标志位
//
login_status
if
(
strncmp
((
char
*)localname,
"login_status"
,
sizeof
(
"login_status"
)) ==
0
) {
loginSuccess
=
NO
;
return
;
}
if
(
loginSuccess
) {
// List
if
(
strncmp
((
char
*)localname,
"List"
,
sizeof
(
"List"
)) ==
0
) {
NSDictionary
* atts=[
self
getAtributes
:attributes
withSize
:nb_attributes];
//
获取
List
的所有属性
_group
=[[
NSMutableDictionary
alloc
]
init
];
members
=[[
NSMutableArray
alloc
]
init
];
[
_group
setObject
:
members
forKey
:
@"members"
];
[
_group
setObject
:[[
NSString
alloc
]
initWithString
:(
NSString
*)[atts
objectForKey
:
@"Name"
]]
forKey
:
@"groupname"
];
[
groups
addObject
:
_group
];
//
把
group
加入数组
return
;
}
// user
if
(
strncmp
((
char
*)localname,
"user"
,
sizeof
(
"user"
)) ==
0
) {
NSDictionary
* atts=[
self
getAtributes
:attributes
withSize
:nb_attributes];
//
获取
List
的所有属性
_user
=[[
NSDictionary
alloc
]
initWithDictionary
:atts];
[
members
addObject
:
_user
];
return
;
}
}
}
//
解析元素结束标记时触发
- (
void
)endElementLocalName:(
const
xmlChar
*)localname
prefix:(
const
xmlChar
*)prefix
URI:(
const
xmlChar
*)URI
{
if
(
strncmp
((
char
*)localname,
"root"
,
sizeof
(
"root"
)) ==
0
){
//root
结束时置
login_status
标志
if
(
loginSuccess
) {
[
_root
setObject
:
@"true"
forKey
:
@"login_status"
];
}
else
{
[
_root
setObject
:
@"false"
forKey
:
@"login_status"
];
}
}
if
(
loginSuccess
) {
//
我们还关心
<List>
的结束标记
if
(
strncmp
((
char
*)localname,
"List"
,
sizeof
(
"List"
)) ==
0
) {
[
_group
release
],
_group
=
nil
;
//
回收
_group
对象,以便重复利用
}
else
if
(
strncmp
((
char
*)localname,
"user"
,
sizeof
(
"user"
)) ==
0
){
[
_user
release
],
_user
=
nil
;
//
回收
_user
对象,以便重复利用
}
}
}
//
解析元素体时触发
- (
void
)charactersFound:(
const
xmlChar
*)ch
len:(
int
)len
{
//
没有元素体需要关心
}
@end
接下来我们看如何在 ViewController 中使用。
四、在 UI 中测试
在 ViewController 中放入一个按钮和一个
WebView,当点击按钮时,请求http服务器,获取通讯录XML 数据,并解析为 Dictionary 对象。把解析结果显示在 WebView 中。
这是按钮的 touch up
inside 事件代码:
-(
IBAction
)go{
if
(
_queue
==
nil
){
_queue
= [[
NSOperationQueue
alloc
]
init
];
}
[
button
setEnabled
:
NO
];
[
progress
setProgress
:
0
];
[
webView
loadHTMLString
:
@""
baseURL
:[
NSURL
URLWithString
:
URL
]];
//
构造
xmlparser
TelNoXmlParser
* parser=[[
TelNoXmlParser
alloc
]
init
];
//
把
self
注册为
delegate
,这样
self
必需实现
syncRequestParseStatusNofity:
方法
,
以接收
statusChanged
方法
SyncRequestParseOperation
*
operation=[[
SyncRequestParseOperation
alloc ]
initWithURLString
:
URL
xmlParser
:parser
delegate
:
self
];
//
把
progress
设置为
progressDelegate
,这样会显示进度
[operation
setProgressDelegate
:
progress
];
[parser
release
];
// opertaion
已
retain
,可以
release
[
_queue
addOperation
:operation];
//
开始处理
[operation
release
];
//
队列已
retain
,可以
release
;
}
这是异步消息到达时的处理代码,当数据接收完时,我们把解析结果在WebView 中显示:
//
实现
statusChanged
通知方法
-(
void
)syncRequestParseStatusNofity:(
id
)sender{
SyncRequestParseOperation
*
operation=(
SyncRequestParseOperation
*)sender;
int
status=[operation
status
];
NSLog
(
@"status:%d"
,status);
if
(status==
kRequestStatusFinished
){
//
如数据接收完成
[
button
setEnabled
:
YES
];
NSDictionary
*
d=[operation
data
];
[
webView
loadHTMLString
:[d
description
]
baseURL
:[
NSURL
URLWithString
:
URL
]];
}
}
这是程序运行时 WebView 显示效果:
注意,当xml 文档比较大时,WebView
的内容是从上到下逐渐刷新的。
这是控制台输出,可以看到服务器响应的数据是被分成很多次下载的:
是非常强大的 http 异步请求开源框架,libxml 是非常老牌的 C 语言xml函数库。在 http + xml 文件的 javaEE-iPhone 应用中,如何把二者结合起来,实现在异步请求数据的同时,进行xml的同步解析呢?
这涉及到 3 方面的关键知识:
¥
ASIHTTPRequest
这部分的内容可以参考作者另一篇博文《ASIHTTPRequest的使用》。
¥
NSOperation 和 Libxml
这部分内容在作者的一篇博文《使用NSOperation实现异步下载》中也有介绍。
背景知识已经具备,下面让我们继续。
一、准备libxml环境
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>
了。
至于 ASIHTTPRequest 的使用环境,请参考
《ASIHTTPRequest的使用》
进行。
二、线程管理
首先,我们肯定要使用线程来进行实现。多线程的操作使用NSOperation子类。新建 o-c class,命名为
SyncRequestParseOperation
,它必需继承NSOperation。
我们决定不使用继承而使用聚合来让它同时具有 ASIHTTPRequest 和 Xml 解析的功能,因此我们导入了libxml/tree.h 和 ASIHTTPRequest.h 。
由于服务器使用了GBK 编码,所以我们也使用了
NSStringEncoding
。
kRequestStatus
定义了一个枚举,用来表示
SyncRequestParseOperation
的不同状态:请求完毕、请求失败、收到数据包。这3种可能状态会被成员变量 status 使用,实际上它是个int。头文件定义如下:
#import
<libxml/tree.h>
#import
"BaseXmlParser.h"
#import
"ASIHTTPRequest.h"
enum
kRequestStatus
{
kRequestStatusFinished
,
kRequestStatusFailed
,
kRequestStatusDataReceived
};
@interface
SyncRequestParseOperation : NSOperation
{
NSURL
*
_url
;
NSDictionary
*
_data
;
//
构建
gb2312
的
encoding
NSStringEncoding
enc
;
//Xml
解析器指针
xmlParserCtxtPtr
_parserContext
;
BaseXmlParser
*
baseParser
;
id
delegate
,
progressDelegate
;
int
status
;
}
@property
(
nonatomic
,
retain
)
NSDictionary *data;
@property
(
nonatomic
,
retain
) NSURL
*url;
@property
(
assign
)
int
status;
- (
id
)initWithURLString:(
NSString
*)
url
xmlParser:(
BaseXmlParser
*) parser
delegate:(
id
)obj;
-(
void
)setProgressDelegate:(
id
)progress;
-(
void
)statusChangedNotify;
@end
BaseXmlParser
是一个Xml解析器的基类,我们使用它的子类来进行Xml解析,在其中定义了一些使用 libxml 时特有的结构体和函数声明。有了它,我们就可以在其子类中覆盖某些方法来解析各种不同的XML
文件。
BaseXmlParser
及其子类我们后面会介绍。
delegate和
progressDelegate 保存两个对象的 id 引用。前者是负责响应
SyncRequestParseOperation
类的一些特殊的通知消息,比如某个状态的改变;后者负责根据收到的数据实时进行进度显示。
接下来我们看实现,首先是初始化
init 方法:
initWithURLString
:xmlParser: delegate:(
id
)obj
方法是个便利的初始化方法,分别对3个成员进行初始化,而不必要对它们一一调用setter方法:http请求地址url、解析器、通知消息的委托对象。
- (
id
)initWithURLString:(
NSString
*)
url
xmlParser:(
BaseXmlParser
*)parser delegate:(
id
)obj{
if
(
self
= [
super
init
]) {
_url
=[[
NSURL
alloc
]
initWithString
:
url
];
delegate
=obj;
baseParser
=[parser
retain
];
//
构建
gb2312
的
encoding
enc
=
CFStringConvertEncodingToNSStringEncoding
(
kCFStringEncodingGB_18030_2000
);
}
return
self
;
}
除了init方法外,我们也提供了 setProgressDelegate
方法:
-(
void
)setProgressDelegate:(
id
)progress{
progressDelegate
=progress;
}
用于对 progressDelegate
进行初始化。
接下来是最主要的部分,NSOperation的生命周期方法:
#pragma
mark NSOperation
的生命周期方法
//
开始线程
-
本类的主方法
- (
void
)start {
NSLog
(
@"operation
start!"
);
if
(![
self
isCancelled
]) {
//
创建
XML
解析器指针
_parserContext
=
xmlCreatePushParserCtxt
(&
_saxHandlerStruct
,
baseParser
,
NULL
,
0
,
NULL
);
//
以异步方式处理事件,并设置代理块
__block
ASIHTTPRequest
*request =
[
ASIHTTPRequest
requestWithURL
:
_url
];
//
设置进度代理
if
(
progressDelegate
!=
nil
) {
[request
setDownloadProgressDelegate
:
progressDelegate
];
}
//
使用
complete
块,在下载完时做一些事情
[request
setCompletionBlock
:^(
void
){
[
self
setStatus
:
kRequestStatusFinished
];
NSLog
(
@"request
completed!"
);
//
添加解析数据(结束),注意最后一个参数
terminate
xmlParseChunk
(
_parserContext
,
NULL
,
0
,
1
);
//
添加解析数据(结束),
if
(
baseParser
!=
nil
){
[
self
setData
:[[
baseParser
getResult
]
copy
]];
}
else
{
NSLog
(
@"baseparser
is nil"
);
}
//
释放
XML
解析器
if
(
_parserContext
) {
xmlFreeParserCtxt
(
_parserContext
),
_parserContext
=
NULL
;
}
[
self
statusChangedNotify
];
}];
//
使用
failed
块,在下载失败时做一些事情
[request
setFailedBlock
:^(
void
){
[
self
setStatus
:
kRequestStatusFailed
];
NSLog
(
@"request
failed !"
);
//
释放
XML
解析器指针
if
(
_parserContext
) {
xmlFreeParserCtxt
(
_parserContext
),
_parserContext
=
NULL
;
}
[
self
statusChangedNotify
];
}];
//
使用
received
块,在接受到数据时做一些事情
[request
setDataReceivedBlock
:^(
NSData
*
data
){
[
self
setStatus
:
kRequestStatusDataReceived
];
NSLog
(
@"received
data:%d"
,
data
.
length
);
//
添加解析数据(结束),注意最后一个参数
terminate
if
(
baseParser
!=
nil
&&
baseParser
!=
NULL
){
[
self
setData
:[[
baseParser
getResult
]
copy
]];
}
else
{
NSLog
(
@"baseparser
is nil"
);
}
//
使用
libxml
解析器进行
xml
解析
xmlParseChunk
(
_parserContext
, (
const
char
*)[
data
bytes], [
data
length
],
0
);
[
self
statusChangedNotify
];
}];
[request
startAsynchronous
];
}
}
//
停止线程
- (
void
)cancel
{
[
super
cancel
];
}
对于一个NSOperation 来说,最主要的是start 方法,因为线程在这里启动。由于使用了
ASIHTTPRequest 的异步方式,所以在start方法中我们没有使用
NSRunLoop
循环(
这个问题参考 http://www.cocoabuilder.com/archive/cocoa/279826-nsurlrequest-and-nsoperationqueue.html )。因为 ASIHTTPRequest 的startAsynchronous 方法提供了额外的线程。我们在 start 方法中使用了一个ASIHTTPRequest
,利用 BaseXmlParser 解析器来提供一系列符合 libxml 规范的回调函数,以响应 sax 解析事件。当然,由于我们要实现“边接收数据,边解析Xml”的目的,我们在
ASIHTTPRequest 的三个委托块中,就对数据进行了处理(使用 libxml 的函数)。
比较怪异的是对 ASIHTTPRequest 的3个事件委托中使用了块语法,块语法介绍可以参考作者另一篇(翻译)博文《块编程指南》。
为了把3个委托事件通知给delegate,我们需要在3个事件委托块中调用delegate
的相应方法:
// status
状态变化通知
-(
void
)statusChangedNotify{
if
(
delegate
!=
nil
) {
SEL
sel=
NSSelectorFromString
(
@"syncRequestParseStatusNofity:"
);
if
([
delegate
respondsToSelector
:sel]){
[
delegate
performSelector
:sel
withObject
:
self
];
//
注意冒号说明带
1
个参数
}
}
}
为了简便,我没有定义新的协议,而只是使用方法名
syncRequestParseStatusNofity:
作为内部协议。如果delegate要想接收通知,就必需实现该方法。作为一种技巧,其中使用了反射机制,避免运行时错误。
三、Sax 异步解析
libxml 是C函数库,其中很多函数需要使用令人生畏的结构体定义。为了便于扩展,这些定义被放到了BaseXmlParser 类中:
#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
*)getAtributes:(
const
xmlChar
**)attributes withSize:(
int
)nb_attributes;
-(
NSDictionary
*)getResult;
@end
//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];
}
//libxml
的
xmlSAXHandler
结构体定义,凡是要实现的
handler
函数都写在这里,不准备实现的用
null
代替。一般而言,我们只实现其中
3
个就够了
static
xmlSAXHandler
_saxHandlerStruct
= {
NULL
,
/* internalSubset */
NULL
,
/* isStandalone
*/
NULL
,
/* hasInternalSubset
*/
NULL
,
/* hasExternalSubset
*/
NULL
,
/* resolveEntity
*/
NULL
,
/* getEntity
*/
NULL
,
/* entityDecl
*/
NULL
,
/* notationDecl
*/
NULL
,
/* attributeDecl
*/
NULL
,
/* elementDecl
*/
NULL
,
/* unparsedEntityDecl
*/
NULL
,
/* setDocumentLocator
*/
NULL
,
/* startDocument
*/
NULL
,
/* endDocument
*/
NULL
,
/* startElement*/
NULL
,
/*
endElement */
NULL
,
/*
reference */
charactersFoundHandler,
/*
characters */
NULL
,
/* ignorableWhitespace */
NULL
,
/* processingInstruction
*/
NULL
,
/* comment
*/
NULL
,
/* warning
*/
NULL
,
/* error
*/
NULL
,
/* fatalError //: unused
error() get all the errors */
NULL
,
/* getParameterEntity */
NULL
,
/* cdataBlock
*/
NULL
,
/* externalSubset
*/
XML_SAX2_MAGIC
,
/* initialized
特殊常量,照写
*/
NULL
,
/* private
*/
startElementHandler,
/*
startElementNs */
endElementHandler,
/*
endElementNs */
NULL
,
/* serror */
};
在 BaseXmlParser 类的头文件中,可以分为两部分。
1.
第一部分是 interface 定义,定义了BaseXmlParser类的成员,包括:
¥
成员变量
enc:基于和前面同样的原因,用于定义GBK编码。
_root:一个Dictionary,用于保存解析后Xml对象,一个xml文档只有一个root 元素,因此用一个Dictionary对象即可。
¥
成员方法
libxml 回调方法:前3个很像是C语言函数的方法其实都是被libxml回调的,它们会在3个静态函数(在第二部分)中调用。
getAttributes方法:这个是一个方便的获取 xml 元素属性的方法。由于本例中的 XML 文档大量使用了属性,所以这个方法很实用。
getResult方法:用于获得 XML 文档解析结果,即 _root 对象。
2.
第二部分是 libxml 回调函数和结构体定义,包括:
¥
回调函数
本例我们决定实现3个回调函数,分别用于响应 Sax 解析中的3个事件:
处理 XML 元素开始标记、处理 XML 元素结束标记、处理 XML
元素体。
为了更 OO 一些,我们没有直接在这 3 个函数中写对应的 XML 解析代码,而是调用了类的成员方法进行处理。这样,我们可以在
implement 部分写入具体的代码。
¥
结构体
只需要填充一个结构体
xmlSAXHandler
即可。这个结构成员数量众多(31个),但我们只需填充你要实现的几个。例如,我们要实现3个回调函数,那么只消在对应的地方填充这3个函数名即可(此外有一个特殊的成员叫
XML_SAX2_MAGIC
,你照填就是了)。为了便于大家理解这些成员所代表的意义,我们也在旁边做了注释,你可以对照着看。
接下来是implement (实现)。
#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
];
}
//
一个便利方法,用于获取元素的属性值
-(
NSDictionary
*)getAtributes:(
const
xmlChar
**)attributes
withSize:(
int
)nb_attributes{
NSMutableDictionary
* atts=[[
NSMutableDictionary
alloc
]
init
];
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
];
[atts
setObject
:val
forKey
:key];
[key
release
],[val
release
];
attributes
+=
5
;
//
指针移动
5
个字符串,到下一个属性
}
return
atts;
}
//--------------------------------------------------------------//
#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
可以看到,除了 getAttributes 和
getResult 方法外,我们都没有进行其它方法的实现。这是因为 Sax 解析跟
Dom 解析不同,针对不同的 XML 文档很难使用相同的逻辑解析,因此我们准备把剩下的内容留给子类来实现,这样不同的XML 文档可以通过不同的子类来进行解析,而不用在每个子类中都写一遍那些怪异的
C 回调函数和结构体声明。
我们要解析的 XML 文档可能是这样的:
<root>
<List Name="同事">
<user name="t2" phone="13884831140"/>
<user name="t3" phone="15877103548"/>
<user name="t1" phone="13399459990"/>
</List>
<List Name="好友">
<user name="f2" phone="13828831140"/>
<user name="f3" phone="15886103548"/>
<user name="f1" phone="13019459990"/>
</List>
</root>
也就是说,这是一个通讯录类似的东西。通讯录把电话号码按性质分成不同的组,就像Windows mobile智能手机上的的通讯录,把电话号码按“家庭”、“好友”、“同事”等进行划分。
我们新建一个 BaseXmlParser的子类
TelNoXmlParser ,让这个 TelNoXmlParser 去实现 3 个回调方法:
#import
<Foundation/Foundation.h>
#import
<libxml/tree.h>
#import
"BaseXmlParser.h"
@interface
TelNoXmlParser : BaseXmlParser {
BOOL
loginSuccess
;
NSMutableArray
*
groups
,*
members
;
NSMutableDictionary
*
_group
;
NSDictionary
*
_user
;
}
@end
#import
"TelNoXmlParser.h"
@implementation
TelNoXmlParser
-(
id
)init{
if
(
self
=[
super
init
]) {
//
一个
groups
数组,代表了所有
List
groups
=[[
NSMutableArray
alloc
]
init
];
[
_root
setObject
:
groups
forKey
:
@"items"
];
loginSuccess
=
YES
;
}
return
self
;
}
-(
void
)dealloc{
[
_group
release
],
_group
=
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
{
//
我们关心
8
个元素标签,所以设置了
8
个标志位
//
login_status
if
(
strncmp
((
char
*)localname,
"login_status"
,
sizeof
(
"login_status"
)) ==
0
) {
loginSuccess
=
NO
;
return
;
}
if
(
loginSuccess
) {
// List
if
(
strncmp
((
char
*)localname,
"List"
,
sizeof
(
"List"
)) ==
0
) {
NSDictionary
* atts=[
self
getAtributes
:attributes
withSize
:nb_attributes];
//
获取
List
的所有属性
_group
=[[
NSMutableDictionary
alloc
]
init
];
members
=[[
NSMutableArray
alloc
]
init
];
[
_group
setObject
:
members
forKey
:
@"members"
];
[
_group
setObject
:[[
NSString
alloc
]
initWithString
:(
NSString
*)[atts
objectForKey
:
@"Name"
]]
forKey
:
@"groupname"
];
[
groups
addObject
:
_group
];
//
把
group
加入数组
return
;
}
// user
if
(
strncmp
((
char
*)localname,
"user"
,
sizeof
(
"user"
)) ==
0
) {
NSDictionary
* atts=[
self
getAtributes
:attributes
withSize
:nb_attributes];
//
获取
List
的所有属性
_user
=[[
NSDictionary
alloc
]
initWithDictionary
:atts];
[
members
addObject
:
_user
];
return
;
}
}
}
//
解析元素结束标记时触发
- (
void
)endElementLocalName:(
const
xmlChar
*)localname
prefix:(
const
xmlChar
*)prefix
URI:(
const
xmlChar
*)URI
{
if
(
strncmp
((
char
*)localname,
"root"
,
sizeof
(
"root"
)) ==
0
){
//root
结束时置
login_status
标志
if
(
loginSuccess
) {
[
_root
setObject
:
@"true"
forKey
:
@"login_status"
];
}
else
{
[
_root
setObject
:
@"false"
forKey
:
@"login_status"
];
}
}
if
(
loginSuccess
) {
//
我们还关心
<List>
的结束标记
if
(
strncmp
((
char
*)localname,
"List"
,
sizeof
(
"List"
)) ==
0
) {
[
_group
release
],
_group
=
nil
;
//
回收
_group
对象,以便重复利用
}
else
if
(
strncmp
((
char
*)localname,
"user"
,
sizeof
(
"user"
)) ==
0
){
[
_user
release
],
_user
=
nil
;
//
回收
_user
对象,以便重复利用
}
}
}
//
解析元素体时触发
- (
void
)charactersFound:(
const
xmlChar
*)ch
len:(
int
)len
{
//
没有元素体需要关心
}
@end
接下来我们看如何在 ViewController 中使用。
四、在 UI 中测试
在 ViewController 中放入一个按钮和一个WebView,当点击按钮时,请求http服务器,获取通讯录XML 数据,并解析为 Dictionary 对象。把解析结果显示在 WebView 中。
这是按钮的 touch up
inside 事件代码:
-(
IBAction
)go{
if
(
_queue
==
nil
){
_queue
= [[
NSOperationQueue
alloc
]
init
];
}
[
button
setEnabled
:
NO
];
[
progress
setProgress
:
0
];
[
webView
loadHTMLString
:
@""
baseURL
:[
NSURL
URLWithString
:
URL
]];
//
构造
xmlparser
TelNoXmlParser
* parser=[[
TelNoXmlParser
alloc
]
init
];
//
把
self
注册为
delegate
,这样
self
必需实现
syncRequestParseStatusNofity:
方法
,
以接收
statusChanged
方法
SyncRequestParseOperation
*
operation=[[
SyncRequestParseOperation
alloc ]
initWithURLString
:
URL
xmlParser
:parser
delegate
:
self
];
//
把
progress
设置为
progressDelegate
,这样会显示进度
[operation
setProgressDelegate
:
progress
];
[parser
release
];
// opertaion
已
retain
,可以
release
[
_queue
addOperation
:operation];
//
开始处理
[operation
release
];
//
队列已
retain
,可以
release
;
}
这是异步消息到达时的处理代码,当数据接收完时,我们把解析结果在WebView 中显示:
//
实现
statusChanged
通知方法
-(
void
)syncRequestParseStatusNofity:(
id
)sender{
SyncRequestParseOperation
*
operation=(
SyncRequestParseOperation
*)sender;
int
status=[operation
status
];
NSLog
(
@"status:%d"
,status);
if
(status==
kRequestStatusFinished
){
//
如数据接收完成
[
button
setEnabled
:
YES
];
NSDictionary
*
d=[operation
data
];
[
webView
loadHTMLString
:[d
description
]
baseURL
:[
NSURL
URLWithString
:
URL
]];
}
}
这是程序运行时 WebView 显示效果:
注意,当xml 文档比较大时,WebView
的内容是从上到下逐渐刷新的。
这是控制台输出,可以看到服务器响应的数据是被分成很多次下载的:
相关文章推荐
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析 .
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest 和 libxml 结合,实现边请求边解析
- ASIHTTPRequest和libxml结合,实现边请求边解析
- ASIHTTPRequest实现https双向认证请求
- ASIHttpRequest和json-framework实现json解析(iOS客户端)
- ASIHTTPRequest实现https双向认证请求
- 利用ASIHTTPRequest请求java服务和JSONKit解析返回的json串
- iPhone开发笔记(9)ASIHttpRequest和json-framework实现json解析(iOS客户端)
- iPhone开发笔记(9)ASIHttpRequest和json-framework实现json解析(iOS客户端)
- ASIHttpRequest和json-framework实现json解析(iOS客户端)
- iPhone开发笔记(9)ASIHttpRequest和json-framework实现json解析(iOS客户端)
- ASIHTTPRequest实现https双向认证请求
- ASIHTTPRequest实现https双向认证请求
- ASIHTTPRequest实现https双向认证请求