国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

iOS開(kāi)發(fā)系列--網(wǎng)絡(luò)開(kāi)發(fā)

2019-11-14 19:57:14
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

概覽

大部分應(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)容:

  1. Web請(qǐng)求和響應(yīng)
    1. 使用代理方法
    2. 簡(jiǎn)化請(qǐng)求方法
    3. 圖片緩存
    4. 擴(kuò)展--文件分段下載
    5. 擴(kuò)展--文件上傳
  2. NSURLsession
    1. 數(shù)據(jù)請(qǐng)求
    2. 文件上傳
    3. 文件下載
    4. 會(huì)話
  3. UIWebView
    1. 瀏覽器實(shí)現(xiàn)
    2. UIWebView與頁(yè)面交互
  4. 網(wǎng)絡(luò)狀態(tài)

Web請(qǐng)求和響應(yīng)

使用代理方法

做過(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ā)的。

requestAndResponse

在Web開(kāi)發(fā)中主要的請(qǐng)求方法有如下幾種:

  • GET請(qǐng)求:get是獲取數(shù)據(jù)的意思,數(shù)據(jù)以明文在URL中傳遞,受限于URL長(zhǎng)度,所以傳輸數(shù)據(jù)量比較小。
  • POST請(qǐng)求:post是向服務(wù)器提交數(shù)據(jù)的意思,提交的數(shù)據(jù)以實(shí)際內(nèi)容形式存放到消息頭中進(jìn)行傳遞,無(wú)法在瀏覽器url中查看到,大小沒(méi)有限制。
  • HEAD請(qǐng)求:請(qǐng)求頭信息,并不返回請(qǐng)求數(shù)據(jù)體,而只返回請(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ì)如下圖:

 DownloadLayout

程序的實(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)行效果:

NSURLConnectionEffect

需要注意:

  1. 根據(jù)響應(yīng)數(shù)據(jù)大小不同可能會(huì)多次執(zhí)行- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data方法。
  2. URL中不能出現(xiàn)中文(例如上面使用GET傳參數(shù)時(shí),file參數(shù)就可能是中文),需要對(duì)URL進(jìn)行編碼,否則會(huì)出錯(cuò)。

簡(jiǎn)化請(qǐng)求方法

當(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格式大致如下:

WeiboJson

整個(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)行效果:

WeboUI 

可以看到使用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)行效果:

WeboEffect

在上面的方法中直接調(diào)用了SDWebImage的分類緩存方法設(shè)置圖片,這個(gè)方法可以分配另外一個(gè)線程去加載圖片(同時(shí)對(duì)于頭像還指定了默認(rèn)圖片,網(wǎng)速較慢時(shí)不至于顯示空白),圖片加載后存放在沙箱的緩存文件夾,如下圖:

WeiboImageCache

滾動(dòng)UITableView再次加載同一個(gè)圖片時(shí)SDWebImage就會(huì)自動(dòng)判斷緩存文件是否有效,如果有效就加載緩存文件,否則重新加載。SDWebImage有很多使用的方法,感興趣的朋友可以訪問(wèn)“SDWebImage Reference)”。

擴(kuò)展--文件分段下載

通過(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)行效果:

DownloadWithBlock

下載文件的生成過(guò)程:

FileGenerateProgress 分段下載的過(guò)程實(shí)現(xiàn)并不復(fù)雜,主要是需要配合后臺(tái)進(jìn)行響應(yīng)進(jìn)行操作。針對(duì)不同的開(kāi)發(fā)技術(shù),服務(wù)器端處理方式稍有差別,但是基本原理是一樣的,那就是讀取Range信息,按需提供相應(yīng)數(shù)據(jù)。

擴(kuò)展--文件上傳

在做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è)置的。常用的屬性值有:

  • application/x-www-form-urlencoded:默認(rèn)值,發(fā)送前對(duì)所有發(fā)送數(shù)據(jù)進(jìn)行url編碼,支持瀏覽器訪問(wèn),通常文本內(nèi)容提交常用這種方式。
  • multipart/form-data:多部分表單數(shù)據(jù),支持瀏覽器訪問(wèn),不進(jìn)行任何編碼,通常用于文件傳輸(此時(shí)傳遞的是二進(jìn)制數(shù)據(jù)) 。
  • text/plain:普通文本數(shù)據(jù)類型,支持瀏覽器訪問(wèn),發(fā)送前其中的空格替換為“+”,但是不對(duì)特殊字符編碼。
  • application/json:json數(shù)據(jù)類型,瀏覽器訪問(wèn)不支持 。
  • text/xml:xml數(shù)據(jù)類型,瀏覽器訪問(wèn)不支持。

要實(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)求頭:

Web_FileUpload_Header

這是發(fā)送的請(qǐng)求體內(nèi)容:

Web_FileUpload_Body

在請(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

NSURLSession

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)行文件的上傳下載。

NSURLSession

通過(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)系圖如下:

 NSURLSession_Class

數(shù)據(jù)請(qǐng)求

前面通過(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];}

會(huì)話

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ì)話:

  1. defaultSessionConfiguration:進(jìn)程內(nèi)會(huì)話(默認(rèn)會(huì)話),用硬盤(pán)來(lái)緩存數(shù)據(jù)。
  2. ephemeralSessionConfiguration:臨時(shí)的進(jìn)程內(nèi)會(huì)話(內(nèi)存),不會(huì)將cookie、緩存儲(chǔ)存到本地,只會(huì)放到內(nèi)存中,當(dāng)應(yīng)用程序退出后數(shù)據(jù)也會(huì)消失。
  3. 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_FileDownLoad

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更新。具體原理如下圖所示:transfer

當(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();            }}

UIWebView

網(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等。

瀏覽器實(shí)現(xiàn)

下面將通過(guò)一個(gè)UIWebView開(kāi)發(fā)一個(gè)簡(jiǎn)單的瀏覽器,界面布局大致如下:

WebBrowserLayout

在這個(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)行效果:

WebViewEffect 

其實(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è)面交互

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ò)程。

在iOS中操作頁(yè)面

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)行效果:

UIWebView_JavascriptEffect 

頁(yè)面中調(diào)用ObjC方法

頁(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)行效果:

WebView_JavascriptEffect2

網(wǎng)絡(luò)狀態(tài)

前面無(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è)的提示:

QQMusic

網(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)容大家可以看官方文檔,常用的操作都有示例代碼。


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 松桃| 武冈市| 永宁县| 互助| 潞城市| 鸡西市| 航空| 苏州市| 衡山县| 安国市| 蓝田县| 伊宁县| 南漳县| 酉阳| 双流县| 荃湾区| 和平县| 离岛区| 海宁市| 象州县| 贵德县| 鲁甸县| 北流市| 金堂县| 呼和浩特市| 吴川市| 昭平县| 金山区| 康保县| 隆林| 宾阳县| 荣昌县| 西贡区| 基隆市| 漠河县| 营山县| 石屏县| 若尔盖县| 巴青县| 信宜市| 文安县|