iOS 网络编程 iOS CoreData 编程 iOS 多线程 iOS 网络编程 本章内容概述 检测网络状态 使用NSURLConnection从网络获取数据 使用NSMutableURLRequest向服务器发送数据 JSON数据解析 XML数据解析 使用开源框架ASIHttpRequest实现网络编程 24.1 检测网络状态 客户端程序完全依赖网络,如果没有网络基本上程序就无法运行。因为数据都在服务器上,客户端只是数据的展现,这样就需要在程序启动时检测网络状态,甚至在程序运行时实时检测网络状态。 苹果官方帮助文档中提供了一个例程,该程序详细演示了如何检测网络状态。该项目的名称是Reachability,在本书的源码可以找到。在项目中的核心类是Reachability.h和Reachability.m,在我们自己的项目中使用时,直接添加这两个文件,并添加SystemConfiguration.framework框架即可。 Reachability类的内容如下: #import <Foundation/Foundation.h> #import <SystemConfiguration/SystemConfiguration.h> typedef enum { NotReachable = 0, ReachableViaWiFi, ReachableViaWWAN } NetworkStatus; #define kReachabilityChangedNotification @"kNetworkReachabilityChangedNotification" @interface Reachability: NSObject { BOOL localWiFiRef; SCNetworkReachabilityRef reachabilityRef; } //reachabilityWithHostName- Use to check the reachability of a particular host name. + (Reachability*) reachabilityWithHostName: (NSString*) hostName; //reachabilityWithAddress- Use to check the reachability of a particular IP address. + (Reachability*) reachabilityWithAddress: (const struct sockaddr\_in*) hostAddress; //reachabilityForInternetConnection- checks whether the default route is available. // Should be used by applications that do not connect to a particular host + (Reachability*) reachabilityForInternetConnection; //reachabilityForLocalWiFi- checks whether a local wifi connection is available. + (Reachability*) reachabilityForLocalWiFi; //Start listening for reachability notifications on the current run loop - (BOOL) startNotifier; - (void) stopNotifier; - (NetworkStatus) currentReachabilityStatus; //WWAN may be available, but not active until a connection has been established. //WiFi may require a connection for VPN on Demand. - (BOOL) connectionRequired; @end 使用该类的步骤是: 使用+ (Reachability) reachabilityWithHostName: (NSString) hostName;方法检测某个网络URL是否可以连接。例如,http://www.baidu.com。或者使用+ (Reachability) reachabilityForLocalWiFi;方法检测wifi的连接状况,或者使用+ (Reachability) reachabilityForInternetConnection;方法检测内网状态。 根据这些方法的返回值,调用- (NetworkStatus) currentReachabilityStatus;方法,判断网络状态,网络状态值有三个:NotReachable = 0,不能连接,ReachableViaWiFi,可以连接wifi,ReachableViaWWAN,可以通过移动网络连接,例如,GSM、3G等。 要实时检测网络状态需要向通知中心添加一个通知,然后调用startNotifier启动通知。 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reachabilityChanged:) name: kReachabilityChangedNotification object: nil]; [hostReach startNotifier]; 24.2 使用NSURLConnection从网络获取数据 可以使用NSURLConnection类从网络获取数据,通过该类可以发出同步请求和可以发出异步请求。发出的请求被封装在NSURLRequest对象中,而构建该对象还需要NSURL对象。下面通过一个案例来演示如何发出一个异步请求从网络上获得数据。该项目在xib界面上添加一个按钮和一个UIWebView组件,点击按钮发出请求,将请求数据显示在UIWebView中,这里我们获得苹果官方网站的首页面。地址是 http://www.apple.com 现步骤如下: 创建一个项目,在xib中添加一个按钮和一个UIWebView组件,并添加按钮的点击事件方法和UIWebView属性。 #import <UIKit/UIKit.h> @interface AmakerViewController : UIViewController // UIWebView属性 @property (strong, nonatomic) IBOutlet UIWebView *myWebView; // 按钮点击方法 - (IBAction)get:(id)sender; @end 实现按钮点击事件方法,定义要访问的网站地址,根据该地址创建NSURL对象,根据该对象再创建NSURLRequest对象,创建一个NSOperationQueue对象。使用NSURLConnection的静态方法+ (void)sendAsynchronousRequest:(NSURLRequest )request queue:(NSOperationQueue )queue completionHandler:(void (^)(NSURLResponse, NSData, NSError*))handler方法发出异步请求,在Block回调方法中获得返回的数据,并显示在UIWebView中。 - (IBAction)get:(id)sender { // 访问网络地址 NSString *str = @"http://www.apple.com/"; // 实例化NSURL对象 NSURL *url = [NSURL URLWithString:str]; // 实例化NSURLRequest对象 NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 实例化操作队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 发出异步请求 [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData * data, NSError *error) { // 获得网络抓取的数据 if ([data length] >0 && error == nil){ // 将NSData转换为NSString NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // 使用UIWebView显示网页 [self.myWebView loadHTMLString:html baseURL:nil]; NSLog(@"HTML = %@", html); } }]; } 程序运行结果如下所示。 另外,也可以使用NSURLConnection的同步方法+ (NSData )sendSynchronousRequest:(NSURLRequest )request returningResponse:(NSURLResponse )response error:(NSError )error向服务器发出同步请求。使用同步请求的时候不多,如果真的使用同步请求也是在单独的一个线程中使用,这样可以避免程序的阻塞。下面代码是一个在另外线程中发出同步请求的代码片段。 // 使用GCD dispatch\_queue\_t queue= dispatch\_get\_global\_queue(DISPATCH\_QUEUE\_PRIORITY\_DEFAULT, 0); dispatch\_async(queue, ^{ NSString *str = @"http://www.apple.com"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLResponse *response; NSError *error; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if ([data length]>0&&error==nil) { NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",str); } }); 24.3 使用NSMutableURLRequest向服务器发送数据 NSMutableURLRequest根据名称可以明白,该请求是一个可变请求,可以附加更多请求信息。例如,向服务器发送数据,例如,在登陆中需要向服务器发送用户名称和密码。下面通过一个案例来演示NSMutableURLRequest的用法,这里是获得新浪的天气预报信息,使用NSMutableURLRequest的setURL方法设置url、使用setTimeoutInterval方法设置请求超时时间、使用setHTTPMethod设置请求方法、使用setHTTPBody设置请求参数。实现步骤如下: 创建一个项目,在xib文件中添加一个按钮和一个UIWebView组件,并在头文件中添加按钮单击实现方法和UIWebView属性。 @interface AmakerViewController : UIViewController // 请求丰富 - (IBAction)get:(id)sender; // UIWebView 属性 @property (strong, nonatomic) IBOutlet UIWebView *myWebView; @end 实现按钮点击事件方法,创建另外一个线程,在Block中异步获得网络数据,创建NSMutableURLRequest对象,并设置请求方法、body、url等属性。并将网络返回结果在UIWebView中展示。 - (IBAction)get:(id)sender { // 创建另外一个线程 dispatch\_queue\_t queue = dispatch\_get\_global\_queue(DISPATCH\_QUEUE\_PRIORITY\_DEFAULT, 0); // 执行异步任务 dispatch\_async(queue, ^(void){ // http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0 // 在Bock中从网络获取数据 NSString *str = @"http://php.weather.sina.com.cn/xml.php"; // 创建NSURL对象 // ?w=2151330&u=c NSURL *url = [NSURL URLWithString:str]; // 可变URL请求 NSMutableURLRequest *mRequest = [[NSMutableURLRequest alloc]init]; //为请求设置URL [mRequest setURL:url]; // 设置请求超时时间 [mRequest setTimeoutInterval:10]; // 设置请求方法为post [mRequest setHTTPMethod:@"POST"]; // body 内容 NSString *body = @"city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0"; // 设置body [mRequest setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]]; // NSLog(@"test=%@",[mRequest]); // 获得响应 NSURLResponse *response; NSError *error; // 获得返回数据 NSData *data = [NSURLConnection sendSynchronousRequest: mRequest returningResponse:&response error:&error]; // 将NSData转换为字符串 NSString *content = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; // 使用UIWebView加载数据 [self.myWebView loadHTMLString:content baseURL:nil]; // 输出结果 NSLog(@"content=%@",content); }); } 程序运行结果如下图所示。 24.4 JSON数据解析 在网络编程中,从服务器获取的数据格式大致分为两种:JSON和XML。其中JSON作为一种轻量级的数据交换格式,正在逐步取代xml,成为网络数据的通用格式。 从IOS5开始,APPLE提供了对json的原生支持(NSJSONSerialization),但是为了兼容以前的ios版本,可以使用第三方库来解析Json。 第三方的库有如下几种: TouchJson包下载: http://toxicsoftware.com/cocoajson/ SBJson 包下载: http://superloopy.io/json-framework/ JSONKit包下载:https://github.com/johnezang/JSONKit 本节我们通过使用SBJson库和Apple自带的NSJSONSerialization来演示JSON的解析过程。首先使用Apple自带的NSJSONSerialization来解析JSON。这里我们调用的是一个天气预报api,http://m.weather.com.cn/data/101010100.html,该api返回的结果是: {"weatherinfo":{"city":"北京","city\_en":"beijing","date\_y":"2013年4月19日","date":"","week":"星期五","fchh":"11","cityid":"101010100","temp1":"6℃~3℃","temp2":"16℃~5℃","temp3":"18℃~7℃","temp4":"20℃~10℃","temp5":"23℃~11℃","temp6":"23℃~9℃","tempF1":"42.8℉~37.4℉","tempF2":"60.8℉~41℉","tempF3":"64.4℉~44.6℉","tempF4":"68℉~50℉","tempF5":"73.4℉~51.8℉","tempF6":"73.4℉~48.2℉","weather1":"小雨转阴","weather2":"多云转晴","weather3":"晴转多云","weather4":"多云","weather5":"多云","weather6":"多云转晴","img1":"7","img2":"2","img3":"1","img4":"0","img5":"0","img6":"1","img7":"1","img8":"99","img9":"1","img10":"99","img11":"1","img12":"0","img\_single":"7","img\_title1":"小雨","img\_title2":"阴","img\_title3":"多云","img\_title4":"晴","img\_title5":"晴","img\_title6":"多云","img\_title7":"多云","img\_title8":"多云","img\_title9":"多云","img\_title10":"多云","img\_title11":"多云","img\_title12":"晴","img\_title\_single":"小雨","wind1":"微风","wind2":"微风","wind3":"微风","wind4":"微风","wind5":"微风","wind6":"微风","fx1":"微风","fx2":"微风","fl1":"小于3级","fl2":"小于3级","fl3":"小于3级","fl4":"小于3级","fl5":"小于3级","fl6":"小于3级","index":"冷","index\_d":"天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。","index48":"冷","index48\_d":"天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。","index\_uv":"最弱","index48\_uv":"弱","index\_xc":"不宜","index\_tr":"适宜","index\_co":"较舒适","st1":"2","st2":"1","st3":"17","st4":"6","st5":"18","st6":"7","index\_cl":"较不宜","index\_ls":"不宜","index\_ag":"易发"}} 通过返回结果我们看到,一般JSON返回的是一个数组,数组中嵌套一个字典,我们可以遍历数组,获得NSDictionary,再通过key获得value。下面我们看一下创建该案例的步骤: 创建一个项目,在界面上添加一个按钮,并添加按钮的单击事件方法。 #import <UIKit/UIKit.h> #define weatherInfo @"weatherinfo" @interface AmakerViewController : UIViewController // 单击事件方法 - (IBAction)parse:(id)sender; // 线程方法 -(void)action1; @end 在按钮点击事件中另外启动一个线程,在该线程中调用action1方法,在该方法中获得网络数据,将网络数据NSData数据格式通过NSJSONSerialization的JSONObjectWithData方法转换为字典。并遍历字典。 // 使用系统自带库 - (IBAction)parse:(id)sender { // 创建另外一个线程 NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(action1) object:nil]; // 启动线程 [t start]; } -(void)action1{ // 天气预报地址 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://m.weather.com.cn/data/101010100.html"]]; // 获得数据 NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; // 将NSData转换为NSString NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"str=%@",str); // 将NSData转换为NSDictionary NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; // 获得可以为weatherinfo的字典 NSDictionary *dic2 = [dic objectForKey:weatherInfo]; // 遍历字典 for (NSString *key in dic2) { NSLog(@"%@:%@",key,[dic2 objectForKey:key]); } } 程序运行结果如下图所示。 另外,可以使用第三方的库来实现JSON的解析,这里使用SBJson。实现步骤如下: 下载SBJson,并添加到项目中。下载地址为:https://github.com/stig/json-framework/tags 。 在界面上添加一个按钮,并添加单击事件方法,在另外一个线程中,从网络获得数据,实例化SBJsonParser,调用objectWithString方法解析数据。 // 启动另外一个线程 - (IBAction)parse2:(id)sender { NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(action2) object:nil]; [t start]; } -(void)action2{ // 天气预报地址 NSURL *url = [NSURL URLWithString:@"http://m.weather.com.cn/data/101010100.html"]; // 通过url直接获得字符串内容 NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; // 实例化json解析类SBJsonParser SBJsonParser *parser2 = [[SBJsonParser alloc]init]; // 调用解析方法,转换为字典 NSDictionary *dic = [parser2 objectWithString:jsonString]; // 解析 NSDictionary *dic2 = [dic objectForKey:weatherInfo]; // 遍历 for (NSString *key in dic2) { NSLog(@"%@:%@",key,[dic2 objectForKey:key]); } } 24.5 XML数据解析 在上一节我讲述了JSON数据格式的解析,本节将讲述另外一种常用的数据格式XML文件。可以通过苹果自带的API NSXMLParser和其代理类NSXMLParserDelegate来解析XML。 其中NSXMLParserDelegate代理类中有一组方法来检测文档的读取状态,例如,文档开始、文档结束、元素开始、元素结束、内容开始等。解析文件的基本思路是,在元素开始时创建一个空对象,找到内容时,读取内容并为对象的属性赋值,元素结束时将对象添加到集合当中。 下面通过一个案例来演示如何解析XML文件。步骤如下: 在项目中创建一个xml文件,内容如下: <?xml version="1.0" encoding="UTF-8"?> <customers> <customer> <id>1</id> <name>tom</name> <age>20</age> </customer> <customer> <id>2</id> <name>kite</name> <age>21</age> </customer> <customer> <id>3</id> <name>rose</name> <age>22</age> </customer> </customers> 创建一个Customer类。 @interface Customer : NSObject // 客户id @property(nonatomic)int cid; // 客户姓名 @property(nonatomic,retain)NSString *name; // 客户年龄 @property(nonatomic)int age; @end 在界面上添加一个按钮,并在头文件中,添加单击事件方法、Customer属性、NSXMLParser属性、NSMutableArray属性并实现NSXMLParserDelegate代理协议。 #import <UIKit/UIKit.h> #import "Customer.h" @interface AmakerViewController : UIViewController<NSXMLParserDelegate> - (IBAction)parse:(id)sender; // 客户实例 @property(nonatomic,retain)Customer *customer; // xml解析器 @property(nonatomic,retain)NSXMLParser *parser; // 可变数组 @property(nonatomic,retain)NSMutableArray *array; // 当前元素 @property(nonatomic,strong)NSString *currentElement; @end 在viewDidLoad方法中,初始化数组,用来容纳客户对象,获得客户xml文件url路径,将客户xml转换为NSData,实例化XML解析器,并为其设置代理。 - (void)viewDidLoad { [super viewDidLoad]; // 初始化数组,用来容纳客户对象 self.array = [NSMutableArray arrayWithCapacity:10]; // 获得客户xml文件url路径 NSString *str = [[NSBundle mainBundle]pathForResource:@"customers" ofType:@"xml"]; // 将客户xml转换为NSData NSData *data = [[NSData alloc]initWithContentsOfFile:str]; // 实例化XML解析器 self.parser = [[NSXMLParser alloc]initWithData:data]; // 并设置代理 self.parser.delegate = self; } 实现协议的开始读文档和结束读文档方法。 // 开始文档 - (void)parserDidStartDocument:(NSXMLParser *)parser{ NSLog(@"parserDidStartDocument..."); } // 结束文档 - (void)parserDidEndDocument:(NSXMLParser *)parser{ NSLog(@"parserDidEndDocument..."); } 实现协议的开始读取元素方法,创建客户实例。 // 开始元素 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{ NSLog(@"didStartElement...%@",elementName); self.currentElement = elementName; if ([self.currentElement isEqualToString:@"customer"]) { self.customer = [[Customer alloc]init]; } } 在协议的找到内容的方法中,获得各个元素内容,并为对象属性赋值。 // 找到内容 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{ NSLog(@"foundCharacters...%@",string); if ([self.currentElement isEqualToString:@"id"]) { int cid = [string integerValue]; [customer setCid:cid]; }else if([self.currentElement isEqualToString:@"name"]){ [customer setName:string]; }else if([self.currentElement isEqualToString:@"age"]){ int age = [string integerValue]; [customer setAge:age]; } } 在协议的结束元素中,将客户对象添加到数组中。 // 结束元素 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{ NSLog(@"didEndElement..."); if ([elementName isEqualToString:@"customer"]) { [self.array addObject:customer]; } self.currentElement=nil; } 实现解析方法parse,删除数组中的所有元素,调用parse方法开始解析。 - (IBAction)parse:(id)sender { // 删除数组中的所有数据 [self.array removeAllObjects]; // 开始解析 [self.parser parse]; // 获得数组大小 NSInteger count = [self.array count]; NSLog(@"count=%d",count); } 程序运行结果如下。 24.6 使用开源框架ASIHttpRequest实现网络编程 ASIHttpRequest是iOS网络开发中最优秀的框架,虽然我们可以使用Apple自己的网络api但是功能很限,不够强大。相反ASIHttpRequest框架简单易用,而且功能强大,例如,可以使用同步请求、异步请求、使用队列、可以实现文件的上传和下载以及显示上传下载进度等。 ASIHttpRequest的官方站点是:http://allseeing-i.com/ASIHTTPRequest/ 。这里包括了下载地址、项目搭建和开发文档。ASIHttpRequest框架的下载地址是:http://github.com/pokeb/asi-http-request/tarball/master 。点击该连接可以直接下载该框架。 下面我们来看如何搭建开发框架,步骤如下: 创建一个项目,添加如下框架,CFNetwork, SystemConfiguration, MobileCoreServices, CoreGraphics 和libz。如下图所示。 从下载的框架中添加如下文件到自己的项目中。 24.6 搭建ASIHttpRequest框架,添加需要的文件 当前框架并不支持arc,如果我们的项目使用了arc,则需要设置如下编译选项,设置这些类不使用arc。 24.7 搭建ASIHttpRequest框架,设置编译选项 重新编译整个项目,如果没有报错,则项目配置成功。 该框架的更多详细用法可以通过如下连接获得帮助:http://allseeing-i.com/ASIHTTPRequest/How-to-use iOS CoreData 编程 iOS 多线程