大部分應(yīng)用程序都或多或少會(huì)牽扯到網(wǎng)絡(luò)開(kāi)發(fā),例如說(shuō)新浪微博、微信等,這些應(yīng)用本身可能采用iOS開(kāi)發(fā),但是所有的數(shù)據(jù)支撐都是基于后臺(tái)網(wǎng)絡(luò)服務(wù)器的。如今,網(wǎng)絡(luò)編程越來(lái)越普遍,孤立的應(yīng)用通常是沒(méi)有生命力的。今天就會(huì)給大家介紹這部分內(nèi)容:
做過(guò)Web開(kāi)發(fā)的朋友應(yīng)該很清楚,Http是無(wú)連接的請(qǐng)求。每個(gè)請(qǐng)求request服務(wù)器都有一個(gè)對(duì)應(yīng)的響應(yīng)response,無(wú)論是asp.net、jsp、php都是基于這種機(jī)制開(kāi)發(fā)的。
在Web開(kāi)發(fā)中主要的請(qǐng)求方法有如下幾種:
在開(kāi)發(fā)中往往數(shù)據(jù)存儲(chǔ)在服務(wù)器端,而客戶端(iOS應(yīng)用)往往通過(guò)向服務(wù)器端發(fā)送請(qǐng)求從服務(wù)器端獲得數(shù)據(jù)。要模擬這個(gè)過(guò)程首先當(dāng)然是建立服務(wù)器端應(yīng)用,應(yīng)用的形式?jīng)]有限制,你可以采用任何Web技術(shù)進(jìn)行開(kāi)發(fā)。假設(shè)現(xiàn)在有一個(gè)文件服務(wù)器,用戶輸入文件名稱就可以下載文件。服務(wù)器端程序很簡(jiǎn)單,只要訪問(wèn)http://192.168.1.208/FileDownload.aspx?file=filename,就可以下載指定filename的文件,由于服務(wù)器端開(kāi)發(fā)的內(nèi)容不是今天的重點(diǎn)在此不再贅述??蛻舳私缑嬖O(shè)計(jì)如下圖:
程序的實(shí)現(xiàn)需要借助幾個(gè)對(duì)象:
NSURLRequest:建立了一個(gè)請(qǐng)求,可以指定緩存策略、超時(shí)時(shí)間。和NSURLRequest對(duì)應(yīng)的還有一個(gè)NSMutableURLRequest,如果請(qǐng)求定義為NSMutableURLRequest則可以指定請(qǐng)求方法(GET或POST)等信息。
NSURLConnection:用于發(fā)送請(qǐng)求,可以指定請(qǐng)求和代理。當(dāng)前調(diào)用NSURLConnection的start方法后開(kāi)始發(fā)送異步請(qǐng)求。
程序代碼如下:
//// KCMainViewController.m// UrlConnection//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"@interface KCMainViewController ()<NSURLConnectionDataDelegate>{ NSMutableData *_data;//響應(yīng)數(shù)據(jù) UITextField *_textField; UIButton *_button; UiprogressView *_PRogressView; UILabel *_label; long long _totalLength;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; }#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ //地址欄 _textField=[[UITextField alloc]initWithFrame:CGRectMake(10, 50, 300, 25)]; _textField.borderStyle=UITextBorderStyleRoundedRect; _textField.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; _textField.text=@"簡(jiǎn)約至上:交互式設(shè)計(jì)四策略.epub"; [self.view addSubview:_textField]; //進(jìn)度條 _progressView=[[UIProgressView alloc]initWithFrame:CGRectMake(10, 100, 300, 25)]; [self.view addSubview:_progressView]; //狀態(tài)顯示 _label=[[UILabel alloc]initWithFrame:CGRectMake(10, 130, 300, 25)]; _label.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; [self.view addSubview:_label]; //下載按鈕 _button=[[UIButton alloc]initWithFrame:CGRectMake(10, 500, 300, 25)]; [_button setTitle:@"下載" forState:UIControlStateNormal]; [_button setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_button addTarget:self action:@selector(sendRequest) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_button]; }#pragma mark 更新進(jìn)度-(void)updateProgress{// [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (_data.length==_totalLength) { _label.text=@"下載完成"; }else{ _label.text=@"正在下載..."; [_progressView setProgress:(float)_data.length/_totalLength]; }// }];}#pragma mark 發(fā)送數(shù)據(jù)請(qǐng)求-(void)sendRequest{ NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.208/FileDownload.aspx?file=%@",_textField.text]; //注意對(duì)于url中的中文是無(wú)法解析的,需要進(jìn)行url編碼(指定編碼類型為utf-8) //另外注意url解碼使用stringByRemovingPercentEncoding方法 urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //創(chuàng)建url鏈接 NSURL *url=[NSURL URLWithString:urlStr]; /*創(chuàng)建請(qǐng)求 cachePolicy:緩存策略 a.NSURLRequestUseProtocolCachePolicy 協(xié)議緩存,根據(jù)response中的Cache-Control字段判斷緩存是否有效,如果緩存有效則使用緩存數(shù)據(jù)否則重新從服務(wù)器請(qǐng)求 b.NSURLRequestReloadIgnoringLocalCacheData 不使用緩存,直接請(qǐng)求新數(shù)據(jù) c.NSURLRequestReloadIgnoringCacheData 等同于 SURLRequestReloadIgnoringLocalCacheData d.NSURLRequestReturnCacheDataElseLoad 直接使用緩存數(shù)據(jù)不管是否有效,沒(méi)有緩存則重新請(qǐng)求 eNSURLRequestReturnCacheDataDontLoad 直接使用緩存數(shù)據(jù)不管是否有效,沒(méi)有緩存數(shù)據(jù)則失敗 timeoutInterval:超時(shí)時(shí)間設(shè)置(默認(rèn)60s) */ NSURLRequest *request=[[NSURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0f]; //創(chuàng)建連接 NSURLConnection *connection=[[NSURLConnection alloc]initWithRequest:request delegate:self]; //啟動(dòng)連接 [connection start]; }#pragma mark - 連接代理方法#pragma mark 開(kāi)始響應(yīng)-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ NSLog(@"receive response."); _data=[[NSMutableData alloc]init]; _progressView.progress=0; //通過(guò)響應(yīng)頭中的Content-Length取得整個(gè)響應(yīng)的總長(zhǎng)度 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSDictionary *httpResponseHeaderFields = [httpResponse allHeaderFields]; _totalLength = [[httpResponseHeaderFields objectForKey:@"Content-Length"] longLongValue];}#pragma mark 接收響應(yīng)數(shù)據(jù)(根據(jù)響應(yīng)內(nèi)容的大小此方法會(huì)被重復(fù)調(diào)用)-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ NSLog(@"receive data."); //連續(xù)接收數(shù)據(jù) [_data appendData:data]; //更新進(jìn)度 [self updateProgress];}#pragma mark 數(shù)據(jù)接收完成-(void)connectionDidFinishLoading:(NSURLConnection *)connection{ NSLog(@"loading finish."); //數(shù)據(jù)接收完保存文件(注意蘋(píng)果官方要求:下載數(shù)據(jù)只能保存在緩存目錄) NSString *savePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; savePath=[savePath stringByAppendingPathComponent:_textField.text]; [_data writeToFile:savePath atomically:YES]; NSLog(@"path:%@",savePath);}#pragma mark 請(qǐng)求失敗-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ //如果連接超時(shí)或者連接地址錯(cuò)誤可能就會(huì)報(bào)錯(cuò) NSLog(@"connection error,error detail is:%@",error.localizedDescription);}@end
運(yùn)行效果:
需要注意:
當(dāng)然,對(duì)于上面文件下載這種大數(shù)據(jù)響應(yīng)的情況使用代理方法處理響應(yīng)具有一定的優(yōu)勢(shì)(可以獲得傳輸進(jìn)度)。但是如果現(xiàn)響應(yīng)數(shù)據(jù)不是文件而是一段字符串(注意web請(qǐng)求的數(shù)據(jù)可以是字符串或者二進(jìn)制,上面文件下載示例中響應(yīng)數(shù)據(jù)是二進(jìn)制),那么采用代理方法處理服務(wù)器響應(yīng)就未免有些太麻煩了。其實(shí)蘋(píng)果官方已經(jīng)提供了下面兩種方法處理一般的請(qǐng)求:
+ (void)sendAsynchronousRequest:request: queue:queue:completionHandler:發(fā)送一個(gè)異步請(qǐng)求
+ (NSData *)sendSynchronousRequest: returningResponse: error:發(fā)送一個(gè)同步請(qǐng)求
假設(shè)在開(kāi)發(fā)一個(gè)類似于微博的應(yīng)用,服務(wù)器端返回的是JSON字符串,我們可以使用上面的方法簡(jiǎn)化整個(gè)請(qǐng)求響應(yīng)的過(guò)程。這里會(huì)使用在“iOS開(kāi)發(fā)系列--UITableView全面解析”文章中自定義的UITableViewCell來(lái)顯示微博數(shù)據(jù),不清楚的朋友可以看一下前面的內(nèi)容。
請(qǐng)求過(guò)程中需要傳遞一個(gè)用戶名和密碼,如果全部正確則服務(wù)器端返回此用戶可以看到的最新微博數(shù)據(jù),響應(yīng)的json格式大致如下:
整個(gè)Json最外層是statuses節(jié)點(diǎn),它是一個(gè)數(shù)組類型,數(shù)組中每個(gè)元素都是一條微博數(shù)據(jù),每條微博數(shù)據(jù)中除了包含微博信息還包含了發(fā)表用戶的信息。
首先需要先定義用戶模型KCUser
//// KCUser.h// UrlConnection//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@interface KCUser : NSObject#pragma mark 編號(hào)@property (nonatomic,strong) NSNumber *Id;#pragma mark 用戶名@property (nonatomic,copy) NSString *name;#pragma mark 城市@property (nonatomic,copy) NSString *city;@end
微博模型KCStatus
KCStatus.h
//// KCStatus.h// UITableView//// Created by Kenshin Cui on 14-3-1.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "KCUser.h"@interface KCStatus : NSObject#pragma mark - 屬性@property (nonatomic,strong) NSNumber *Id;//微博id@property (nonatomic,copy) NSString *profileImageUrl;//頭像@property (nonatomic,strong) KCUser *user;//發(fā)送用戶@property (nonatomic,copy) NSString *mbtype;//會(huì)員類型@property (nonatomic,copy) NSString *createdAt;//創(chuàng)建時(shí)間@property (nonatomic,copy) NSString *source;//設(shè)備來(lái)源@property (nonatomic,copy) NSString *text;//微博內(nèi)容@end
KCStatus.m
//// KCStatus.m// UITableView//// Created by Kenshin Cui on 14-3-1.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCStatus.h"@implementation KCStatus-(NSString *)source{ return [NSString stringWithFormat:@"來(lái)自 %@",_source];}@end
其次需要自定義微博顯示的單元格KCStatusTableViewCell,這里需要注意,由于服務(wù)器返回?cái)?shù)據(jù)中頭像和會(huì)員類型圖片已經(jīng)不在本地,需要從服務(wù)器端根據(jù)返回JSON的中圖片的路徑去加載。
KCStatusTableViewCell.h
//// KCStatusTableViewCell.h// UITableView//// Created by Kenshin Cui on 14-3-1.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <UIKit/UIKit.h>@class KCStatus;@interface KCStatusTableViewCell : UITableViewCell#pragma mark 微博對(duì)象@property (nonatomic,strong) KCStatus *status;#pragma mark 單元格高度@property (assign,nonatomic) CGFloat height;@end
KCStatusTableViewCell.m
//// KCStatusTableViewCell.m// UITableView//// Created by Kenshin Cui on 14-3-1.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCStatusTableViewCell.h"#import "KCStatus.h"#define KCColor(r,g,b) [UIColor colorWithHue:r/255.0 saturation:g/255.0 brightness:b/255.0 alpha:1] //顏色宏定義#define kStatusTableViewCellControlSpacing 10 //控件間距#define kStatusTableViewCellBackgroundColor KCColor(251,251,251)#define kStatusGrayColor KCColor(50,50,50)#define kStatusLightGrayColor KCColor(120,120,120)#define kStatusTableViewCellAvatarWidth 40 //頭像寬度#define kStatusTableViewCellAvatarHeight kStatusTableViewCellAvatarWidth#define kStatusTableViewCellUserNameFontSize 14#define kStatusTableViewCellMbTypeWidth 13 //會(huì)員圖標(biāo)寬度#define kStatusTableViewCellMbTypeHeight kStatusTableViewCellMbTypeWidth#define kStatusTableViewCellCreateAtFontSize 12#define kStatusTableViewCellSourceFontSize 12#define kStatusTableViewCellTextFontSize 14@interface KCStatusTableViewCell(){ UIImageView *_avatar;//頭像 UIImageView *_mbType;//會(huì)員類型 UILabel *_userName; UILabel *_createAt; UILabel *_source; UILabel *_text;}@end@implementation KCStatusTableViewCell- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { [self initSubView]; } return self;}#pragma mark 初始化視圖-(void)initSubView{ //頭像控件 _avatar=[[UIImageView alloc]init]; [self addSubview:_avatar]; //用戶名 _userName=[[UILabel alloc]init]; _userName.textColor=kStatusGrayColor; _userName.font=[UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]; [self addSubview:_userName]; //會(huì)員類型 _mbType=[[UIImageView alloc]init]; [self addSubview:_mbType]; //日期 _createAt=[[UILabel alloc]init]; _createAt.textColor=kStatusLightGrayColor; _createAt.font=[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]; [self addSubview:_createAt]; //設(shè)備 _source=[[UILabel alloc]init]; _source.textColor=kStatusLightGrayColor; _source.font=[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]; [self addSubview:_source]; //內(nèi)容 _text=[[UILabel alloc]init]; _text.textColor=kStatusGrayColor; _text.font=[UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]; _text.numberOfLines=0; _text.lineBreakMode=NSLineBreakByWordWrapping; [self addSubview:_text];}#pragma mark 設(shè)置微博-(void)setStatus:(KCStatus *)status{ //設(shè)置頭像大小和位置 CGFloat avatarX=10,avatarY=10; CGRect avatarRect=CGRectMake(avatarX, avatarY, kStatusTableViewCellAvatarWidth, kStatusTableViewCellAvatarHeight);// _avatar.image=[UIImage imageNamed:status.profileImageUrl]; NSURL *avatarUrl=[NSURL URLWithString:status.profileImageUrl]; NSData *avatarData=[NSData dataWithContentsOfURL:avatarUrl]; UIImage *avatarImage= [UIImage imageWithData:avatarData]; _avatar.image=avatarImage; _avatar.frame=avatarRect; //設(shè)置會(huì)員圖標(biāo)大小和位置 CGFloat userNameX= CGRectGetMaxX(_avatar.frame)+kStatusTableViewCellControlSpacing ; CGFloat userNameY=avatarY; //根據(jù)文本內(nèi)容取得文本占用空間大小 CGSize userNameSize=[status.user.name sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]}]; CGRect userNameRect=CGRectMake(userNameX, userNameY, userNameSize.width,userNameSize.height); _userName.text=status.user.name; _userName.frame=userNameRect; //設(shè)置會(huì)員圖標(biāo)大小和位置 CGFloat mbTypeX=CGRectGetMaxX(_userName.frame)+kStatusTableViewCellControlSpacing; CGFloat mbTypeY=avatarY; CGRect mbTypeRect=CGRectMake(mbTypeX, mbTypeY, kStatusTableViewCellMbTypeWidth, kStatusTableViewCellMbTypeHeight);// _mbType.image=[UIImage imageNamed:status.mbtype]; NSURL *mbTypeUrl=[NSURL URLWithString:status.mbtype]; NSData *mbTypeData=[NSData dataWithContentsOfURL:mbTypeUrl]; UIImage *mbTypeImage= [UIImage imageWithData:mbTypeData]; _mbType.image=mbTypeImage; _mbType.frame=mbTypeRect; //設(shè)置發(fā)布日期大小和位置 CGSize createAtSize=[status.createdAt sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]}]; CGFloat createAtX=userNameX; CGFloat createAtY=CGRectGetMaxY(_avatar.frame)-createAtSize.height; CGRect createAtRect=CGRectMake(createAtX, createAtY, createAtSize.width, createAtSize.height); _createAt.text=status.createdAt; _createAt.frame=createAtRect; //設(shè)置設(shè)備信息大小和位置 CGSize sourceSize=[status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]}]; CGFloat sourceX=CGRectGetMaxX(_createAt.frame)+kStatusTableViewCellControlSpacing; CGFloat sourceY=createAtY; CGRect sourceRect=CGRectMake(sourceX, sourceY, sourceSize.width,sourceSize.height); _source.text=status.source; _source.frame=sourceRect; //設(shè)置微博內(nèi)容大小和位置 CGFloat textX=avatarX; CGFloat textY=CGRectGetMaxY(_avatar.frame)+kStatusTableViewCellControlSpacing; CGFloat textWidth=self.frame.size.width-kStatusTableViewCellControlSpacing*2; CGSize textSize=[status.text boundingRectWithSize:CGSizeMake(textWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]} context:nil].size; CGRect textRect=CGRectMake(textX, textY, textSize.width, textSize.height); _text.text=status.text; _text.frame=textRect; _height=CGRectGetMaxY(_text.frame)+kStatusTableViewCellControlSpacing;}#pragma mark 重寫(xiě)選擇事件,取消選中-(void)setSelected:(BOOL)selected animated:(BOOL)animated{ }@end
最后就是KCMainViewController,在這里需要使用NSURLConnection的靜態(tài)方法發(fā)送請(qǐng)求、獲得請(qǐng)求數(shù)據(jù),然后對(duì)請(qǐng)求數(shù)據(jù)進(jìn)行JSON序列化,將JSON字符串序列化成微博對(duì)象通過(guò)UITableView顯示到界面中。
//// KCMainViewController.m// UrlConnection//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"#import "KCStatusTableViewCell.h"#import "KCStatus.h"#import "KCUser.h"#define kURL @"http://192.168.1.208/ViewStatus.aspx"@interface KCMainViewController ()<UITableViewDataSource,UITableViewDelegate>{ UITableView *_tableView; NSMutableArray *_status; NSMutableArray *_statusCells;//存儲(chǔ)cell,用于計(jì)算高度 NSString *_userName; NSString *_password;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; _userName=@"KenshinCui"; _password=@"123"; [self layoutUI]; [self sendRequest]; }#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ _tableView =[[UITableView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame style:UITableViewStylePlain]; _tableView.dataSource=self; _tableView.delegate=self; [self.view addSubview:_tableView];}#pragma mark 加載數(shù)據(jù)-(void)loadData:(NSData *)data{ _status=[[NSMutableArray alloc]init]; _statusCells=[[NSMutableArray alloc]init]; /*json序列化 options:序列化選項(xiàng),枚舉類型,但是可以指定為枚舉以外的類型,例如指定為0則可以返回NSDictionary或者NSArray a.NSJSONReadingMutableContainers:返回NSMutableDictionary或NSMutableArray b.NSJSONReadingMutableLeaves:返回NSMutableString字符串 c.NSJSONReadingAllowFragments:可以解析JSON字符串的外層既不是字典類型(NSMutableDictionary、NSDictionary)又不是數(shù)組類型(NSMutableArray、NSArray)的數(shù)據(jù),但是必須是有效的JSON字符串 error:錯(cuò)誤信息 */ NSError *error; //將對(duì)象序列化為字典 NSDictionary *dic= [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; NSArray *array= (NSArray *)dic[@"statuses"]; [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { KCStatus *status=[[KCStatus alloc]init]; //通過(guò)KVC給對(duì)象賦值 [status setValuesForKeysWithDictionary:obj]; KCUser *user=[[KCUser alloc]init]; [user setValuesForKeysWithDictionary:obj[@"user"]]; status.user=user; [_status addObject:status]; //存儲(chǔ)tableViewCell KCStatusTableViewCell *cell=[[KCStatusTableViewCell alloc]init]; [_statusCells addObject:cell]; }];}#pragma mark 發(fā)送數(shù)據(jù)請(qǐng)求-(void)sendRequest{ NSString *urlStr=[NSString stringWithFormat:@"%@",kURL]; //注意對(duì)于url中的中文是無(wú)法解析的,需要進(jìn)行url編碼(指定編碼類型位utf-8) //另外注意url解碼使用stringByRemovingPercentEncoding方法 urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //創(chuàng)建url鏈接 NSURL *url=[NSURL URLWithString:urlStr]; /*創(chuàng)建可變請(qǐng)求*/ NSMutableURLRequest *requestM=[[NSMutableURLRequest alloc]initWithURL:url cachePolicy:0 timeoutInterval:5.0f]; [requestM setHTTPMethod:@"POST"];//設(shè)置位post請(qǐng)求 //創(chuàng)建post參數(shù) NSString *bodyDataStr=[NSString stringWithFormat:@"userName=%@&password=%@",_userName,_password]; NSData *bodyData=[bodyDataStr dataUsingEncoding:NSUTF8StringEncoding]; [requestM setHTTPBody:bodyData]; //發(fā)送一個(gè)異步請(qǐng)求 [NSURLConnection sendAsynchronousRequest:requestM queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (!connectionError) {// NSString *jsonStr=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];// NSLog(@"jsonStr:%@",jsonStr); //加載數(shù)據(jù) [self loadData:data]; //刷新表格 [_tableView reloadData]; }else{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ }]; } }]; }#pragma mark - 數(shù)據(jù)源方法#pragma mark 返回分組數(shù)-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 1;}#pragma mark 返回每組行數(shù)-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return _status.count;}#pragma mark返回每行的單元格-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cellIdentifier=@"UITableViewCellIdentifierKey1"; KCStatusTableViewCell *cell; cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if(!cell){ cell=[[KCStatusTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } //在此設(shè)置微博,以便重新布局 KCStatus *status=_status[indexPath.row]; cell.status=status; return cell;}#pragma mark - 代理方法#pragma mark 重新設(shè)置單元格高度-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ KCStatusTableViewCell *cell= _statusCells[indexPath.row]; cell.status=_status[indexPath.row]; return cell.height;}@end
運(yùn)行效果:
可以看到使用NSURLConnection封裝的靜態(tài)方法可以直接獲得NSData,不需要使用代理一步步自己組裝數(shù)據(jù)。這里采用了POST方式發(fā)送請(qǐng)求,使用POST發(fā)送請(qǐng)求需要組裝數(shù)據(jù)體,不過(guò)數(shù)據(jù)長(zhǎng)度不像GET方式存在限制。從iOS5開(kāi)始蘋(píng)果官方提供了JSON序列化和反序列化相關(guān)方法(上面程序中僅僅用到了反序列化方法,序列化使用dataWithJSONObject:options:opt error:方法)方便的對(duì)數(shù)組和字典進(jìn)行序列化和反序列化。但是注意反序列化參數(shù)設(shè)置,程序中設(shè)置成了0,直接反序列化為不可變對(duì)象以提高性能。
注意:
1.現(xiàn)在多數(shù)情況下互聯(lián)網(wǎng)數(shù)據(jù)都是以JSON格式進(jìn)行傳輸,但是有時(shí)候也會(huì)面對(duì)xml存儲(chǔ)。在IOS中可以使用NSXMLParser進(jìn)行XML解析,由于實(shí)際使用并不多,在此不再贅述。
2.使用KVC給對(duì)象賦值時(shí)(通常是NSDictionary或NSMutalbeDictionary)注意對(duì)象的屬性最好不要定義為基本類型(如int),否則如果屬性值為null則會(huì)報(bào)錯(cuò),最后定義為ObjC對(duì)象類型(如使用NSNumber代替int等);
開(kāi)發(fā)Web類的應(yīng)用圖片緩存問(wèn)題不得不提及,因?yàn)閳D片的下載相當(dāng)耗時(shí)。對(duì)于前面的微博數(shù)據(jù),頭像和微博類型圖標(biāo)在數(shù)據(jù)庫(kù)中是以鏈接形式存放的,取得鏈接后還必須進(jìn)行對(duì)應(yīng)的圖片加載。大家都知道圖片往往要比文本內(nèi)容大得多,在UITableView中上下滾動(dòng)就會(huì)重新加載數(shù)據(jù),對(duì)于文本由于已經(jīng)加載到本地自然不存在問(wèn)題,但是對(duì)于圖片來(lái)說(shuō)如果每次都必須重新從服務(wù)器端加載就不太合適了。
解決圖片加載的辦法有很多,可以事先存儲(chǔ)到內(nèi)存中,也可以保存到臨時(shí)文件。在內(nèi)存中存儲(chǔ)雖然簡(jiǎn)單但是往往不可取,因?yàn)槌绦蛑匦聠?dòng)之后還面臨這重新請(qǐng)求的問(wèn)題,類似于新浪微博、QQ、微信等應(yīng)用一般會(huì)存儲(chǔ)在文件中,這樣應(yīng)用程序即使重啟也會(huì)從文件中讀取。但是使用文件緩存圖片可能就要自己做很多事情,例如緩存文件是否過(guò)期?緩存數(shù)據(jù)越來(lái)越大如何管理存儲(chǔ)空間?
這些問(wèn)題其實(shí)很多第三方框架已經(jīng)做的很好了,實(shí)際開(kāi)發(fā)中往往會(huì)采用一些第三方框架來(lái)處理圖片。例如這里可以選用SDWebImage框架。SDWebImage使用起來(lái)相當(dāng)簡(jiǎn)單,開(kāi)發(fā)者不必過(guò)多關(guān)心它的緩存和多線程加載問(wèn)題,一個(gè)方法就可以解決。這里直接修改KCStatusTableViewCell中相關(guān)代碼即可:
#pragma mark 設(shè)置微博-(void)setStatus:(KCStatus *)status{ //設(shè)置頭像大小和位置 CGFloat avatarX=10,avatarY=10; CGRect avatarRect=CGRectMake(avatarX, avatarY, kStatusTableViewCellAvatarWidth, kStatusTableViewCellAvatarHeight); NSURL *avatarUrl=[NSURL URLWithString:status.profileImageUrl]; UIImage *defaultAvatar=[UIImage imageNamed:@"defaultAvatar.jpg"];//默認(rèn)頭像 [_avatar sd_setImageWithURL:avatarUrl placeholderImage:defaultAvatar]; _avatar.frame=avatarRect; //設(shè)置會(huì)員圖標(biāo)大小和位置 CGFloat userNameX= CGRectGetMaxX(_avatar.frame)+kStatusTableViewCellControlSpacing ; CGFloat userNameY=avatarY; //根據(jù)文本內(nèi)容取得文本占用空間大小 CGSize userNameSize=[status.user.name sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellUserNameFontSize]}]; CGRect userNameRect=CGRectMake(userNameX, userNameY, userNameSize.width,userNameSize.height); _userName.text=status.user.name; _userName.frame=userNameRect; //設(shè)置會(huì)員圖標(biāo)大小和位置 CGFloat mbTypeX=CGRectGetMaxX(_userName.frame)+kStatusTableViewCellControlSpacing; CGFloat mbTypeY=avatarY; CGRect mbTypeRect=CGRectMake(mbTypeX, mbTypeY, kStatusTableViewCellMbTypeWidth, kStatusTableViewCellMbTypeHeight); NSURL *mbTypeUrl=[NSURL URLWithString:status.mbtype]; [_mbType sd_setImageWithURL:mbTypeUrl ]; _mbType.frame=mbTypeRect; //設(shè)置發(fā)布日期大小和位置 CGSize createAtSize=[status.createdAt sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellCreateAtFontSize]}]; CGFloat createAtX=userNameX; CGFloat createAtY=CGRectGetMaxY(_avatar.frame)-createAtSize.height; CGRect createAtRect=CGRectMake(createAtX, createAtY, createAtSize.width, createAtSize.height); _createAt.text=status.createdAt; _createAt.frame=createAtRect; //設(shè)置設(shè)備信息大小和位置 CGSize sourceSize=[status.source sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:kStatusTableViewCellSourceFontSize]}]; CGFloat sourceX=CGRectGetMaxX(_createAt.frame)+kStatusTableViewCellControlSpacing; CGFloat sourceY=createAtY; CGRect sourceRect=CGRectMake(sourceX, sourceY, sourceSize.width,sourceSize.height); _source.text=status.source; _source.frame=sourceRect; //設(shè)置微博內(nèi)容大小和位置 CGFloat textX=avatarX; CGFloat textY=CGRectGetMaxY(_avatar.frame)+kStatusTableViewCellControlSpacing; CGFloat textWidth=self.frame.size.width-kStatusTableViewCellControlSpacing*2; CGSize textSize=[status.text boundingRectWithSize:CGSizeMake(textWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kStatusTableViewCellTextFontSize]} context:nil].size; CGRect textRect=CGRectMake(textX, textY, textSize.width, textSize.height); _text.text=status.text; _text.frame=textRect; _height=CGRectGetMaxY(_text.frame)+kStatusTableViewCellControlSpacing;}
運(yùn)行效果:
在上面的方法中直接調(diào)用了SDWebImage的分類緩存方法設(shè)置圖片,這個(gè)方法可以分配另外一個(gè)線程去加載圖片(同時(shí)對(duì)于頭像還指定了默認(rèn)圖片,網(wǎng)速較慢時(shí)不至于顯示空白),圖片加載后存放在沙箱的緩存文件夾,如下圖:
滾動(dòng)UITableView再次加載同一個(gè)圖片時(shí)SDWebImage就會(huì)自動(dòng)判斷緩存文件是否有效,如果有效就加載緩存文件,否則重新加載。SDWebImage有很多使用的方法,感興趣的朋友可以訪問(wèn)“SDWebImage Reference)”。
通過(guò)前面的演示大家應(yīng)該對(duì)于iOS的Web請(qǐng)求有了大致的了解,可以通過(guò)代理方法接收數(shù)據(jù)也可以直接通過(guò)靜態(tài)方法接收數(shù)據(jù),但是實(shí)際開(kāi)發(fā)中更推薦使用靜態(tài)方法。關(guān)于前面的文件下載示例,更多的是希望大家了解代理方法接收響應(yīng)數(shù)據(jù)的過(guò)程,實(shí)際開(kāi)發(fā)中也不可能使用這種方法進(jìn)行文件下載。這種下載有個(gè)致命的問(wèn)題:不適合進(jìn)行大文件分段下載。因?yàn)榇矸椒ㄔ诮邮諗?shù)據(jù)時(shí)雖然表面看起來(lái)是每次讀取一部分響應(yīng)數(shù)據(jù),事實(shí)上它只有一次請(qǐng)求并且也只接收了一次服務(wù)器響應(yīng),只是當(dāng)響應(yīng)數(shù)據(jù)較大時(shí)系統(tǒng)會(huì)重復(fù)調(diào)用數(shù)據(jù)接收方法,每次將已讀取的數(shù)據(jù)拿出一部分交給數(shù)據(jù)接收方法。這樣一來(lái)對(duì)于上G的文件進(jìn)行下載,如果中途暫停的話再次請(qǐng)求還是從頭開(kāi)始下載,不適合大文件斷點(diǎn)續(xù)傳(另外說(shuō)明一點(diǎn),上面NSURLConnection示例中使用了NSMutableData進(jìn)行數(shù)據(jù)接收和追加只是為了方便演示,實(shí)際開(kāi)發(fā)建議直接寫(xiě)入文件)。
實(shí)際開(kāi)發(fā)文件下載的時(shí)候不管是通過(guò)代理方法還是靜態(tài)方法執(zhí)行請(qǐng)求和響應(yīng),我們都會(huì)分批請(qǐng)求數(shù)據(jù),而不是一次性請(qǐng)求數(shù)據(jù)。假設(shè)一個(gè)文件有1G,那么只要每次請(qǐng)求1M的數(shù)據(jù),請(qǐng)求1024次也就下載完了。那么如何讓服務(wù)器每次只返回1M的數(shù)據(jù)呢?
在網(wǎng)絡(luò)開(kāi)發(fā)中可以在請(qǐng)求的頭文件中設(shè)置一個(gè)range信息,它代表請(qǐng)求數(shù)據(jù)的大小。通過(guò)這個(gè)字段配合服務(wù)器端可以精確的控制每次服務(wù)器響應(yīng)的數(shù)據(jù)范圍。例如指定bytes=0-1023,然后在服務(wù)器端解析Range信息,返回該文件的0到1023之間的數(shù)據(jù)的數(shù)據(jù)即可(共1024Byte)。這樣,只要在每次發(fā)送請(qǐng)求控制這個(gè)頭文件信息就可以做到分批請(qǐng)求。
當(dāng)然,為了讓整個(gè)數(shù)據(jù)保持完整,每次請(qǐng)求的數(shù)據(jù)都需要逐步追加直到整個(gè)文件請(qǐng)求完成。但是如何知道整個(gè)文件的大?。科鋵?shí)在前面的文件下載演示中大家可以看到,可以通過(guò)頭文件信息獲取整個(gè)文件大小。但是這么做的話就必須請(qǐng)求整個(gè)數(shù)據(jù),這樣分段下載就沒(méi)有任何意義了。所幸在WEB開(kāi)發(fā)中我們還有另一種請(qǐng)求方法“HEAD”,通過(guò)這種請(qǐng)求服務(wù)器只會(huì)響應(yīng)頭信息,其他數(shù)據(jù)不會(huì)返回給客戶端,這樣一來(lái)整個(gè)數(shù)據(jù)的大小也就可以得到了。下面給出完整的程序代碼,關(guān)鍵的地方已經(jīng)給出注釋(為了簡(jiǎn)化代碼,這里沒(méi)有使用代理方法):
KCMainViewController.m
//// KCMainViewController.m// UrlConnection//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"#define kUrl @"http://192.168.1.208/FileDownload.aspx"#define kFILE_BLOCK_SIZE (1024) //每次1KB@interface KCMainViewController ()<NSURLConnectionDataDelegate>{ UITextField *_textField; UIButton *_button; UIProgressView *_progressView; UILabel *_label; long long _totalLength; long long _loadedLength;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI];}#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ //地址欄 _textField=[[UITextField alloc]initWithFrame:CGRectMake(10, 50, 300, 25)]; _textField.borderStyle=UITextBorderStyleRoundedRect; _textField.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; _textField.text=@"1.jpg";// _textField.text=@"1.jpg"; [self.view addSubview:_textField]; //進(jìn)度條 _progressView=[[UIProgressView alloc]initWithFrame:CGRectMake(10, 100, 300, 25)]; [self.view addSubview:_progressView]; //狀態(tài)顯示 _label=[[UILabel alloc]initWithFrame:CGRectMake(10, 130, 300, 25)]; _label.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; [self.view addSubview:_label]; //下載按鈕 _button=[[UIButton alloc]initWithFrame:CGRectMake(10, 500, 300, 25)]; [_button setTitle:@"下載" forState:UIControlStateNormal]; [_button setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_button addTarget:self action:@selector(downloadFileAsync) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_button]; }#pragma mark 更新進(jìn)度-(void)updateProgress{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (_loadedLength==_totalLength) { _label.text=@"下載完成"; }else{ _label.text=@"正在下載..."; } [_progressView setProgress:(double)_loadedLength/_totalLength]; }];}#pragma mark 取得請(qǐng)求鏈接-(NSURL *)getDownloadUrl:(NSString *)fileName{ NSString *urlStr=[NSString stringWithFormat:@"%@?file=%@",kUrl,fileName]; urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; return url;}#pragma mark 取得保存地址(保存在沙盒緩存目錄)-(NSString *)getSavePath:(NSString *)fileName{ NSString *path=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; return [path stringByAppendingPathComponent:fileName];}#pragma mark 文件追加-(void)fileAppend:(NSString *)filePath data:(NSData *)data{ //以可寫(xiě)方式打開(kāi)文件 NSFileHandle *fileHandle=[NSFileHandle fileHandleForWritingAtPath:filePath]; //如果存在文件則追加,否則創(chuàng)建 if (fileHandle) { [fileHandle seekToEndOfFile]; [fileHandle writeData:data]; [fileHandle closeFile];//關(guān)閉文件 }else{ [data writeToFile:filePath atomically:YES];//創(chuàng)建文件 }}#pragma mark 取得文件大小-(long long)getFileTotlaLength:(NSString *)fileName{ NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[self getDownloadUrl:fileName] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f]; //設(shè)置為頭信息請(qǐng)求 [request setHTTPMethod:@"HEAD"]; NSURLResponse *response; NSError *error; //注意這里使用了同步請(qǐng)求,直接將文件大小返回 [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error) { NSLog(@"detail error:%@",error.localizedDescription); } //取得內(nèi)容長(zhǎng)度 return response.expectedContentLength;}#pragma mark 下載指定塊大小的數(shù)據(jù)-(void)downloadFile:(NSString *)fileName startByte:(long long)start endByte:(long long)end{ NSString *range=[NSString stringWithFormat:@"Bytes=%lld-%lld",start,end]; NSLog(@"%@",range);// NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:[self getDownloadUrl:fileName]]; NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:[self getDownloadUrl:fileName] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f]; //通過(guò)請(qǐng)求頭設(shè)置數(shù)據(jù)請(qǐng)求范圍 [request setValue:range forHTTPHeaderField:@"Range"]; NSURLResponse *response; NSError *error; //注意這里使用同步請(qǐng)求,避免文件塊追加順序錯(cuò)誤 NSData *data= [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if(!error){ NSLog(@"dataLength=%lld",(long long)data.length); [self fileAppend:[self getSavePath:fileName] data:data]; } else{ NSLog(@"detail error:%@",error.localizedDescription); }}#pragma mark 文件下載-(void)downloadFile{ _totalLength=[self getFileTotlaLength:_textField.text]; _loadedLength=0; long long startSize=0; long long endSize=0; //分段下載 while(startSize< _totalLength){ endSize=startSize+kFILE_BLOCK_SIZE-1; if (endSize>_totalLength) { endSize=_totalLength-1; } [self downloadFile:_textField.text startByte:startSize endByte:endSize]; //更新進(jìn)度 _loadedLength+=(endSize-startSize)+1; [self updateProgress]; startSize+=kFILE_BLOCK_SIZE; }}#pragma mark 異步下載文件-(void)downloadFileAsync{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self downloadFile]; });}@end
運(yùn)行效果:
下載文件的生成過(guò)程:
分段下載的過(guò)程實(shí)現(xiàn)并不復(fù)雜,主要是需要配合后臺(tái)進(jìn)行響應(yīng)進(jìn)行操作。針對(duì)不同的開(kāi)發(fā)技術(shù),服務(wù)器端處理方式稍有差別,但是基本原理是一樣的,那就是讀取Range信息,按需提供相應(yīng)數(shù)據(jù)。
在做WEB應(yīng)用程序開(kāi)發(fā)時(shí),如果要上傳一個(gè)文件往往會(huì)給form設(shè)置一個(gè)enctype=”multipart/form-data”的屬性,不設(shè)置這個(gè)值在后臺(tái)無(wú)法正常接收文件。在WEB開(kāi)發(fā)過(guò)程中,form的這個(gè)屬性其實(shí)本質(zhì)就是指定請(qǐng)求頭中Content-Type類型,當(dāng)然使用GET方法提交就不用說(shuō)了,必須使用URL編碼。但是如果使用POST方法傳遞數(shù)據(jù)其實(shí)也是類似的,同樣需要進(jìn)行編碼,具體編碼方式其實(shí)就是通過(guò)enctype屬性進(jìn)行設(shè)置的。常用的屬性值有:
要實(shí)現(xiàn)文件上傳,必須采用POST上傳,同時(shí)請(qǐng)求類型必須是multipart/form-data。在Web開(kāi)發(fā)中,開(kāi)發(fā)人員不必過(guò)多的考慮mutiparty/form-data更多的細(xì)節(jié),一般使用file控件即可完成文件上傳。但是在iOS中如果要實(shí)現(xiàn)文件上傳,就沒(méi)有那么簡(jiǎn)單了,我們必須了解這種數(shù)據(jù)類型的請(qǐng)求是如何工作的。
下面是在瀏覽器中上傳一個(gè)文件時(shí),發(fā)送的請(qǐng)求頭:
這是發(fā)送的請(qǐng)求體內(nèi)容:
在請(qǐng)求頭中,最重要的就是Content-Type,它的值分為兩部分:前半部分是內(nèi)容類型,前面已經(jīng)解釋過(guò)了;后面是邊界boundary用來(lái)分隔表單中不同部分的數(shù)據(jù),后面一串?dāng)?shù)字是瀏覽器自動(dòng)生成的,它的格式并不固定,可以是任意字符。和請(qǐng)求體中的源代碼部分進(jìn)行對(duì)比不難發(fā)現(xiàn)其實(shí)boundary的內(nèi)容和請(qǐng)求體的數(shù)據(jù)部分前的字符串相比少了兩個(gè)“--”。請(qǐng)求體中Content-Disposition中指定了表單元素的name屬性和文件名稱,同時(shí)指定了Content-Type表示文件類型。當(dāng)然,在請(qǐng)求體中最重要的就是后面的數(shù)據(jù)部分,它其實(shí)就是二進(jìn)制字符串。由此可以得出以下結(jié)論,請(qǐng)求體內(nèi)容由如下幾部分按順序執(zhí)行組成:
--boundaryContent-Disposition:form-data;name=”表單控件名稱”;filename=”上傳文件名稱”Content-Type:文件MIME Types文件二進(jìn)制數(shù)據(jù);--boundary--了解這些信息后,只要使用POST方法給服務(wù)器端發(fā)送請(qǐng)求并且請(qǐng)求內(nèi)容按照上面的格式設(shè)置即可。
下面是實(shí)現(xiàn)代碼:
//// KCMainViewController.m// UrlConnection//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"#define kUrl @"http://192.168.1.208/FileUpload.aspx"#define kBOUNDARY_STRING @"KenshinCui"@interface KCMainViewController ()<NSURLConnectionDataDelegate>{ UITextField *_textField; UIButton *_button;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; }#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ //地址欄 _textField=[[UITextField alloc]initWithFrame:CGRectMake(10, 50, 300, 25)]; _textField.borderStyle=UITextBorderStyleRoundedRect; _textField.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; _textField.text=@"pic.jpg"; [self.view addSubview:_textField]; //上傳按鈕 _button=[[UIButton alloc]initWithFrame:CGRectMake(10, 500, 300, 25)]; [_button setTitle:@"上傳" forState:UIControlStateNormal]; [_button setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_button addTarget:self action:@selector(uploadFile) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_button]; }#pragma mark 取得請(qǐng)求鏈接-(NSURL *)getUploadUrl:(NSString *)fileName{ NSString *urlStr=[NSString stringWithFormat:@"%@?file=%@",kUrl,fileName]; urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; return url;}#pragma mark 取得mime types-(NSString *)getMIMETypes:(NSString *)fileName{ return @"image/jpg";}#pragma mark 取得數(shù)據(jù)體-(NSData *)getHttpBody:(NSString *)fileName{ NSMutableData *dataM=[NSMutableData data]; NSString *strTop=[NSString stringWithFormat:@"--%@/nContent-Disposition: form-data; name=/"file1/"; filename=/"%@/"/nContent-Type: %@/n/n",kBOUNDARY_STRING,fileName,[self getMIMETypes:fileName]]; NSString *strBottom=[NSString stringWithFormat:@"/n--%@--",kBOUNDARY_STRING]; NSString *filePath=[[NSBundle mainBundle] pathForResource:fileName ofType:nil]; NSData *fileData=[NSData dataWithContentsOfFile:filePath]; [dataM appendData:[strTop dataUsingEncoding:NSUTF8StringEncoding]]; [dataM appendData:fileData]; [dataM appendData:[strBottom dataUsingEncoding:NSUTF8StringEncoding]]; return dataM;}#pragma mark 上傳文件-(void)uploadFile{ NSString *fileName=_textField.text; NSMutableURLRequest *request= [NSMutableURLRequest requestWithURL:[self getUploadUrl:fileName] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f]; request.HTTPMethod=@"POST"; NSData *data=[self getHttpBody:fileName]; //通過(guò)請(qǐng)求頭設(shè)置 [request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)data.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING] forHTTPHeaderField:@"Content-Type"]; //設(shè)置數(shù)據(jù)體 request.HTTPBody=data; //發(fā)送請(qǐng)求 [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if(connectionError){ NSLog(@"error:%@",connectionError.localizedDescription); } }];}@end
NSURLConnection是2003年伴隨著Safari一起發(fā)行的網(wǎng)絡(luò)開(kāi)發(fā)API,距今已經(jīng)有十一年。當(dāng)然,在這十一年間它表現(xiàn)的相當(dāng)優(yōu)秀,有大量的應(yīng)用基礎(chǔ),這也是為什么前面花了那么長(zhǎng)時(shí)間對(duì)它進(jìn)行詳細(xì)介紹的原因。但是這些年伴隨著iPhone、iPad的發(fā)展,對(duì)于NSURLConnection設(shè)計(jì)理念也提出了新的挑戰(zhàn)。在2013年WWDC上蘋(píng)果揭開(kāi)了NSURLSession的面紗,將它作為NSURLConnection的繼任者。相比較NSURLConnection,NSURLSession提供了配置會(huì)話緩存、協(xié)議、cookie和證書(shū)能力,這使得網(wǎng)絡(luò)架構(gòu)和應(yīng)用程序可以獨(dú)立工作、互不干擾。另外,NSURLSession另一個(gè)重要的部分是會(huì)話任務(wù),它負(fù)責(zé)加載數(shù)據(jù),在客戶端和服務(wù)器端進(jìn)行文件的上傳下載。
通過(guò)前面的介紹大家可以看到,NSURLConnection完成的三個(gè)主要任務(wù):獲取數(shù)據(jù)(通常是JSON、XML等)、文件上傳、文件下載。其實(shí)在NSURLSession時(shí)代,他們分別由三個(gè)任務(wù)來(lái)完成:NSURLSessionData、NSURLSessionUploadTask、NSURLSessionDownloadTask,這三個(gè)類都是NSURLSessionTask這個(gè)抽象類的子類,相比直接使用NSURLConnection,NSURLSessionTask支持任務(wù)的暫停、取消和恢復(fù),并且默認(rèn)任務(wù)運(yùn)行在其他非主線程中,具體關(guān)系圖如下:
前面通過(guò)請(qǐng)求一個(gè)微博數(shù)據(jù)進(jìn)行數(shù)據(jù)請(qǐng)求演示,現(xiàn)在通過(guò)NSURLSessionDataTask實(shí)現(xiàn)這個(gè)功能,其實(shí)現(xiàn)流程與使用NSURLConnection的靜態(tài)方法類似,下面是主要代碼:
-(void)loadJsonData{ //1.創(chuàng)建url NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.208/ViewStatus.aspx?userName=%@&password=%@",@"KenshinCui",@"123"]; urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; //2.創(chuàng)建請(qǐng)求 NSURLRequest *request=[NSURLRequest requestWithURL:url]; //3.創(chuàng)建會(huì)話(這里使用了一個(gè)全局會(huì)話)并且啟動(dòng)任務(wù) NSURLSession *session=[NSURLSession sharedSession]; //從會(huì)話創(chuàng)建任務(wù) NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSString *dataStr=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",dataStr); }else{ NSLog(@"error is :%@",error.localizedDescription); } }]; [dataTask resume];//恢復(fù)線程,啟動(dòng)任務(wù)}
下面看一下如何使用NSURLSessionUploadTask實(shí)現(xiàn)文件上傳,這里貼出主要的幾個(gè)方法:
#pragma mark 取得mime types-(NSString *)getMIMETypes:(NSString *)fileName{ return @"image/jpg";}#pragma mark 取得數(shù)據(jù)體-(NSData *)getHttpBody:(NSString *)fileName{ NSString *boundary=@"KenshinCui"; NSMutableData *dataM=[NSMutableData data]; NSString *strTop=[NSString stringWithFormat:@"--%@/nContent-Disposition: form-data; name=/"file1/"; filename=/"%@/"/nContent-Type: %@/n/n",boundary,fileName,[self getMIMETypes:fileName]]; NSString *strBottom=[NSString stringWithFormat:@"/n--%@--",boundary]; NSString *filePath=[[NSBundle mainBundle] pathForResource:fileName ofType:nil]; NSData *fileData=[NSData dataWithContentsOfFile:filePath]; [dataM appendData:[strTop dataUsingEncoding:NSUTF8StringEncoding]]; [dataM appendData:fileData]; [dataM appendData:[strBottom dataUsingEncoding:NSUTF8StringEncoding]]; return dataM;}#pragma mark 上傳文件-(void)uploadFile{ NSString *fileName=@"pic.jpg"; //1.創(chuàng)建url NSString *urlStr=@"http://192.168.1.208/FileUpload.aspx"; urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; //2.創(chuàng)建請(qǐng)求 NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url]; request.HTTPMethod=@"POST"; //3.構(gòu)建數(shù)據(jù) NSString *path=[[NSBundle mainBundle] pathForResource:fileName ofType:nil]; NSData *data=[self getHttpBody:fileName]; request.HTTPBody=data; [request setValue:[NSString stringWithFormat:@"%lu",(unsigned long)data.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",@"KenshinCui"] forHTTPHeaderField:@"Content-Type"]; //4.創(chuàng)建會(huì)話 NSURLSession *session=[NSURLSession sharedSession]; NSURLSessionUploadTask *uploadTask=[session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { NSString *dataStr=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",dataStr); }else{ NSLog(@"error is :%@",error.localizedDescription); } }]; [uploadTask resume];}
如果僅僅通過(guò)上面的方法或許文件上傳還看不出和NSURLConnection之間的區(qū)別,因?yàn)槠唇由蟼鲾?shù)據(jù)的過(guò)程和前面是一樣的。事實(shí)上在NSURLSessionUploadTask中還提供了一個(gè)- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler方法用于文件上傳。這個(gè)方法通常會(huì)配合“PUT”請(qǐng)求進(jìn)行使用,由于PUT方法包含在Web DAV協(xié)議中,不同的WEB服務(wù)器其配置啟用PUT的方法也不同,并且出于安全考慮,各類WEB服務(wù)器默認(rèn)對(duì)PUT請(qǐng)求也是拒絕的,所以實(shí)際使用時(shí)還需做重分考慮,在這里不具體介紹,有興趣的朋友可以自己試驗(yàn)一下。
使用NSURLSessionDownloadTask下載文件的過(guò)程與前面差不多,需要注意的是文件下載文件之后會(huì)自動(dòng)保存到一個(gè)臨時(shí)目錄,需要開(kāi)發(fā)人員自己將此文件重新放到其他指定的目錄中。
-(void)downloadFile{ //1.創(chuàng)建url NSString *fileName=@"1.jpg"; NSString *urlStr=[NSString stringWithFormat: @"http://192.168.1.208/FileDownload.aspx?file=%@",fileName]; urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; //2.創(chuàng)建請(qǐng)求 NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url]; //3.創(chuàng)建會(huì)話(這里使用了一個(gè)全局會(huì)話)并且啟動(dòng)任務(wù) NSURLSession *session=[NSURLSession sharedSession]; NSURLSessionDownloadTask *downloadTask=[session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if (!error) { //注意location是下載后的臨時(shí)保存路徑,需要將它移動(dòng)到需要保存的位置 NSError *saveError; NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:fileName]; NSLog(@"%@",savePath); NSURL *saveUrl=[NSURL fileURLWithPath:savePath]; [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&saveError]; if (!saveError) { NSLog(@"save sucess."); }else{ NSLog(@"error is :%@",saveError.localizedDescription); } }else{ NSLog(@"error is :%@",error.localizedDescription); } }]; [downloadTask resume];}
NSURLConnection通過(guò)全局狀態(tài)來(lái)管理cookies、認(rèn)證信息等公共資源,這樣如果遇到兩個(gè)連接需要使用不同的資源配置情況時(shí)就無(wú)法解決了,但是這個(gè)問(wèn)題在NSURLSession中得到了解決。NSURLSession同時(shí)對(duì)應(yīng)著多個(gè)連接,會(huì)話通過(guò)工廠方法來(lái)創(chuàng)建,同一個(gè)會(huì)話中使用相同的狀態(tài)信息。NSURLSession支持進(jìn)程三種會(huì)話:
defaultSessionConfiguration:進(jìn)程內(nèi)會(huì)話(默認(rèn)會(huì)話),用硬盤(pán)來(lái)緩存數(shù)據(jù)。 ephemeralSessionConfiguration:臨時(shí)的進(jìn)程內(nèi)會(huì)話(內(nèi)存),不會(huì)將cookie、緩存儲(chǔ)存到本地,只會(huì)放到內(nèi)存中,當(dāng)應(yīng)用程序退出后數(shù)據(jù)也會(huì)消失。 backgroundSessionConfiguration:后臺(tái)會(huì)話,相比默認(rèn)會(huì)話,該會(huì)話會(huì)在后臺(tái)開(kāi)啟一個(gè)線程進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)處理。下面將通過(guò)一個(gè)文件下載功能對(duì)兩種會(huì)話進(jìn)行演示,在這個(gè)過(guò)程中也會(huì)用到任務(wù)的代理方法對(duì)上傳操作進(jìn)行更加細(xì)致的控制。下面先看一下使用默認(rèn)會(huì)話下載文件,代碼中演示了如何通過(guò)NSURLSessionConfiguration進(jìn)行會(huì)話配置,如果通過(guò)代理方法進(jìn)行文件下載進(jìn)度展示(類似于前面中使用NSURLConnection代理方法,其實(shí)下載并未分段,如果需要分段需要配合后臺(tái)進(jìn)行),同時(shí)在這個(gè)過(guò)程中可以準(zhǔn)確控制任務(wù)的取消、掛起和恢復(fù)。
//// KCMainViewController.m// URLSession//// Created by Kenshin Cui on 14-03-23.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"@interface KCMainViewController ()<NSURLSessionDownloadDelegate>{ UITextField *_textField; UIProgressView *_progressView; UILabel *_label; UIButton *_btnDownload; UIButton *_btnCancel; UIButton *_btnSuspend; UIButton *_btnResume; NSURLSessionDownloadTask *_downloadTask;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI];}#pragma mark 界面布局-(void)layoutUI{ //地址欄 _textField=[[UITextField alloc]initWithFrame:CGRectMake(10, 50, 300, 25)]; _textField.borderStyle=UITextBorderStyleRoundedRect; _textField.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; _textField.text=@"[Objective-C.程序設(shè)計(jì)(第4版)].(斯蒂芬).林冀等.掃描版[電子書(shū)www.minxue.net].pdf"; [self.view addSubview:_textField]; //進(jìn)度條 _progressView=[[UIProgressView alloc]initWithFrame:CGRectMake(10, 100, 300, 25)]; [self.view addSubview:_progressView]; //狀態(tài)顯示 _label=[[UILabel alloc]initWithFrame:CGRectMake(10, 130, 300, 25)]; _label.textColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0]; [self.view addSubview:_label]; //下載按鈕 _btnDownload=[[UIButton alloc]initWithFrame:CGRectMake(20, 500, 50, 25)]; [_btnDownload setTitle:@"下載" forState:UIControlStateNormal]; [_btnDownload setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_btnDownload addTarget:self action:@selector(downloadFile) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnDownload]; //取消按鈕 _btnCancel=[[UIButton alloc]initWithFrame:CGRectMake(100, 500, 50, 25)]; [_btnCancel setTitle:@"取消" forState:UIControlStateNormal]; [_btnCancel setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_btnCancel addTarget:self action:@selector(cancelDownload) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnCancel]; //掛起按鈕 _btnSuspend=[[UIButton alloc]initWithFrame:CGRectMake(180, 500, 50, 25)]; [_btnSuspend setTitle:@"掛起" forState:UIControlStateNormal]; [_btnSuspend setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_btnSuspend addTarget:self action:@selector(suspendDownload) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnSuspend]; //恢復(fù)按鈕 _btnResume=[[UIButton alloc]initWithFrame:CGRectMake(260, 500, 50, 25)]; [_btnResume setTitle:@"恢復(fù)" forState:UIControlStateNormal]; [_btnResume setTitleColor:[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; [_btnResume addTarget:self action:@selector(resumeDownload) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_btnResume];}#pragma mark 設(shè)置界面狀態(tài)-(void)setUIStatus:(int64_t)totalBytesWritten expectedToWrite:(int64_t)totalBytesExpectedToWrite{ dispatch_async(dispatch_get_main_queue(), ^{ _progressView.progress=(float)totalBytesWritten/totalBytesExpectedToWrite; if (totalBytesWritten==totalBytesExpectedToWrite) { _label.text=@"下載完成"; [UIApplication sharedApplication].networkActivityIndicatorVisible=NO; _btnDownload.enabled=YES; }else{ _label.text=@"正在下載..."; [UIApplication sharedApplication].networkActivityIndicatorVisible=YES; } });}#pragma mark 文件下載-(void)downloadFile{ //1.創(chuàng)建url NSString *fileName=_textField.text; NSString *urlStr=[NSString stringWithFormat: @"http://192.168.1.208/FileDownload.aspx?file=%@",fileName]; urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; //2.創(chuàng)建請(qǐng)求 NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url]; //3.創(chuàng)建會(huì)話 //默認(rèn)會(huì)話 NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfig.timeoutIntervalForRequest=5.0f;//請(qǐng)求超時(shí)時(shí)間 sessionConfig.allowsCellularaccess=true;//是否允許蜂窩網(wǎng)絡(luò)下載(2G/3G/4G) //創(chuàng)建會(huì)話 NSURLSession *session=[NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];//指定配置和代理 _downloadTask=[session downloadTaskWithRequest:request]; [_downloadTask resume];}#pragma mark 取消下載-(void)cancelDownload{ [_downloadTask cancel]; }#pragma mark 掛起下載-(void)suspendDownload{ [_downloadTask suspend];}#pragma mark 恢復(fù)下載下載-(void)resumeDownload{ [_downloadTask resume];}#pragma mark - 下載任務(wù)代理#pragma mark 下載中(會(huì)多次調(diào)用,可以記錄下載進(jìn)度)-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ [self setUIStatus:totalBytesWritten expectedToWrite:totalBytesExpectedToWrite];}#pragma mark 下載完成-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSError *error; NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:_textField.text]; NSLog(@"%@",savePath); NSURL *saveUrl=[NSURL fileURLWithPath:savePath]; [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&error]; if (error) { NSLog(@"Error is:%@",error.localizedDescription); }}#pragma mark 任務(wù)完成,不管是否下載成功-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ [self setUIStatus:0 expectedToWrite:0]; if (error) { NSLog(@"Error is:%@",error.localizedDescription); }}@end
演示效果:
NSURLSession支持程序的后臺(tái)下載和上傳,蘋(píng)果官方將其稱為進(jìn)程之外的上傳和下載,這些任務(wù)都是交給后臺(tái)守護(hù)線程完成的,而非應(yīng)用程序本身。即使文件在下載和上傳過(guò)程中崩潰了也可以繼續(xù)運(yùn)行(注意如果用戶強(qiáng)制退關(guān)閉應(yīng)用程序,NSURLSession會(huì)斷開(kāi)連接)。下面看一下如何在后臺(tái)進(jìn)行文件下載,這在實(shí)際開(kāi)發(fā)中往往很有效,例如在手機(jī)上緩存一個(gè)視頻在沒(méi)有網(wǎng)絡(luò)的時(shí)候觀看(為了簡(jiǎn)化程序這里不再演示任務(wù)的取消、掛起等操作)。下面對(duì)前面的程序稍作調(diào)整使程序能在后臺(tái)完成下載操作:
//// KCMainViewController.m// URLSession//// Created by Kenshin Cui on 14-03-23.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"#import "AppDelegate.h"@interface KCMainViewController ()<NSURLSessionDownloadDelegate>{ NSURLSessionDownloadTask *_downloadTask; NSString *_fileName;}@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self downloadFile];}#pragma mark 取得一個(gè)后臺(tái)會(huì)話(保證一個(gè)后臺(tái)會(huì)話,這通常很有必要)-(NSURLSession *)backgroundSession{ static NSURLSession *session; static dispatch_once_t token; dispatch_once(&token, ^{ NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.cmjstudio.URLSession"]; sessionConfig.timeoutIntervalForRequest=5.0f;//請(qǐng)求超時(shí)時(shí)間 sessionConfig.discretionary=YES;//系統(tǒng)自動(dòng)選擇最佳網(wǎng)絡(luò)下載 sessionConfig.HTTPMaximumConnectionsPerHost=5;//限制每次最多一個(gè)連接 //創(chuàng)建會(huì)話 session=[NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];//指定配置和代理 }); return session;}#pragma mark 文件下載-(void)downloadFile{ _fileName=@"1.mp4"; NSString *urlStr=[NSString stringWithFormat: @"http://192.168.1.208/FileDownload.aspx?file=%@",_fileName]; urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSURL *url=[NSURL URLWithString:urlStr]; NSMutableURLRequest *request=[NSMutableURLRequest requestWithURL:url]; //后臺(tái)會(huì)話 _downloadTask=[[self backgroundSession] downloadTaskWithRequest:request]; [_downloadTask resume];}#pragma mark - 下載任務(wù)代理#pragma mark 下載中(會(huì)多次調(diào)用,可以記錄下載進(jìn)度)-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{// [NSThread sleepForTimeInterval:0.5];// NSLog(@"%.2f",(double)totalBytesWritten/totalBytesExpectedToWrite);}#pragma mark 下載完成-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSError *error; NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@",[NSDate date]]]; NSLog(@"%@",savePath); NSURL *saveUrl=[NSURL fileURLWithPath:savePath]; [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&error]; if (error) { NSLog(@"didFinishDownloadingToURL:Error is %@",error.localizedDescription); }}#pragma mark 任務(wù)完成,不管是否下載成功-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{ if (error) { NSLog(@"DidCompleteWithError:Error is %@",error.localizedDescription); }}@end
運(yùn)行上面的程序會(huì)發(fā)現(xiàn)即使程序退出到后臺(tái)也能正常完成文件下載。為了提高用戶體驗(yàn),通常會(huì)在下載時(shí)設(shè)置文件下載進(jìn)度,但是通過(guò)前面的介紹可以知道:當(dāng)程序進(jìn)入后臺(tái)后,事實(shí)上任務(wù)是交給iOS系統(tǒng)來(lái)調(diào)度的,具體什么時(shí)候下載完成就不得而知,例如有個(gè)較大的文件經(jīng)過(guò)一個(gè)小時(shí)下載完了,正常打開(kāi)應(yīng)用程序看到的此文件下載進(jìn)度應(yīng)該在100%的位置,但是由于程序已經(jīng)在后臺(tái)無(wú)法更新程序UI,而此時(shí)可以通過(guò)應(yīng)用程序代理方法進(jìn)行UI更新。具體原理如下圖所示:
當(dāng)NSURLSession在后臺(tái)開(kāi)啟幾個(gè)任務(wù)之后,如果有其中幾個(gè)任務(wù)完成后系統(tǒng)會(huì)調(diào)用此應(yīng)用程序的-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler代理方法;此方法會(huì)包含一個(gè)competionHandler(此操作表示應(yīng)用完成所有處理工作),通常我們會(huì)保存此對(duì)象;直到最后一個(gè)任務(wù)完成,此時(shí)會(huì)重新通過(guò)會(huì)話標(biāo)識(shí)(上面sessionConfig中設(shè)置的)找到對(duì)應(yīng)的會(huì)話并調(diào)用NSURLSession的-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session代理方法,在這個(gè)方法中通常可以進(jìn)行UI更新,并調(diào)用completionHandler通知系統(tǒng)已經(jīng)完成所有操作。具體兩個(gè)方法代碼示例如下:
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{ //backgroundSessionCompletionHandler是自定義的一個(gè)屬性 self.backgroundSessionCompletionHandler=completionHandler; }-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; //Other Operation.... if (appDelegate.backgroundSessionCompletionHandler) { void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler; appDelegate.backgroundSessionCompletionHandler = nil; completionHandler(); }}
網(wǎng)絡(luò)開(kāi)發(fā)中還有一個(gè)常用的UI控件UIWebView,它是iOS中內(nèi)置的瀏覽器控件,功能十分強(qiáng)大。如一些社交軟件往往在應(yīng)用程序內(nèi)不需要打開(kāi)其他瀏覽器就能看一些新聞之類的頁(yè)面,就是通過(guò)這個(gè)控件實(shí)現(xiàn)的。需要注意的是UIWebView不僅能加載網(wǎng)絡(luò)資源還可以加載本地資源,目前支持的常用的文檔格式如:html、pdf、docx、txt等。
下面將通過(guò)一個(gè)UIWebView開(kāi)發(fā)一個(gè)簡(jiǎn)單的瀏覽器,界面布局大致如下:
在這個(gè)瀏覽器中將實(shí)現(xiàn)這樣幾個(gè)功能:
1.如果輸入以”file://”開(kāi)頭的地址將加載Bundle中的文件
2.如果輸入以“http”開(kāi)頭的地址將加載網(wǎng)絡(luò)資源
3.如果輸入內(nèi)容不符合上面兩種情況將使用bing搜索此內(nèi)容
//// KCMainViewController.m// UIWebView//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"'#define kFILEPROTOCOL @"file://"@interface KCMainViewController ()<UISearchBarDelegate,UIWebViewDelegate>{ UIWebView *_webView; UIToolbar *_toolbar; UISearchBar *_searchBar; UIBarButtonItem *_barButtonBack; UIBarButtonItem *_barButtonForward;}@end@implementation KCMainViewController#pragma mark - 界面UI事件- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI];}#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ /*添加地址欄*/ _searchBar=[[UISearchBar alloc]initWithFrame:CGRectMake(0, 20, 320, 44)]; _searchBar.delegate=self; [self.view addSubview:_searchBar]; /*添加瀏覽器控件*/ _webView=[[UIWebView alloc]initWithFrame:CGRectMake(0, 64, 320, 460)]; _webView.dataDetectorTypes=UIDataDetectorTypeAll;//數(shù)據(jù)檢測(cè),例如內(nèi)容中有郵件地址,點(diǎn)擊之后可以打開(kāi)郵件軟件編寫(xiě)郵件 _webView.delegate=self; [self.view addSubview:_webView]; /*添加下方工具欄*/ _toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, 524, 320, 44)]; UIButton *btnBack=[UIButton buttonWithType:UIButtonTypeCustom]; btnBack.bounds=CGRectMake(0, 0, 32, 32); [btnBack setImage:[UIImage imageNamed:@"back.png"] forState:UIControlStateNormal]; [btnBack setImage:[UIImage imageNamed:@"back_disable.png"] forState:UIControlStateDisabled]; [btnBack addTarget:self action:@selector(webViewBack) forControlEvents:UIControlEventTouchUpInside]; _barButtonBack=[[UIBarButtonItem alloc]initWithCustomView:btnBack]; _barButtonBack.enabled=NO; UIBarButtonItem *btnSpacing=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIButton *btnForward=[UIButton buttonWithType:UIButtonTypeCustom]; btnForward.bounds=CGRectMake(0, 0, 32, 32); [btnForward setImage:[UIImage imageNamed:@"forward.png"] forState:UIControlStateNormal]; [btnForward setImage:[UIImage imageNamed:@"forward_disable.png"] forState:UIControlStateDisabled]; [btnForward addTarget:self action:@selector(webViewForward) forControlEvents:UIControlEventTouchUpInside]; _barButtonForward=[[UIBarButtonItem alloc]initWithCustomView:btnForward]; _barButtonForward.enabled=NO; _toolbar.items=@[_barButtonBack,btnSpacing,_barButtonForward]; [self.view addSubview:_toolbar];}#pragma mark 設(shè)置前進(jìn)后退按鈕狀態(tài)-(void)setBarButtonStatus{ if (_webView.canGoBack) { _barButtonBack.enabled=YES; }else{ _barButtonBack.enabled=NO; } if(_webView.canGoForward){ _barButtonForward.enabled=YES; }else{ _barButtonForward.enabled=NO; }}#pragma mark 后退-(void)webViewBack{ [_webView goBack];}#pragma mark 前進(jìn)-(void)webViewForward{ [_webView goForward];}#pragma mark 瀏覽器請(qǐng)求-(void)request:(NSString *)urlStr{ //創(chuàng)建url NSURL *url; //如果file://開(kāi)頭的字符串則加載bundle中的文件 if([urlStr hasPrefix:kFILEPROTOCOL]){ //取得文件名 NSRange range= [urlStr rangeOfString:kFILEPROTOCOL]; NSString *fileName=[urlStr substringFromIndex:range.length]; url=[[NSBundle mainBundle] URLForResource:fileName withExtension:nil]; }else if(urlStr.length>0){ //如果是http請(qǐng)求則直接打開(kāi)網(wǎng)站 if ([urlStr hasPrefix:@"http"]) { url=[NSURL URLWithString:urlStr]; }else{//如果不符合任何協(xié)議則進(jìn)行搜索 urlStr=[NSString stringWithFormat:@"http://m.bing.com/search?q=%@",urlStr]; } urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];//url編碼 url=[NSURL URLWithString:urlStr]; } //創(chuàng)建請(qǐng)求 NSURLRequest *request=[NSURLRequest requestWithURL:url]; //加載請(qǐng)求頁(yè)面 [_webView loadRequest:request];}#pragma mark - WebView 代理方法#pragma mark 開(kāi)始加載-(void)webViewDidStartLoad:(UIWebView *)webView{ //顯示網(wǎng)絡(luò)請(qǐng)求加載 [UIApplication sharedApplication].networkActivityIndicatorVisible=true;}#pragma mark 加載完畢-(void)webViewDidFinishLoad:(UIWebView *)webView{ //隱藏網(wǎng)絡(luò)請(qǐng)求加載圖標(biāo) [UIApplication sharedApplication].networkActivityIndicatorVisible=false; //設(shè)置按鈕狀態(tài) [self setBarButtonStatus];}#pragma mark 加載失敗-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ NSLog(@"error detail:%@",error.localizedDescription); UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"系統(tǒng)提示" message:@"網(wǎng)絡(luò)連接發(fā)生錯(cuò)誤!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil]; [alert show];}#pragma mark - SearchBar 代理方法#pragma mark 點(diǎn)擊搜索按鈕或回車-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{ [self request:_searchBar.text];}@end
運(yùn)行效果:
其實(shí)UIWebView整個(gè)使用相當(dāng)簡(jiǎn)單:創(chuàng)建URL->創(chuàng)建請(qǐng)求->加載請(qǐng)求,無(wú)論是加載本地文件還是Web內(nèi)容都是這三個(gè)步驟。UIWebView內(nèi)容加載事件同樣是通過(guò)代理通知外界,常用的代理方法如開(kāi)始加載、加載完成、加載出錯(cuò)等,這些方法通??梢詭椭_(kāi)發(fā)者更好的控制請(qǐng)求加載過(guò)程。
注意:UIWebView打開(kāi)本地pdf、word文件依靠的并不是UIWebView自身解析,而是依靠MIME Type識(shí)別文件類型并調(diào)用對(duì)應(yīng)應(yīng)用打開(kāi)。
UIWebView與頁(yè)面的交互主要體現(xiàn)在兩方面:使用ObjC方法進(jìn)行頁(yè)面操作、在頁(yè)面中調(diào)用ObjC方法兩部分。和其他移動(dòng)操作系統(tǒng)不同,iOS中所有的交互都集中于一個(gè)stringByEvaluatingjavaScriptFromString:方法中,以此來(lái)簡(jiǎn)化開(kāi)發(fā)過(guò)程。
1.首先在request方法中使用loaDHTMLString:加載了html內(nèi)容,當(dāng)然你也可以將html放到bundle或沙盒中讀取并且加載。
2.然后在webViewDidFinishLoad:代理方法中通過(guò)stringByEvaluatingJavascriptFromString: 方法可以操作頁(yè)面中的元素,例如在下面的方法中讀取了頁(yè)面標(biāo)題、修改了其中的內(nèi)容。
//// KCMainViewController.m// UIWebView//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"'@interface KCMainViewController ()<UISearchBarDelegate,UIWebViewDelegate>{ UIWebView *_webView;}@end@implementation KCMainViewController#pragma mark - 界面UI事件- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; [self request];}#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ /*添加瀏覽器控件*/ _webView=[[UIWebView alloc]initWithFrame:CGRectMake(0, 20, 320, 548)]; _webView.dataDetectorTypes=UIDataDetectorTypeAll;//數(shù)據(jù)檢測(cè)類型,例如內(nèi)容中有郵件地址,點(diǎn)擊之后可以打開(kāi)郵件軟件編寫(xiě)郵件 _webView.delegate=self; [self.view addSubview:_webView];}#pragma mark 瀏覽器請(qǐng)求-(void)request{ //加載html內(nèi)容 NSString *htmlStr=@"<html>/ <head><title>Kenshin Cui's Blog</title></head>/ <body style=/"color:#0092FF;/">/ <h1 id=/"header/">I am Kenshin Cui</h1>/ <p>iOS 開(kāi)發(fā)系列</p>/ </body></html>"; //加載請(qǐng)求頁(yè)面 [_webView loadHTMLString:htmlStr baseURL:nil];}#pragma mark - WebView 代理方法#pragma mark 開(kāi)始加載-(void)webViewDidStartLoad:(UIWebView *)webView{ //顯示網(wǎng)絡(luò)請(qǐng)求加載 [UIApplication sharedApplication].networkActivityIndicatorVisible=true;}#pragma mark 加載完畢-(void)webViewDidFinishLoad:(UIWebView *)webView{ //隱藏網(wǎng)絡(luò)請(qǐng)求加載圖標(biāo) [UIApplication sharedApplication].networkActivityIndicatorVisible=false; //取得html內(nèi)容 NSLog(@"%@",[_webView stringByEvaluatingJavaScriptFromString:@"document.title"]); //修改頁(yè)面內(nèi)容 NSLog(@"%@",[_webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('header').innerHTML='Kenshin Cui//'s Blog'"]);}#pragma mark 加載失敗-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ NSLog(@"error detail:%@",error.localizedDescription); UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"系統(tǒng)提示" message:@"網(wǎng)絡(luò)連接發(fā)生錯(cuò)誤!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil]; [alert show];}@end
運(yùn)行效果:
頁(yè)面中的js是無(wú)法直接調(diào)用ObjC方法的,但是可以變換一下思路:當(dāng)需要進(jìn)行一個(gè)js操作時(shí)讓頁(yè)面進(jìn)行一個(gè)重定向,并且在重定向過(guò)程中傳入一系列參數(shù)。在UIWebView的代理方法中有一個(gè)webView: shouldStartLoadWithRequest:navigationType方法,這個(gè)方法會(huì)在頁(yè)面加載前執(zhí)行,這樣可以在這里攔截重定向,并且獲取定向URL中的參數(shù),根據(jù)這些參數(shù)約定一個(gè)方法去執(zhí)行。
當(dāng)訪問(wèn)百度搜索手機(jī)版時(shí)會(huì)發(fā)現(xiàn),有時(shí)候點(diǎn)擊頁(yè)面中的某個(gè)元素可以調(diào)出iOS操作系統(tǒng)的UIActionSheet,下面不妨模擬一下這個(gè)過(guò)程。首先需要定義一個(gè)js方法,為了方便擴(kuò)展,這個(gè)js保存在MyJs.js文件中存放到Bundle中,同時(shí)在頁(yè)面中加載這個(gè)文件內(nèi)容。MyJs.js內(nèi)容如下:
function showSheet(title,cancelButtonTitle,destructiveButtonTitle,otherButtonTitle) { var url='kcactionsheet://?'; var paramas=title+'&'+cancelButtonTitle+'&'+destructiveButtonTitle; if(otherButtonTitle){ paramas+='&'+otherButtonTitle; } window.location.href=url+ encodeURIComponent(paramas);}var blog=document.getElementById('blog');blog.onclick=function(){ showSheet('系統(tǒng)提示','取消','確定',null);};
這個(gè)js的功能相當(dāng)單一,調(diào)用showSheet方法則會(huì)進(jìn)行一個(gè)重定向,調(diào)用過(guò)程中需要傳遞一系列參數(shù),當(dāng)然這些參數(shù)都是UIActionSheet中需要使用的,注意這里約定所有調(diào)用UIActionSheet的方法參數(shù)都以”kcactionsheet”開(kāi)頭。
然后在webView: shouldStartLoadWithRequest:navigationType方法中截獲以“kcactionsheet”協(xié)議開(kāi)頭的請(qǐng)求,對(duì)于這類請(qǐng)求獲得對(duì)應(yīng)參數(shù)調(diào)用UIActionSheet??匆幌峦暾a:
//// KCMainViewController.m// UIWebView//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"@interface KCMainViewController ()<UISearchBarDelegate,UIWebViewDelegate>{ UIWebView *_webView;}@end@implementation KCMainViewController#pragma mark - 界面UI事件- (void)viewDidLoad { [super viewDidLoad]; [self layoutUI]; [self request];}#pragma mark - 私有方法#pragma mark 界面布局-(void)layoutUI{ /*添加瀏覽器控件*/ _webView=[[UIWebView alloc]initWithFrame:CGRectMake(0, 20, 320, 548)]; _webView.dataDetectorTypes=UIDataDetectorTypeAll;//數(shù)據(jù)檢測(cè)類型,例如內(nèi)容中有郵件地址,點(diǎn)擊之后可以打開(kāi)郵件軟件編寫(xiě)郵件 _webView.delegate=self; [self.view addSubview:_webView];}#pragma mark 顯示actionsheet-(void)showSheetWithTitle:(NSString *)title cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitle{ UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:title delegate:nil cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:otherButtonTitle, nil]; [actionSheet showInView:self.view];}#pragma mark 瀏覽器請(qǐng)求-(void)request{ //加載html內(nèi)容 NSString *htmlStr=@"<html>/ <head><title>Kenshin Cui's Blog</title></head>/ <body style=/"color:#0092FF;/">/ <h1 id=/"header/">I am Kenshin Cui</h1>/ <p id=/"blog/">iOS 開(kāi)發(fā)系列</p>/ </body></html>"; //加載請(qǐng)求頁(yè)面 [_webView loadHTMLString:htmlStr baseURL:nil]; }#pragma mark - WebView 代理方法#pragma mark 頁(yè)面加載前(此方法返回false則頁(yè)面不再請(qǐng)求)-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if ([request.URL.scheme isEqual:@"kcactionsheet"]) { NSString *paramStr=request.URL.query; NSArray *params= [[paramStr stringByRemovingPercentEncoding] componentsSeparatedByString:@"&"]; id otherButton=nil; if (params.count>3) { otherButton=params[3]; } [self showSheetWithTitle:params[0] cancelButtonTitle:params[1] destructiveButtonTitle:params[2] otherButtonTitles:otherButton]; return false; } return true;}#pragma mark 開(kāi)始加載-(void)webViewDidStartLoad:(UIWebView *)webView{ //顯示網(wǎng)絡(luò)請(qǐng)求加載 [UIApplication sharedApplication].networkActivityIndicatorVisible=true;}#pragma mark 加載完畢-(void)webViewDidFinishLoad:(UIWebView *)webView{ //隱藏網(wǎng)絡(luò)請(qǐng)求加載圖標(biāo) [UIApplication sharedApplication].networkActivityIndicatorVisible=false; //加載js文件 NSString *path=[[NSBundle mainBundle] pathForResource:@"MyJs.js" ofType:nil]; NSString *jsStr=[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; //加載js文件到頁(yè)面 [_webView stringByEvaluatingJavaScriptFromString:jsStr];}#pragma mark 加載失敗-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{ NSLog(@"error detail:%@",error.localizedDescription); UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"系統(tǒng)提示" message:@"網(wǎng)絡(luò)連接發(fā)生錯(cuò)誤!" delegate:self cancelButtonTitle:nil otherButtonTitles:@"確定", nil]; [alert show];}@end
運(yùn)行效果:
前面無(wú)論是下載還是上傳都沒(méi)有考慮網(wǎng)絡(luò)狀態(tài),事實(shí)上實(shí)際開(kāi)發(fā)過(guò)程中這個(gè)問(wèn)題是不得不思考的,試想目前誰(shuí)會(huì)用3G或4G網(wǎng)絡(luò)下載一個(gè)超大的文件啊,因此實(shí)際開(kāi)發(fā)過(guò)程中如果程序部署到了真機(jī)上必須根據(jù)不同的網(wǎng)絡(luò)狀態(tài)決定用戶的操作,例如下圖就是在使用QQ音樂(lè)播放在線音樂(lè)的提示:
網(wǎng)絡(luò)狀態(tài)檢查在早期都是通過(guò)蘋(píng)果官方的Reachability類進(jìn)行檢查(需要自行下載),但是這個(gè)類本身存在一些問(wèn)題,并且官方后來(lái)沒(méi)有再更新。后期大部分開(kāi)發(fā)者都是通過(guò)第三方框架進(jìn)行檢測(cè),在這里就不再使用官方提供的方法,直接使用AFNetworking框架檢測(cè)。不管使用官方提供的類還是第三方框架,用法都是類似的,通常是發(fā)送一個(gè)URL然后去檢測(cè)網(wǎng)絡(luò)狀態(tài)變化,網(wǎng)絡(luò)改變后則調(diào)用相應(yīng)的網(wǎng)絡(luò)狀態(tài)改變方法。下面是一個(gè)網(wǎng)絡(luò)監(jiān)測(cè)的簡(jiǎn)單示例:
//// KCMainViewController.m// Network status//// Created by Kenshin Cui on 14-3-22.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCMainViewController.h"#import "AFNetworking.h"@interface KCMainViewController ()<NSURLConnectionDataDelegate>@end@implementation KCMainViewController#pragma mark - UI方法- (void)viewDidLoad { [super viewDidLoad]; [self checkNetworkStatus]; }#pragma mark - 私有方法#pragma mark 網(wǎng)絡(luò)狀態(tài)變化提示-(void)alert:(NSString *)message{ UIAlertView *alertView=[[UIAlertView alloc]initWithTitle:@"System Info" message:message delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles: nil]; [alertView show];}#pragma mark 網(wǎng)絡(luò)狀態(tài)監(jiān)測(cè)-(void)checkNetworkStatus{ //創(chuàng)建一個(gè)用于測(cè)試的url NSURL *url=[NSURL URLWithString:@"http://www.apple.com"]; AFHTTPRequestOperationManager *operationManager=[[AFHTTPRequestOperationManager alloc]initWithBaseURL:url]; //根據(jù)不同的網(wǎng)絡(luò)狀態(tài)改變?nèi)プ鱿鄳?yīng)處理 [operationManager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusReachableViaWWAN: [self alert:@"2G/3G/4G Connection."]; break; case AFNetworkReachabilityStatusReachableViaWiFi: [self alert:@"WiFi Connection."]; break; case AFNetworkReachabilityStatusNotReachable: [self alert:@"Network not found."]; break; default: [self alert:@"Unknown."]; break; } }]; //開(kāi)始監(jiān)控 [operationManager.reachabilityManager startMonitoring];}@end
AFNetworking是網(wǎng)絡(luò)開(kāi)發(fā)中常用的一個(gè)第三方框架,常用的網(wǎng)絡(luò)開(kāi)發(fā)它都能幫助大家更好的實(shí)現(xiàn),例如JSON數(shù)據(jù)請(qǐng)求、文件下載、文件上傳(并且支持?jǐn)帱c(diǎn)續(xù)傳)等,甚至到AFNetworking2.0之后還加入了對(duì)NSURLSession的支持。由于本文更多目的在于分析網(wǎng)絡(luò)操作原理,因此在此不再贅述,更多內(nèi)容大家可以看官方文檔,常用的操作都有示例代碼。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注