😊😊😊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度仰望天空,该死!我这无处安放的魅力!