preface

Recently, I started a new project, so I hardly had time to write my own blog. Most of them were writing features (bugs), and I did not have much research. I had promised to write two articles every month, but I failed to stick to it. It’s just a little bit of a trick that the big guy can ignore.



background

The new project includes functions related to upload and download network requests. Since it is written by Swift, AlamoFire is naturally chosen (there seems to be no choice) to do the bottom layer. Normal network requests such as POST and GET are directly and foolly called AlamoFire interface

Set the universal timeout period

These two interfaces are available when making requests with Alamofire

/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
/// `method`, `parameters`, `encoding` and `headers`.
///
/// - parameter url:        The URL.
/// - parameter method:     The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding:   The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers:    The HTTP headers. `nil` by default.
///
/// - returns: The created `DataRequest`.
public func request(_ url: URLConvertible, method: Alamofire.HTTPMethod = default, parameters: Parameters? = default, encoding: ParameterEncoding = default, headers: HTTPHeaders? = default) -> Alamofire.DataRequest

/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of a URL based on the
/// specified `urlRequest`.
///
/// - parameter urlRequest: The URL request
///
/// - returns: The created `DataRequest`.
public func request(_ urlRequest: URLRequestConvertible) -> Alamofire.DataRequest
Copy the code

And that’s what we usually do when we call it

let req : URLRequest = URLRequest(url: URL(fileURLWithPath: "32"), cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10) // Use default Alamofire. Request (URL(fileURLWithPath: Request (req) let semaphore = DispatchSemaphore(value: 0)Copy the code

The first method we cannot pass timeout, the second method, we can through the incoming URLRequest to set the timeout, but we usually most of the request, a project may be in addition to some special download request all timeout is the same, so we need to write the same code many times, There are two ways to do this

  • Encapsulate the method that generates the Request. Common parameters such as timeout, header, and Request mode are written inside the method, and variable parameters such as URL and can be passed in as parameters.

  • Create Alamofire.SessionManager Use the SessionManager to set timeout times and other generic things

let networkManager : SessionManager = {
        let config : URLSessionConfiguration = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 10
        let manager = Alamofire.SessionManager.init(configuration: config)
        return manager
    }()
Copy the code

Breakpoint continuingly

Alamofire supports breakpoint continuation download, which saves half of the downloaded data locally and then continues the download through data splicing at the next startup. The usage is also very simple, just call the interface, the key is the developer to maintain the downloaded data, such as memory or hard disk, how long to save, what is the elimination strategy and so on. It’s really just two steps, breakpoint and continuation

Step 1 Breakpoint

Listen to the download interruption, after the interruption has been downloaded data for retention, I use a property to save, specific to the project we can use their own storage way, save to the hard disk or database and so on

Alamofire.download("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in return (URL(fileURLWithPath: String(describing : NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, false)[0]+"123.mp4")), [.createIntermediateDirectories, .removePreviousFile]) }.responseJSON { (response) in switch response.result { case .success: Self. tmpData = response.resumeData default: print("failed")}}Copy the code

The second step is to continue

When the download starts again, we need to continue the download based on the data of the previous step, so we call the method Alamofire

/// Creates a `DownloadRequest` using the default `SessionManager` from the `resumeData` produced from a
/// previous request cancellation to retrieve the contents of the original request and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken
/// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the
/// data is written incorrectly and will always fail to resume the download. For more information about the bug and
/// possible workarounds, please refer to the following Stack Overflow post:
///
///    - http://stackoverflow.com/a/39347461/1342462
///
/// - parameter resumeData:  The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
///                          when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for additional
///                          information.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
public func download(resumingWith resumeData: Data, to destination: Alamofire.DownloadRequest.DownloadFileDestination? = default) -> Alamofire.DownloadRequest
Copy the code

This interface requires us to pass in existing data and then download it based on the data we pass in. It allows you to re-specify the destination path if you need to

Alamofire.download(resumingWith: tmpData!)
Copy the code

It also returns a request object. We can click the syntax to get the progress, response and other information

Batch download

When we need to download many things at the same time, we often need to maintain a download queue, such as the next load of material list and so on. Alamo provides us with a download interface, but the download thread queue needs to be maintained by ourselves, which is actually a multi-threaded concurrent queue.

GCD

We naturally think of GCD, but GCD has a problem that it can not control the maximum number of concurrent files, and the queue management is not perfect, for example, we need to download 100 files, if we open up 100 threads at the same time, it is definitely not possible, let alone mobile devices support (up to 70). Even if it did, it would be too expensive. Although GCD can use semaphores for thread control, but the pausing of each thread and so on is a problem, and after all, it is a curviline way to save the country.

OperationQueue

Operation and OperationQueue are gCD-encapsulated objects, which can provide more Operation choices. Multi-threaded tasks can be implemented using methods or blocks, and other operations can be performed using inheritance, categories, etc. But the simultaneous implementation code is relatively complex. However, it does not use C language implementation like GCD, so the efficiency will be lower than GCD. However, thread control is much more flexible than GCD, which is preferred for download threads.

implementation

We encapsulate each download task as an operation. Note that Operation can’t be used directly, we need to use a subclass of it, so I’m going to use BlockOperation and its closure is the download task that needs to be executed, and then we add it to the queue and start executing the task

let op : BlockOperation = BlockOperation { [weak self] in Alamofire.download("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",  method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in return (URL(fileURLWithPath: String(describing : NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, false)[0]+"123.mp4")), [.createIntermediateDirectories, .removePreviousFile]) }.downloadProgress { [weak self] (pro) in let percent = Float(pro.completedUnitCount) / Float(pro.totalUnitCount) if count == 0 { self?.downLoadLabel.snp.remakeConstraints { (make) in make.width.equalTo(300 *  percent) make.height.equalTo(30) make.top.equalTo((self?.stopButton.snp.bottom)!) .offset(30) make.left.equalToSuperview().offset(30) } } else { self? .downLoadLabel2.snp.remakeConstraints { (make) in make.width.equalTo(300 * percent) make.height.equalTo(30) make.top.equalTo((self? .downLoadLabel.snp.bottom)!) .offset(30) make.left.equalToSuperview().offset(30) } } }.responseJSON { (response) in switch response.result { case .success: print("success") case .failure: self? .tmpData = response.resumeData default: print("failed") } } } queue.addOperation(op)Copy the code

We can set the priority, start, pause, and other properties of each Opeeation object by simply calling the interface, which will not be explained here. And then we’re going to set our queue, we’re going to set the maximum number of concurrent tasks, and you can do that depending on the situation, but I only have two downloads in the demo, so I’m going to set the maximum number of concurrent tasks to 1 so it’s one download at a time.

let queue : OperationQueue = {
        let que : OperationQueue = OperationQueue()
        que.maxConcurrentOperationCount = 1
        return que
    }()
Copy the code

Let’s run it and hit Start download

Strangely enough, we found that it was still downloading at the same time, so we tried other numbers, no matter how many they were downloading at the same time, the maximum number of threads didn’t matter at all, and then let’s look at the tasks that were joining the queue up there. Normally, each operation will be executed only after the completion of another operation, and the system’s criterion for judging the completion is that the closure of the previous operation has been completed. What we put in the closure is a download task, while the download of Alamofire is executed asynchronously. So the closure that caused the operation is done, but the download was done asynchronously in another thread, and the download is not actually complete. We just need to make sure that the code in the operation closure is executed synchronously. Alamofire is implemented based on URLSession and does not provide synchronization methods like Connection, so we use semaphore cards like this

And then it’s going to follow the queue that we set up

Some would say a simultaneous downloads will have influence, but not the first way we synchronization is a semaphore, or asynchronous essentially just download we block the current thread, the blocked thread must not the main thread (unless Alamofire developers took him back to the main thread to download, this basic impossible), And when we add the download task to an operation, it is destined not to be in the main thread. Every operation will be assigned to a place other than the main thread, so there will be no performance impact.

conclusion

Due to the lack of time, I have done so much temporarily and encountered these problems, so I write a summary. This article will continue to update and slowly share the whole network layer. It may be slow to update because the workload is a bit saturated. Thank you for your attention