One, a brief introduction
In Alamofire, in order to facilitate management and clarify division of labor, Alamofire makes a clear division of the whole request process and uniformly submits it to SessionManager for management. SessionManager is responsible for creating and managing objects such as SessionDelegate, URLSession, and URLRequest. Let’s look at an example request:
let urlStr = "http://onapp.yahibo.top/public/? s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["page":"1"."size":"20"]).responseJSON {
(response) in
switch response.result{
case .success(let json):
print("json:\(json)")
break
case .failure(let error):
print("error:\(error)")
break}}Copy the code
Request here is equivalent to a request API of Alamofire, which internally implements all required request configuration. In addition, request apis such as Download, Upload and Stream are encapsulated for direct use by users. The general request process is URL -> request -> Task -> Response. The general URL is the resource link of String type. Request is the most important part of our request. In Alamofire, the request process is subdivided into tasks. Let’s explore request as the main line to see how the framework is subdivided.
2. General configuration of URLRequest
Let’s look at the general configuration:
let url = URL.init(string: "1 protocol ://2 host address /3 one path /4 one & parameter 2")!
var request = URLRequest.init(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let postData = ["username":"hibo"."password":"123456"]
request.httpBody = try?JSONSerialization.data(withJSONObject: postData, options: [])
request.timeoutInterval = 30
request.cachePolicy = .useProtocolCachePolicy
Copy the code
A series of parameter configurations are required, and more configurations may be required according to different needs. Almost every page in the project needs network requests. For the above code amount, it must be encapsulated, extracted general code for reuse, and open configuration parameters for special needs.
3. URLRequest encapsulation
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
var originalRequest: URLRequest?
do {
originalRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = tryencoding.encode(originalRequest! , with: parameters)return request(encodedURLRequest)
} catch {
return request(originalRequest, failedWith: error)
}
}
Copy the code
Url:
Requesting a resource connectionMethod: HTTPMethod
Enumerates type parameters that set the request type defaultget
typeThe Parameters:
Request parameter, dictionary type, defaultnil
Encoding:
Support encoding format, there are three types:URLEncoding, JSONEncoding, PropertyListEncoding
The defaultURLEncoding
Headers:
The request header parameter Settings are null by default
The above are the general external parameters, which are set by users according to their requirements. The default value is provided internally. If the user does not set the default value (general attribute value), the default value will be enabled. These parameters are the attributes of the URLRequest, the main attributes in the request.
4. Parameter processing
In development, we often use GET or POST requests. A GET request whose parameters are an ampersand or/coincidence delimiter concatenated over an address, often used to retrieve data. POST requests encapsulate parameters in the body of the request and often submit data.
Let’s look at how the framework handles these parameters:
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map{$0 + "&"}????"") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") = =nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)}return urlRequest
}
Copy the code
- Returns with no arguments
URLRequest
object - If there are parameters, the parameters need to be processed separately.
GET
You have to splice,POST
Need to add add tohttpBody
In the request body percentEncodedQuery
Obtain the parameters after the domain name and concatenate them with the request parameters
The query function concatenates the parameters. As follows:
private func query(_ parameters: [String: Any]) -> String {
var components: [(String.String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\ [$0)=\ [$1)" }.joined(separator: "&")}Copy the code
- Pass in a dictionary argument
- The dictionary
key
theASCII
Sort and recombine into a tuple array queryComponents
The internal recursive operation will bekey
andvalue
Do the percent code and put it back into the tuple- through
map
Combine tuple elements to form a new array in passjoined
The concatenate () function concatenates the elements of an array&
Are separated
queryComponents
public func queryComponents(fromKey key: String, value: Any)- > [(String.String)] {
var components: [(String.String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]. "", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))
} else {
components.append((escape(key), escape("\(value)")))}}else if let bool = value as? Bool {
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
} else {
components.append((escape(key), escape("\(value)")))}return components
}
Copy the code
- Recursive operation, iterating through each layer of the data until the key-value pair is of non-dictionary and array type, starting the pair
key
andvalue
Do a percent code operation escape
Is the encoding function
A GET request
Query to concatenate all key value pairs into the form “username=hibo&password=123456”, directly concatenate request domain name, request. Url is the domain name, . Through the above urlComponents percentEncodedQuery has split out the resource path as a result, splicing connection is: host + resource path (/ test/list/s = API) + & + parameters
A POST request
Give default configuration without content-Type configuration. As follows:
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8".forHTTPHeaderField: "Content-Type")
Copy the code
Convert the concatenated query parameters to Data, which is passed to the request body httpBody. At this point, the parameters for GET or other requests are set. Once URLRequest is set up, you can create tasks to send requests. Now let’s see what we can do with the frame.
Send a request
Follow the code to find another request method:
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(originalRequest, failedWith: error)
}
}
Copy the code
There are only three steps:
-
In this method, instead of initiating a request, session and request for initiating a task are handed over to the DataRequest. As for task stratification, the manager said, I just maintain the parameters you need and take the specific tasks home to do, with a clear division of labor and clearer thinking.
-
Binding The binding returns a task and a Request so that the SessionDelegate can deliver the task. The Task delegate object is obtained from the Task in the SessionDelegate
-
Start request.resume() with manager to manage tasks
DataRequest
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = DataTaskDelegate(task: task)
self.originalTask = originalTask
case .download(let originalTask, let task):
taskDelegate = DownloadTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = UploadTaskDelegate(task: task)
self.originalTask = originalTask
case .stream(let originalTask, let task):
taskDelegate = TaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent()}}Copy the code
- Enumerations are used to configure different task agents
TaskDelegate
Divided into a number of subclasses, with different functions. The subclasses are as follows:
DataTaskDelegate DownloadTaskDelegate UploadTaskDelegate
According to different task types, the Request class determines the different processing classes, while the stream class has the base class to handle the tasks. Closures of the corresponding broker events are declared externally for passing broker messages. As follows:
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
Copy the code
These closures are implemented in the Request class or extension method and are not actually implemented but bridge to the interface implementation so that the proxy event passes directly to the interface closure.
conclusion
1, our SessionManager
httpAdditionalHeaders
Parameter configuration- create
URLRequest
- create
URLSession
URLSessionDelegate
Delegate toSessionDelegate
- Initiating a task request
2, SessionDelegate
- Implement proxy methods for all network requests and declare closures through which proxy response events are passed
- If no external closure is implemented to receive a proxy event, the proxy event is passed
task
Sign over toTaskDelegate
A specific subclass of the
3, the Request
- Responsible for task creation and retention
URLSession
The object isURLRequest
object - Responsible for the task delivery, delivery to
TaskDelegate
In the - Implement data transfer method, external set closure parameters, internal closure bridge to
TaskDelegate
In, that is, implementationTaskDelegate
Closure declared externally in - Method returns
self
Implement chain calls
4, TaskDelegate
- Create subclasses, responsible for specific task implementation
- Externally declared closures pass out broker messages
- The modified object is in
Request
The use of