😊😊😊Alamofire專題目錄,歡迎及時反饋交流 😊😊😊git
Alamofire 目錄直通車 --- 和諧學習,不急不躁!github
很是高興,這個
Alamofire
篇章立刻也結束了!那麼這也做爲Alamofire
的終章,給你們介紹整個Alamofire
剩餘的內容,以及下載器封裝,最後總結一下!swift
這個類主要對 SystemConfiguration.framework
中的 SCNetworkReachability
相關的東西進行封裝的,主要用來管理和監聽網絡狀態的變化緩存
let networkManager = NetworkReachabilityManager(host: "www.apple.com")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
/// 網絡監控
networkManager!.listener = {
status in
var message = ""
switch status {
case .unknown:
message = "未知網絡,請檢查..."
case .notReachable:
message = "沒法鏈接網絡,請檢查..."
case .reachable(.wwan):
message = "蜂窩移動網絡,注意節省流量..."
case .reachable(.ethernetOrWiFi):
message = "WIFI-網絡,使勁造吧..."
}
print("***********\(message)*********")
let alertVC = UIAlertController(title: "網絡情況提示", message: message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "我知道了", style: .default, handler: nil))
self.window?.rootViewController?.present(alertVC, animated: true, completion: nil)
}
networkManager!.startListening()
return true
}
複製代碼
didFinishLaunchingWithOptions
NetworkReachabilityManager
對象status
來處理事務1:咱們首先來看看 NetworkReachabilityManager
的初始化安全
public convenience init?(host: String) {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
self.init(reachability: reachability)
}
private init(reachability: SCNetworkReachability) {
self.reachability = reachability
// 將前面的標誌設置爲無保留值,以表示未知狀態
self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
}
複製代碼
SCNetworkReachabilityCreateWithName
建立了 reachability
對象,這也是咱們 SystemConfiguration
下很是很是重要的類!reachability
對象,方便後面持續使用0.0.0.0
0.0.0.0地址
視爲一個特殊的 token
,它能夠監視設備的通常路由狀態,包括 IPv4和IPv6。
2:open var listener: Listener?
網絡
3:networkManager!.startListening()
開啓監聽session
這裏也是這個內容點的重點所在閉包
open func startListening() -> Bool {
// 獲取上下文結構信息
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passUnretained(self).toOpaque()
// 將客戶端分配給目標,當目標的可達性發生更改時,目標將接收回調
let callbackEnabled = SCNetworkReachabilitySetCallback(
reachability,
{ (_, flags, info) in
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
reachability.notifyListener(flags)
},
&context
)
// 在給定分派隊列上爲給定目標調度或取消調度回調
let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
// 異步執行狀態,以及通知
listenerQueue.async {
guard let flags = self.flags else { return }
self.notifyListener(flags)
}
return callbackEnabled && queueEnabled
}
複製代碼
SCNetworkReachabilityContext
的初始化,這個結構體包含用戶指定的數據和回調函數.Unmanaged.passUnretained(self).toOpaque()
就是將非託管類引用轉換爲指針SCNetworkReachabilitySetCallback
:將客戶端分配給目標,當目標的可達性發生更改時,目標將接收回調。(這也是隻要咱們的網絡狀態發生改變時,就會響應的緣由)4:self.notifyListener(flags)
咱們看看狀態處理以及回調併發
listener?(networkReachabilityStatusForFlags(flags))
在回調的時候還內部處理了 flags
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
guard isNetworkReachable(with: flags) else { return .notReachable }
var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
#if os(iOS)
if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
#endif
return networkStatus
}
複製代碼
isNetworkReachable
判斷有無網絡.reachable(.ethernetOrWiFi)
是否存在 WIFI 網絡.reachable(.wwan)
判斷蜂窩網絡網絡監聽處理,仍是很是簡單的!代碼的思路也沒有太噁心,就是經過 SCNetworkReachabilityRef
這個一個內部類去處理網絡狀態,而後經過對 flags
分狀況處理,肯定是無網絡、仍是WIFI、仍是蜂窩app
AFError
中將錯誤定義成了五個大類型// 當「URLConvertible」類型沒法建立有效的「URL」時返回。
case invalidURL(url: URLConvertible)
// 當參數編碼對象在編碼過程當中拋出錯誤時返回。
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
// 當多部分編碼過程當中的某個步驟失敗時返回。
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
// 當「validate()」調用失敗時返回。
case responseValidationFailed(reason: ResponseValidationFailureReason)
// 當響應序列化程序在序列化過程當中遇到錯誤時返回。
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
複製代碼
這裏經過對枚舉拓展了計算屬性,來直接對錯誤類型進行 if判斷
,不用在 switch
一個一個判斷了
extension AFError {
// 返回AFError是否爲無效URL錯誤
public var isInvalidURLError: Bool {
if case .invalidURL = self { return true }
return false
}
// 返回AFError是不是參數編碼錯誤。
// 當「true」時,「underlyingError」屬性將包含關聯的值。
public var isParameterEncodingError: Bool {
if case .parameterEncodingFailed = self { return true }
return false
}
// 返回AFError是不是多部分編碼錯誤。
// 當「true」時,「url」和「underlyingError」屬性將包含相關的值。
public var isMultipartEncodingError: Bool {
if case .multipartEncodingFailed = self { return true }
return false
}
// 返回「AFError」是否爲響應驗證錯誤。
// 當「true」時,「acceptableContentTypes」、「responseContentType」和「responseCode」屬性將包含相關的值。
public var isResponseValidationError: Bool {
if case .responseValidationFailed = self { return true }
return false
}
// 返回「AFError」是否爲響應序列化錯誤。
// 當「true」時,「failedStringEncoding」和「underlyingError」屬性將包含相關的值。
public var isResponseSerializationError: Bool {
if case .responseSerializationFailed = self { return true }
return false
}
}
複製代碼
AFError
錯誤處理,這個類的代碼也是很是簡單的!你們自行閱讀如下應該沒有太多疑問,這裏也就不花篇幅去囉嗦了!
extension Notification.Name {
/// Used as a namespace for all `URLSessionTask` related notifications.
public struct Task {
/// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`.
public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
/// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
/// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
/// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
}
}
複製代碼
Notification.Name
經過擴展了一個 Task
這樣的結構體,把跟 task
相關的通知都綁定在這個 Task
上,所以,在代碼中就能夠這麼使用:NotificationCenter.default.post(
name: Notification.Name.Task.DidComplete,
object: strongSelf,
userInfo: [Notification.Key.Task: task]
)
複製代碼
Notification.Name.Task.DidComplete
表達的很是清晰,通常都能知道是 task
請求完成以後的通知。不再須要噁心的字符串,須要匹配,萬一寫錯了,那麼也是一種隱藏的危機!extension Notification {
/// Used as a namespace for all `Notification` user info dictionary keys.
public struct Key {
/// User info dictionary key representing the `URLSessionTask` associated with the notification.
public static let Task = "org.alamofire.notification.key.task"
/// User info dictionary key representing the responseData associated with the notification.
public static let ResponseData = "org.alamofire.notification.key.responseData"
}
}
複製代碼
Notification
,新增了一個 Key結構體
,這個結構體用於取出通知中的 userInfo。
userInfo[Notification.Key.ResponseData] = data
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
複製代碼
Notifications
實際上是一個 Task結構體
,該結構體中定義了一些字符串,這些字符串就是所需通知的 key
,當網絡請求 DidResume、DIdSuspend、DIdCancel、DidComplete
都會發出通知。Validation
主要是用來驗證請求是否成功,若是出錯了就作相應的處理這裏的下載器筆者是基於 Alamofire(2)— 後臺下載 繼續給你們分析幾個關鍵點
//MARK: - 暫停/繼續/取消
func suspend() {
self.currentDownloadRequest?.suspend()
}
func resume() {
self.currentDownloadRequest?.resume()
}
func cancel() {
self.currentDownloadRequest?.cancel()
}
複製代碼
Request
管理 task
任務的生命週期suspend
和 resume
方法cancel
裏面調用:downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }
保存了取消時候的 resumeData
斷點續傳的重點:就是保存響應 resumeData
,而後調用:manager.download(resumingWith: resumeData)
if let resumeData = currentDownloadRequest?.resumeData {
let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let fileUrl = documentUrl?.appendingPathComponent("resumeData.tmp")
try! resumeData.write(to: fileUrl!)
currentDownloadRequest = LGDowloadManager.shared.manager.download(resumingWith: resumeData)
}
複製代碼
resumeData
download(resumingWith: resumeData)
就能夠輕鬆實現斷點續傳1:準備條件
咱們們在前面Alamofire(2)— 後臺下載處理的時候,針對 URLSession
是由要求的
background(withIdentifier:)
方法建立 URLSessionConfiguration
,其中這個 identifier
必須是固定的,並且爲了不跟 其餘App
衝突,建議這個identifier
跟應用程序的 Bundle ID
相關,保證惟一Background Sessions
,即它的生命週期跟App幾乎一致,爲方便使用,最好是做爲 AppDelegate
的屬性,或者是全局變量。2:測試反饋
OK,準備好了條件,咱們開始測試!當應用程序被用戶殺死的時候,再回來!
⚠️ 咱們驚人的發現,會報錯:load failed with error Error Domain=NSURLErrorDomain Code=-999
, 這個BUG 我但是常常看見,因而飛快定位:
urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
😲 果真應用程序會回到完成代理,你們若是細心想想也是能夠理解的:應用程序被用戶kill,也是舒服用戶取消,這個任務執行失敗啊! 😲
3:處理事務
if let error = error {
if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
LGDowloadManager.shared.resumeData = resumeData
print("保存完畢,你能夠斷點續傳!")
}
}
複製代碼
NSError
error
獲取裏面 inifo
, 再經過 key
拿到相應的 resumeData
URL
下載的時候,只要取出對應的 task
保存的 resumeData
download(resumingWith: resumeData)
完美!固然若是你有特殊封裝也能夠執行調用 Alamofire
封裝的閉包
manager.delegate.taskDidComplete = { (session, task, error) in
print("**************")
if let error = error {
if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
LGDowloadManager.shared.resumeData = resumeData
print("保存完畢,你能夠斷點續傳!")
}
}
print("**************")
}
複製代碼
問題
這裏咱們在實際開發過程當中,也會遇到各類各樣的BUG,那麼在下載的時候 APP Crash
也是徹底可能的!問題在於:咱們這個時候怎麼辦?
思考
咱們經過上面的條件,發現其實 apple
針對下載任務是有特殊處理的!我把它理解是在另外一進程處理的!下載程序的代理方法仍是會繼續執行!那麼我在直接把全部下載相關代理方法所有斷點
測試結果
// 告訴委託下載任務已完成下載
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
// 下載進度也會不斷執行
func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
複製代碼
urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
完成也會調用問題一:OK,看似感受一切都完美(不須要處理),可是錯了:咱們用戶不知道你已經在後臺執行了,他有可能下次進來有點擊下載(還有UI頁面,也沒有顯示的進度)
問題二:由於 Alamofire
的 request
沒有建立,因此沒有對應的 task
思路:重重壓力,我找到了一個很是重要的閉包(URLSession
的屬性)-- getTasksWithCompletionHandler
因而有下面這麼一段代碼
manager.session.getTasksWithCompletionHandler({ (dataTasks, uploadTasks, downloadTasks) in
print(dataTasks)
print(uploadTasks)
print(downloadTasks)
})
複製代碼
session
里正在執行的任務,咱們只須要便利找到響應的 Task
task
對應 url
保存起來url
的時候,就判斷讀取就OK,若是存在就不須要開啓新的任務,只要告訴用戶已經開始下載就OK,UI頁面處理而已func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
代理裏面匹配 downloadTask
保存進度,而後更新界面就OK!didFinishDownloadingTo
記得對下載回來的文件進行路徑轉移!首先這裏很是感謝 iOS原生級別後臺下載詳解 提供的測試總結!Tiercel2 框架一個很是強大的下載框架,推薦你們使用
downloadTask
同樣,調用相關的 session代理方法
Background Sessions
裏面全部的任務(注意是全部任務,不僅僅是下載任務)都完成後,會調用 AppDelegate
的 application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法,激活 App
,而後跟在前臺時同樣,調用相關的session代理方法
,最後再調用 urlSessionDidFinishEvents(forBackgroundURLSession:)
方法crash
或者 App被系統關閉
:當 Background Sessions
裏面全部的任務(注意是全部任務,不僅僅是下載任務)都完成後,會自動啓動App
,調用 AppDelegate的application(_:didFinishLaunchingWithOptions:)
方法,而後調用 application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法,當建立了對應的Background Sessions
後,纔會跟在前臺時同樣,調用相關的 session
代理方法,最後再調用 urlSessionDidFinishEvents(forBackgroundURLSession:)
方法crash
或者 App被系統關閉
,打開 App
保持前臺,當全部的任務都完成後才建立對應的 Background Sessions:
沒有建立 session
時,只會調用 AppDelegate的application(_:handleEventsForBackgroundURLSession:completionHandler:)
方法,當建立了對應的 Background Sessions
後,纔會跟在前臺時同樣,調用相關的 session
代理方法,最後再調用 urlSessionDidFinishEvents(forBackgroundURLSession:)
方法crash
或者 App被系統關閉
,打開 App
,建立對應的 Background Sessions
後全部任務才完成:跟在前臺的時候同樣到這裏,這個篇章就分析完畢了!看到這裏估計你也對
Alamofire
有了必定的瞭解。這個篇章完畢,我仍是會繼續更新(儘管如今掘進iOS人羣很少,閱讀量很少)但這是個人執着!但願還在iOS行業奮鬥的小夥伴,繼續加油,守的雲開見日出!💪💪💪就問此時此刻還有誰?45度仰望天空,該死!我這無處安放的魅力!