😊😊😊Alamofire thematic directory. Welcome timely feedback and exchange

  • Alamofire (1) – URLSession prerequisite skill
  • Alamofire (2) — Background download
  • Alamofire (3) — Request
  • Alamofire (4) — Details you need to know
  • Alamofire (5) — Response
  • Alamofire (6) — Multiple form uploads
  • Alamofire (7) — Safety certification
  • Alamofire (8) — Final Chapter (Network Monitoring & Notifications & Downloader Packaging)

Alamofire Directory through train — Harmonious learning, not impatient!


So glad that this Alamofire chapter is almost over! So this is the final chapter of Alamofire, to introduce you to the rest of Alamofire, as well as the downloader package, and finally to conclude!

A, NetworkReachabilityManager

. This class is mainly for SystemConfiguration framework of encapsulate SCNetworkReachability related things, mainly used to manage and monitor the change of network state

1️ one: First we use monitoring network state

let networkManager = NetworkReachabilityManager(host: "www.apple.com")

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    /// network monitoringnetworkManager! .listener = { statusin
        var message = ""
        switch status {
        case .unknown:
            message = Unknown network, please check...
        case .notReachable:
            message = "Unable to connect to network, please check..."
        case .reachable(.wwan):
            message = "Cellular mobile network, attention to save traffic..."
        case .reachable(.ethernetOrWiFi):
            message = "WIFI- Internet, go for it..."
        }
        print("* * * * * * * * * * *\(message)* * * * * * * * *")
        let alertVC = UIAlertController(title: "Network Health Alert", message: message, preferredStyle: .alert)
        alertVC.addAction(UIAlertAction(title: "I know.", style: .default, handler: nil))
        self.window? .rootViewController? .present(alertVC, animated:true, completion: nil) } networkManager! .startListening()return true
}
Copy the code
  • The usage is very simple, because for global listening, it is usually written indidFinishLaunchingWithOptions
  • createNetworkReachabilityManagerobject
  • Set the callback through the callbackstatusTo take care of business
  • Finally, be sure to turn on listening (internal key encapsulation)

2️ retail: Underlying source code analysis

1: Let’s have a look firstNetworkReachabilityManagerThe initialization

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
    // Set the preceding flag to an unreserved value to indicate unknown status
    self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)}Copy the code
  • The underlying source code inside the callSCNetworkReachabilityCreateWithNameTo create thereachabilityObjects, this is also usSystemConfigurationVery, very important class!
  • Save it in thisreachabilityObject for later continuous use
  • Set the preceding flag to an unreserved value to indicate unknown status
  • The initialization method, which also provides a default creation, monitors the address0.0.0.0
  • Accessibility of will0.0.0.0 addressAs a specialtoken, it can monitor the general routing status of the device, includingIPv4 and IPv6.

2:open var listener: Listener?

  • This is the externally supplied state callback closure

3:networkManager! .startListening()Open to monitor

This is also the point of this content point

open func startListening(a) -> Bool {
    // Get context structure information
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = Unmanaged.passUnretained(self).toOpaque()
    // Assign a client to a target that will receive a callback when the target's reachability changes
    let callbackEnabled = SCNetworkReachabilitySetCallback(
        reachability,
        { (_, flags, info) in
            let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!) .takeUnretainedValue() reachability.notifyListener(flags) }, &context )Schedule or cancel the schedule callback for the given destination on the given dispatch queue
    let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
    // Asynchronous execution status, as well as notifications
    listenerQueue.async {
        guard let flags = self.flags else { return }
        self.notifyListener(flags)
    }
    return callbackEnabled && queueEnabled
}
Copy the code
  • callSCNetworkReachabilityContextThis structure contains user-specified data and callback functions.
  • Unmanaged.passUnretained(self).toOpaque()Is to convert an unmanaged class reference to a pointer
  • SCNetworkReachabilitySetCallback: Assigns a client to a target that receives a callback when the target’s reacability changes.(This is why we respond whenever our network state changes)
  • Schedule or cancel the schedule callback for a given destination on a given dispatch queue
  • Asynchronously performs status information processing and issues notifications

4:self.notifyListener(flags)Let’s look at state handling and callbacks

  • Call thelistener? (networkReachabilityStatusForFlags(flags))It was handled internally during the callbackflags
  • It is also understandable that we need not a flag bit, but cellular, WIFI, no network!
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
}
Copy the code
  • throughisNetworkReachableCheck whether there is network
  • through.reachable(.ethernetOrWiFi)Whether there is WIFI network
  • It’s also been added on iOS.reachable(.wwan)Judge cellular network

3 ️ ⃣ : summary

Network monitoring processing, or very simple! SCNetworkReachabilityRef is an internal class to handle network status, and then determine whether there is no network, WIFI, or cellular by handling flags

3. AFError error processing

AFErrorDefines errors into five broad types

// Returned when the "URLConvertible" type cannot create a valid "URL".
case invalidURL(url: URLConvertible)
// Returns when the argument encoding object throws an error during encoding.
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
// Returns when a step in the multi-part encoding process fails.
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
// Returns when the "validate()" call fails.
case responseValidationFailed(reason: ResponseValidationFailureReason)
// Returns when the response serializer encounters an error during serialization.
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
Copy the code

Here we extend the calculation property with enumerations to work directly on error typesIf judgment, need not inswitchOne by one

extension AFError {
    // Returns whether AFError is an invalid URL error
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }
    // Returns AFError if the parameter encoding is incorrect.
    // When "true", the "underlyingError" attribute will contain the associated value.
    public var isParameterEncodingError: Bool {
        if case .parameterEncodingFailed = self { return true }
        return false
    }
    // Returns whether AFError is a multipart encoding error.
    // When "true", the "URL" and "underlyingError" attributes will contain the associated values.
    public var isMultipartEncodingError: Bool {
        if case .multipartEncodingFailed = self { return true }
        return false
    }
    // Returns whether "AFError" is a response validation error.
    // When true, the "acceptableContentTypes", "responseContentType", and "responseCode" properties will contain the associated values.
    public var isResponseValidationError: Bool {
        if case .responseValidationFailed = self { return true }
        return false
    }
    // Returns whether "AFError" is a serialization error for the response.
    // When "true", the "failedStringEncoding" and "underlyingError" properties will contain the associated values.
    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false}}Copy the code

summary

AFError error handling, this class code is also very simple! You read the following should not have too many questions, here also do not spend the length of the wordy!

Notifications & Validation

Notifications

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")}}Copy the code
  • Notification.NameBy extending oneTaskSuch a structure, with thetaskRelevant notifications are bound to this oneTaskSo, in code you can use:
NotificationCenter.default.post(
                name: Notification.Name.Task.DidComplete,
                object: strongSelf,
                userInfo: [Notification.Key.Task: task]
            )
Copy the code
  • Notification.Name.Task.DidCompleteThe expression is very clear, generally can know istaskNotification of request completion. No more disgusting strings, need to match, in case of wrong writing, that is also a hidden crisis!

Notification userinfo&key expand

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"}}Copy the code
  • Extend theNotification, a new one is addedThe Key structureThis structure is used to fetch the notificationThe userInfo.
  • useuserInfo[Notification.Key.ResponseData] = data
NotificationCenter.default.post(
    name: Notification.Name.Task.DidResume,
    object: self,
    userInfo: [Notification.Key.Task: task]
)
Copy the code
  • The essence of design is to be simpler! You can also take some ideas from this thinking and apply them to actual development: just create different architectures for your business.

summary

  • NotificationsIt’s actually aTask structure, which defines the strings that you want to notifykeyWhen requested by the networkDidResume, DIdSuspend, DIdCancel, DidCompleteWill be notified.
  • ValidationMainly used to verify whether the request is successful, if the error to do the corresponding processing

Five, download

Here the downloader author is based on Alamofire (2) – background downloads continue to give you a few key points of analysis

1️ discontinuance & cancellation

//MARK: - Pause/continue/cancel
func suspend(a) {
    self.currentDownloadRequest? .suspend() }func resume(a) {
    self.currentDownloadRequest? .resume() }func cancel(a) {
    self.currentDownloadRequest? .cancel() }Copy the code
  • Through our download transaction manager:RequestmanagementtaskThe life cycle of a task
  • Where task transactions are invokedsuspendresumemethods
  • cancelInside call:downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }Saved when cancelledresumeData

2️ discontinuation

The point of breakpoint continuation is to save the responseresumeData, and then call:manager.download(resumingWith: resumeData)

if letresumeData = currentDownloadRequest? .resumeData {let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    letfileUrl = documentUrl? .appendingPathComponent("resumeData.tmp")
    try! resumeData.write(to: fileUrl!)
    currentDownloadRequest = LGDowloadManager.shared.manager.download(resumingWith: resumeData)
}
Copy the code
  • So you can see that the most important thing about breakpoint continuation is savingresumeData
  • Then process the file path and save
  • The last calldownload(resumingWith: resumeData)You can easily achieve breakpoint continuation

3️ discount: When the application is killed by the user

1: Prepare conditions

In the previous Alamofire (2) – background download processing, the URLSession is required

  • You must use thebackground(withIdentifier:)Method to createURLSessionConfigurationAnd this oneidentifierIt has to be fixed, and in order to avoidOther AppConflict, suggest thisidentifierWith the appBundle IDRelevant, guaranteed to be unique
  • When you create an URLSession, you must pass it to the delegate
  • It must be created when the App startsBackground SessionsIn other words, its life cycle is almost the same as that of App. For convenience, it is best to be used asAppDelegateProperty, or a global variable.

2: Test feedback

OK, we’re ready for the test! When the application is killed by the user, come back!

⚠️ : Load failed with error error Domain=NSURLErrorDomain Code=-999, load failed with error Domain=NSURLErrorDomain Code=-999

urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)

😲 sure enough, the application will return to the completion agent, we can understand if we think carefully: the application was killed by the user, but also comfortable user cancel, this task failed ah! 😲

3: Deal with transactions

if let error = error {
    if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
        LGDowloadManager.shared.resumeData = resumeData
        print("Save, you can continue to break!")}}Copy the code
  • Error gets, and then converts to correspondingNSError
  • througherrorTo get insideinifoAnd then through thekeyGet the correspondingresumeData
  • Because the previous one has guaranteed simple lifecycle benefits, it can be saved when the application is launched
  • Next time I click on the sameURLWhen you download it, just pull out the correspondingtaskThe savedresumeData
  • performdownload(resumingWith: resumeData)Perfect!

Of course you can also execute closures that call the Alamofire wrapper if you have a special wrapper

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("Save, you can continue to break!")}}print("* * * * * * * * * * * * * *")}Copy the code

4️ retail: APP Crash or being shut down by the system

The problem

Here we will also encounter various bugs in the actual development process, so it is completely possible to Crash the APP when downloading! The question is: what do we do now?

thinking

We through the above conditions, found that in fact Apple for download task is special processing! I understood it to be in a different process! The agent method for downloading the application will continue! So I am directly downloading all the relevant proxy method breakpoints

The test results

// Tell the delegate that the download task is complete
func urlSession(
        _ session: URLSession,
        downloadTask: URLSessionDownloadTask,
        didFinishDownloadingTo location: URL)
// The download progress is also executed continuously
func urlSession(
        _ session: URLSession,
        downloadTask: URLSessionDownloadTask,
        didWriteData bytesWritten: Int64,
        totalBytesWritten: Int64,
        totalBytesExpectedToWrite: Int64)
Copy the code
  • When our program comes back, it runs silently in the background
  • urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)Completion is also called

Problem 1: OK, it seems that everything is perfect (there is no need to deal with it), but wrong: our users do not know that you have executed in the background, he may click download next time (there is also no progress displayed on the UI page).

Problem two: becauseAlamofirerequestIt was not created, so there is no correspondingtask

Thinking: pressure, I found a very important closure (URLSession attributes) – getTasksWithCompletionHandler then there is such a piece of code below

manager.session.getTasksWithCompletionHandler({ (dataTasks, uploadTasks, downloadTasks) in
    print(dataTasks)
    print(uploadTasks)
    print(downloadTasks)
})
Copy the code
  • This closure can listen to the currentsessionIn is performing the task, we just need to facilitate finding the responseTask
  • And then use the cachetaskThe correspondingurlSave up
  • The next time the user clicks the same buttonurlIf there is no need to open a new task, just tell the user has started to download OK, UI page processing only
  • The progress? It’s pretty easy because the proxy is going on in the background, we just need to be infunc urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)Match inside agentdownloadTaskSave the progress, and then update the interface is OK!
  • Details:didFinishDownloadingToRemember to transfer the downloaded files!

5️ one: If the application creash, but download is completed

First of all here is very grateful to iOS native level background download details provided by the test summary! Tiercel2 framework is a very powerful download framework, recommended to use

  • At the front desk: RegulardownloadTaskSame, call relatedSession proxy method
  • In the background: whenBackground SessionsIs called when all the tasks in it (note that all tasks, not just download tasks) are completeAppDelegateapplication(_:handleEventsForBackgroundURLSession:completionHandler:)Method, activationAppAnd then, as in the foreground, call the relevantSession proxy method, and finally calledurlSessionDidFinishEvents(forBackgroundURLSession:)methods
  • crashorThe App is closed by the system: whenBackground SessionsIt automatically starts when all tasks (not just download tasks) are completedApp, the callApplication of AppDelegate (_ : didFinishLaunchingWithOptions:)Method, and then callapplication(_:handleEventsForBackgroundURLSession:completionHandler:)Method when the correspondingBackground SessionsAfter that, it will call the relevant one as it did in the foregroundsessionProxy method, called lasturlSessionDidFinishEvents(forBackgroundURLSession:)methods
  • crashorThe App is closed by the systemOpen,AppHold the foreground until all tasks are completeBackground Sessions:Do not createsessionIs only calledApplication of AppDelegate (_ : handleEventsForBackgroundURLSession: completionHandler:)Method when the correspondingBackground SessionsAfter that, it will call the relevant one as it did in the foregroundsessionProxy method, called lasturlSessionDidFinishEvents(forBackgroundURLSession:)methods
  • crashorThe App is closed by the systemOpen,App, and create the correspondingBackground SessionsThen all tasks are completed: just like at the front desk

Here, this chapter analysis is finished! You probably know something about Alamofire. After this chapter is over, I will continue to update it (even though there are not many people digging iOS and not many people reading it), but this is my obsession! I hope those who are still struggling in the iOS industry will continue to come on and see the sunrise! 💪 💪 💪

Just ask who else is there right now? 45 degrees up in the sky, damn it! My charm with no place to put it!