[toc]
1 URLSession的使用
1.1 發(fā)起一個簡單的請求
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil {
print("請求成功\(String(describing: response))" )
}
}.resume()
步驟:
1.創(chuàng)建URLSession實(shí)例。
2.創(chuàng)建并添加URLSessionTask實(shí)例。
3.resume()方法開啟URLSessionTask。
1.2 復(fù)雜一點(diǎn)的用法
// 創(chuàng)建URLSessionDelegate并實(shí)現(xiàn)代理方法。
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// ...
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// ...
}
func tesURLSession() {
// 1.創(chuàng)建URLSessionConfiguration
let configurationEphemeral = URLSessionConfiguration.ephemeral
// 3.根據(jù)configure、delegate、delegate執(zhí)行的隊(duì)列創(chuàng)建URLSession實(shí)例
let session = URLSession.init(configuration: configurationEphemeral, delegate: self, delegateQueue: OperationQueue.main)
// 4.創(chuàng)建URLSessionDelegate并實(shí)現(xiàn)代理方法。
// 5.resume()方法開啟URLSessionTask
session.downloadTask(with: url).resume();
}
步驟:
1.創(chuàng)建URLSessionConfiguration。
2.創(chuàng)建URLSessionDelegate。
3.根據(jù)configure、delegate、delegate執(zhí)行的隊(duì)列創(chuàng)建URLSession實(shí)例。
4.創(chuàng)建并添加URLSessionTask實(shí)例。
5.resume()方法開啟URLSessionTask。
2 從URLConnection到URLSession
2.1 Connect與Session
先看看官方文檔對于URLSession的第一句描述。
NSURLSession is a replacement API for NSURLConnection。
也就是說Apple推出URLSession就是用來替換NSURLConnection。那么為什么要搞出來個URLSession去代替NSURLConnection而不是在NSURLConnection上去迭代?
我們先理解一下session和connect的直觀含義,前者是會話、后者是連接,做過數(shù)據(jù)庫、通信協(xié)議相關(guān)開發(fā)和研究工作的小伙伴都能理解這二者的區(qū)別。
我們先看看數(shù)據(jù)庫訪問中二者的區(qū)別(以下摘自 Oracle 9i &10g 編程藝術(shù)):
連接(connection):連接是從客戶到Oracle 實(shí)例的一條物理路徑。連接可以在網(wǎng)絡(luò)上建立,或者通過IPC機(jī)制建立。通常會在客戶進(jìn)程與一個專用服務(wù)器或一個調(diào)度器之間建立連接。
會話(session):會話是實(shí)例中存在的一個邏輯實(shí)體。這就是你的會話狀態(tài)(session state),也就是表示特定會話的一組內(nèi)存中的數(shù)據(jù)結(jié)構(gòu).提到"數(shù)據(jù)庫連接"時(shí),大多數(shù)人首先想到的就是“會話”。你要在服務(wù)器中的會話上執(zhí)行SQL、提交事務(wù)和運(yùn)行存儲過程。
一般來說,會話=連接+狀態(tài),一個連接可以提供給多個會話,而一個會話也有可能利用多個連接。
有小伙伴會說,http是無狀態(tài)的,所以只有http連接一說,沒有http會話一說。確實(shí),早期http是無狀態(tài)的,后來為了提高h(yuǎn)ttp工作的效率,加入了cookie/session機(jī)制,cookie/session保存http的狀態(tài),http會話由此誕生。
據(jù)此,Apple將他們設(shè)備里的一個http請求當(dāng)做一個http會話,或者說是一個URLSession是沒有任務(wù)問題的。
2.2 從URLConnection到URLSession的轉(zhuǎn)變與http2.0是不是有聯(lián)系
從URLConnection到URLSession的轉(zhuǎn)變與http2.0是不是有聯(lián)系呢?我們先看看兩個時(shí)間點(diǎn):
ios7.0發(fā)布時(shí)間 2013.09
http2.0草案測試版本發(fā)布時(shí)間 2013.08
協(xié)議的目標(biāo):
異步多路復(fù)用
頭部壓縮
請求/響應(yīng)管線化
重點(diǎn)看看這個異步多路復(fù)用,不了解的小伙伴可以去這篇博客了解一下,淺析HTTP/2的多路復(fù)用。
我這里簡單的說一下,http1.1搞出來個長連接,主要是為了解決http1.0發(fā)起一個http請求要重新通過tcp握手協(xié)議建立tcp通道的資源浪費(fèi)問題。但是http1.1又有個問題:串行的文件傳輸效率低、服務(wù)器連接數(shù)過多壓力過大問題。于是在http2.0就搞出了個多路復(fù)用。細(xì)節(jié)小伙伴可以去看一下上面的文章和其他的資料。
回到我們的問題:從URLConnection到URLSession的轉(zhuǎn)變與http2.0是不是有聯(lián)系呢?
以前我們對于一個http請求,就說是一個connection顯然是合適的。但是在http2.0中,瀏覽器或者客戶端對一個域名發(fā)起的所有http請求都用的是同一個http連接,也就是說肯定不會創(chuàng)建連接,那么發(fā)起一個請求叫URLConnection顯示是不合適的。
那么http2.0發(fā)起一個http請求用什么合適呢?Apple選擇的是建立會話Session,發(fā)起一個http請求就是發(fā)起一個會話,完成一個或多個任務(wù)。
3 URLSession-Configuration
3.1 Configrantion是什么
翻閱文檔看看。(以下引文均載自官方文檔)
Overview
An URLSessionConfiguration object defines the behavior and policies to use when uploading and downloading data using an URLSession object. When uploading or downloading data, creating a configuration object is always the first step you must take. You use this object to configure the timeout values, caching policies, connection requirements, and other types of information that you intend to use with your URLSession object.
It is important to configure your URLSessionConfiguration object appropriately before using it to initialize a session object. Session objects make a copy of the configuration settings you provide and use those settings to configure the session. Once configured, the session object ignores any changes you make to the URLSessionConfiguration object. If you need to modify your transfer policies, you must update the session configuration object and use it to create a new URLSession object.
Overview中關(guān)于URLConfigrantion的描述有以下幾個重要信息:
1、URLSessionConfiguration對象定義了使用URLSession對象上傳和下載數(shù)據(jù)時(shí)使用的行為和策略。
2、用URLSessionConfiguration對象來為URLSession對象配置超時(shí)值、緩存策略、連接需求和其他類型的信息。
3、創(chuàng)建URLSession必須創(chuàng)建配置URLSessionConfiguration。并且一旦創(chuàng)建不可修改,如果想更改只能重新創(chuàng)建一個URLSession。
Note
In some cases, the policies defined in this configuration may be overridden by policies specified by an NSURLRequest object provided for a task. Any policy specified on the request object is respected unless the session’s policy is more restrictive. For example, if the session configuration specifies that cellular networking should not be allowed, the NSURLRequest object cannot request cellular networking.
該注意點(diǎn)提到任務(wù)中的NSURLRequest可能會覆蓋配置中定義的策略。某些策略是Configuration指定的,不能被NSURLRequest覆蓋。例如,如果會話配置指定不允許蜂窩網(wǎng)絡(luò),則NSURLRequest對象無法請求蜂窩網(wǎng)絡(luò)。
關(guān)于Types of Session Configurations
The behavior and capabilities of a URL session are largely determined by the kind of configuration used to create the session.
The singleton shared session (which has no configuration object) is for basic requests. It’s not as customizable as sessions that you create, but it serves as a good starting point if you have very limited requirements. You access this session by calling the shared class method. See that method’s discussion for more information about its limitations.
Default sessions behave much like the shared session (unless you customize them further), but let you obtain data incrementally using a delegate. You can create a default session configuration by calling the default method on the URLSessionConfiguration class.
Ephemeral sessions are similar to default sessions, but they don’t write caches, cookies, or credentials to disk. You can create an ephemeral session configuration by calling the ephemeral method on the URLSessionConfiguration class.
Background sessions let you perform uploads and downloads of content in the background while your app isn’t running. You can create a background session configuration by calling the backgroundSessionConfiguration(_:) method on the URLSessionConfiguration class.
Configurations主要有三種模式:
- Default sessions:這是一個默認(rèn)模式,其他相關(guān)配置會取默認(rèn)項(xiàng)。它會將緩存、cookie或者憑據(jù)寫入磁盤。它有一個磁盤緩存。
- Ephemeral sessions:臨時(shí)會話模式。和Default模式類似。只是沒有磁盤緩存,不會將緩存、cookie或憑據(jù)寫入磁盤。
- Background sessions:后臺會話允許您在應(yīng)用程序不運(yùn)行時(shí)在后臺執(zhí)行內(nèi)容的上載和下載??梢酝ㄟ^調(diào)用URLSessionConfiguration類上的backgroundSessionConfiguration(_:)方法來創(chuàng)建后臺會話配置。 這里有些問題可以通過調(diào)用URLSessionConfiguration類上的backgroundSessionConfiguration(_:) 已經(jīng)廢棄了,現(xiàn)在用background(withIdentifier: String) 。
在1.1中使用的URLSession單例,它的Configurations是default模式。
測試Default、和Ephemeral模式
// default - 默認(rèn),有持久緩存器,保存
let configurationDefault = URLSessionConfiguration.default
// ephemeral - 短暫的,無持久緩存器,
let configurationEphemeral = URLSessionConfiguration.ephemeral
print("沙盒大?。篭(String(describing: configurationDefault.urlCache?.diskCapacity))")
print("內(nèi)存大小:\(String(describing: configurationDefault.urlCache?.memoryCapacity))")
print("沙盒大?。篭(String(describing: configurationEphemeral.urlCache?.diskCapacity))")
print("內(nèi)存大?。篭(String(describing: configurationEphemeral.urlCache?.memoryCapacity))")
// 打印結(jié)果
沙盒大小:Optional(10000000)
內(nèi)存大?。篛ptional(512000)
沙盒大小:Optional(0)
內(nèi)存大?。篛ptional(512000)
打印結(jié)果可以看出ephemeral是沒有磁盤緩存的。小伙伴們也可以去模擬器的tmp文件夾下觀察相關(guān)緩存的變換和區(qū)別。
創(chuàng)建一個后臺下載
let date:Date = Date.init(timeIntervalSinceNow: 0)
let timeInterval:TimeInterval = date.timeIntervalSince1970
// 后臺下載,這里我們以時(shí)間戳作為標(biāo)識
let configurationBackground = URLSessionConfiguration.background(withIdentifier: "\(timeInterval)")
// 創(chuàng)建session
let session = URLSession.init(configuration: configurationBackground, delegate: self, delegateQueue: OperationQueue.main)
// resume啟動線程
session.downloadTask(with: url).resume();
// 配置delegate略
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("didFinishDownloadingTo")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print("didFinishDownloadingTo")
}
有兩點(diǎn)需要注意:
- 要想后臺模式生效必須在Appdelegate中實(shí)現(xiàn)application:handleEventsForBackgroundURLSession:completionHandler:方法。
- 后臺模式下并不是所有的URLSession代理方法都會回調(diào)。
比如上面的下載模式下func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)方法和optional func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64)會回調(diào)但是optional func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)則不會回調(diào)。
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
print("handleEventsForBackgroundURLSession")
}
3.2 URLSessionConfiguration屬性
常規(guī)屬性
identifier:配置對象的后臺標(biāo)識符。
httpAdditionalHeaders:與請求一起發(fā)送的附加頭文件的字典。
不要對以下頭進(jìn)行更改:Authorization、Connection、Host、Proxy-Authenticate、Proxy-Authorization、WWW-Authenticate。
Content-Length這個頭字段會自動計(jì)算出來,不需要我們自己設(shè)置。
這個配置的值如果和URLRequest對象里設(shè)置的header有相同的key,則會被后者覆蓋。networkServiceType:網(wǎng)絡(luò)請求類型。默認(rèn)是NSURLRequest.NetworkServiceType.default。
通過提供準(zhǔn)確的信息,您可以提高系統(tǒng)優(yōu)化平衡電池壽命、性能和其他注意事項(xiàng)的能力。大多數(shù)情況下用NSURLRequest.NetworkServiceType.default。類型有default、voip、video、background、voice、callSignaling(請求時(shí)呼叫信號)、responsiveData(用戶迫切等待的數(shù)據(jù))。
設(shè)置網(wǎng)絡(luò)請求類型究竟有多大提升呢?以后再研究。allowsCellularAccess:確定是否通過蜂窩網(wǎng)絡(luò)進(jìn)行連接。默認(rèn)是ture。
timeoutIntervalForRequest:等待附加數(shù)據(jù)時(shí)使用的超時(shí)時(shí)間間隔。默認(rèn)60s。
這個超時(shí)是一次TCP請求發(fā)起到接收的超時(shí)時(shí)間設(shè)置,如果超過該時(shí)間則發(fā)起超時(shí)重傳機(jī)制,要求對方再傳一次,上一個超時(shí)數(shù)據(jù)到來也不會接收。一般不會修改該值。timeoutIntervalForResource:允許資源請求占用的最大時(shí)間。默認(rèn)是7天。
一般性數(shù)據(jù)請求會設(shè)置該值(比如請求用戶信息等需要停留在頁面等待數(shù)據(jù)要設(shè)置比較小的值),而后臺上傳或者下載其他任務(wù)要根據(jù)實(shí)際情況慎重選擇該值。如果超時(shí),任務(wù)會廢棄并啟動超時(shí)處理機(jī)制,超時(shí)處理需要我們自己來定義處理(一般三方網(wǎng)絡(luò)框架會友好的暴露該超時(shí)回調(diào))。sharedContainerIdentifier:后臺URL會話中的文件下載到的共享容器的標(biāo)識符。一般不設(shè)置該值。
關(guān)于應(yīng)用擴(kuò)展的的內(nèi)容和共享容器設(shè)置可以參看這篇官方文檔。
以后有時(shí)間再詳述ios應(yīng)用擴(kuò)展相關(guān)內(nèi)容。waitsForConnectivity:一個布爾值指示會話是否應(yīng)等等連接變?yōu)榭捎没蛘吡⒓词?。默認(rèn)是false。
對于一些需要耗時(shí)的任務(wù),比如wifi模式上傳一個小視頻的后臺任務(wù),蜂窩網(wǎng)絡(luò)不予執(zhí)行,我們?yōu)榱耸褂脩艋謴?fù)網(wǎng)絡(luò)后繼續(xù)上傳一般會將這個值設(shè)置為YES。
需要注意的是,這個屬性ios11及以上才支持。
對于網(wǎng)絡(luò)丟失的處理,官方給出了相關(guān)的文檔
設(shè)置Cookie策略
httpCookieAcceptPolicy:決定何時(shí)應(yīng)該接受Cookie的策略性常數(shù)。默認(rèn)值是HTTPCookie.AcceptPolicy.onlyFromMainDocumentDomain。
HTTPCookie.AcceptPolicy可選值有always、never、onlyFromMainDocumentDomain,如果要自行解析,設(shè)置為HTTPCookie.AcceptPolicy.never,然后從allHeaderFields用cookies(withResponseHeaderFields:for:) 方法解析出cookies。httpShouldSetCookies:確定請求是否應(yīng)該包好來自Cookie存儲的Cookie。默認(rèn)是true。
httpCookieStorage:管理cookie存儲的單一對象(共享實(shí)例)。
一般在對接多個服務(wù)時(shí)需要解析響應(yīng)的cookie,這時(shí)候就根據(jù)domain和name來解析相應(yīng)的cookie。
設(shè)置安全策略
tlsMaximumSupportedProtocol: 次會話中進(jìn)行連接時(shí)客戶端應(yīng)請求的最大TLS協(xié)議版本。
tlsMinimumSupportedProtocol:協(xié)議協(xié)商期間應(yīng)該接受的最小TLS協(xié)議。
urlCredentialStorage:提供身份驗(yàn)證憑據(jù)的憑證存儲。
(1)根據(jù)configuration的這個屬性來確定會話內(nèi)任務(wù)使用的憑據(jù)存儲對象。
(2)如果不希望使用憑據(jù)存儲,將此屬性設(shè)置為nil。
(3)對于default 和 background sessions,默認(rèn)值是共享憑據(jù)存儲對象。
(4)對于ephemeral sessions,默認(rèn)值是一個私有憑據(jù)存儲對象,該對象只在內(nèi)存中存儲數(shù)據(jù),當(dāng)您的會話無效時(shí)將銷毀該對象。
urlCredentialStorage涉及到身份認(rèn)證、認(rèn)證挑戰(zhàn)類容。后續(xù)會有詳細(xì)篇幅介紹。
設(shè)置緩存策略
urlCache:用于向會話中的請求提供緩存響應(yīng)的URL緩存。
(1)configuration根據(jù)urlCache來確定會話內(nèi)任務(wù)使用的URL緩存對象。
(2)若要禁用緩存,請將此屬性設(shè)置為nil。
(3)default sessions默認(rèn)共享URL緩存對象。
(4)background sessions默認(rèn)緩存對象為nil。
(5)ephemeral sessions只在內(nèi)存中存儲數(shù)據(jù)的私有緩存對象。當(dāng)會話失效時(shí),該對象銷毀。requestCachepolicy:一個預(yù)定義常量,用于確定何時(shí)從緩存中返回響應(yīng)。
(1)configuration根據(jù)requestCachepolicy的值來確定會話內(nèi)任務(wù)使用的請求緩存策略。
(2)CachePolicy指定緩存策略是否依賴于過期時(shí)間、年齡、是否應(yīng)該完全禁用緩存以及是否應(yīng)該與服務(wù)器聯(lián)系以確定自上次請求依賴內(nèi)容是否發(fā)生了變更。
(3)默認(rèn)值是NSURLRequest.CachePolicy.useProtocolCachePolicy。
支持后臺轉(zhuǎn)移
- sessionSendsLauchEvents:一個bool,只是在傳輸完成時(shí)是否應(yīng)該在后頭繼續(xù)或啟動應(yīng)用程序。
- isDiscretionary:一個bool用于確定是否可以根據(jù)系統(tǒng)的判斷來調(diào)度后臺任務(wù)以獲得最佳性能。
支持自定義協(xié)議
- protocolClasses:在會話中處理請求的額外協(xié)議子類的數(shù)組。
- URLProtocol:一個NSURLProtocol對象處理加載協(xié)議特定的URL數(shù)據(jù)。在NSURLProtocol類本身是一個抽象類,可以為特定URL方案的URL處理基礎(chǔ)設(shè)施。您可以為您的盈盈支持的任何自定義協(xié)議或者URL方案創(chuàng)建子類。
支持多路TCP
- multipathServiceType:指定用于通過Wi-Fi和蜂窩接口傳輸數(shù)據(jù)的多路徑TCP連接策略的服務(wù)類型。
- URLSessionConfiguration.MultipathServiceType:指定多路徑TCP使用的服務(wù)類型的常量。
設(shè)置HTTP策略和代理屬性
- httpMaximumConnectionsPerHost:公式連接到給定主機(jī)的最大數(shù)量。
- httpShouUsePipelining: 一個bool值,用于確定會話是否應(yīng)使用http流水線。
- connectionProxyDictionary:包含有關(guān)在此會話中使用的代理信息的字典。
4 URLSessionTask
- URLSessionTask。
為具體的網(wǎng)絡(luò)任務(wù)
- URLSessionUploadTask:上傳硬盤中的文件到服務(wù)器,一般是Http post或put。
- URLSessionDownloadTask:從遠(yuǎn)處服務(wù)器下載文件到臨時(shí)文件位置。
- URLSessionStreamTask:通過流文件傳輸。
5 URLSessionDelegate
設(shè)置URLSession回調(diào)代理。對于不同的Task使用相應(yīng)類型的Delegate的。
delegate的繼承關(guān)系URLSessionDelegate <- URLSessionTaskDelegate。URLSessionDataDelegate、URLDownLoadDelegate、URLSessionStreamDelegate等繼承自URLSessionTaskDelegate。
- URLSessionDelegate:
- URLSessionTaskDelegate:
- URLSessionDataDelegate:
- URLDownLoadDelegate:
- URLSessionStreamDelegate:
6 NSURLRequestCachePolicy
NSURLRequestUseProtocolCahcePolicy。
(1)如果一個NSCachedURLResponse對于請求并不存在,數(shù)據(jù)將會從源端獲取。
(2)如果請求擁有一個緩存的響應(yīng),那么URL加載系統(tǒng)會檢查這個響應(yīng)來決定,如果它指向內(nèi)容必須重新生效的話。
(3)如果內(nèi)容必須重新生效,將建立一個連向源端的鏈接來查看內(nèi)容是否發(fā)生變換。鍵入內(nèi)容沒有變化,那么響應(yīng)就從本地緩存返回?cái)?shù)據(jù)。
(4)如果內(nèi)容發(fā)生變化,那么數(shù)據(jù)將從源端獲取。NSURLRequestReloadIgnoringLocalCacheData
(1)加載源數(shù)據(jù),不適用本地緩存數(shù)據(jù)。NSURLRequestReloadIgnoringLocalAndRemoteCacheData
(1)本地緩存數(shù)據(jù)、代理和其他中介都要護(hù)士他們的緩存,直接加載源數(shù)據(jù)。NSURLRequestReloadIgnoringCacheData=NSURLRequestReloadIgnoringLocalCacheDate
NSURLRequestReturnCacheDataElseLoad
(1)指定已緩存數(shù)據(jù)來響應(yīng)請求,不管他的生命時(shí)長和過期時(shí)間。
(2)如果在緩存中沒有已存數(shù)據(jù)來響應(yīng)請求的話,數(shù)據(jù)從源端加載。-
NSURLRequestReturnCacheDataDontLoad
(1)指定已緩存的數(shù)據(jù)來響應(yīng)請求,不管生命時(shí)長和過期時(shí)間。
(2)如果在緩存中沒有已存數(shù)據(jù)來響應(yīng)URL加載請求,不去嘗試從源端加載數(shù)據(jù),此時(shí)任務(wù)加載請求失敗。這個常量指定一個類似于離線模式的行為。一般很少使用該模式
NSURLRequestReloadRevalidatiingCacheData
如果已存的緩存數(shù)據(jù)被源端確認(rèn)為有效(未過期、生命時(shí)長允許等)則允許使用緩存數(shù)據(jù)響應(yīng)請求,否則從源段加載數(shù)據(jù)。