JSON 请求的 AlamoFire 异步完成处理程序
AlamoFire asynchronous completionHandler for JSON request
使用 AlamoFire 框架后,我注意到主线程上的 completionHandler 运行。我想知道下面的代码是否是在完成处理程序中创建核心数据导入任务的好习惯:
Alamofire.request(.GET, "http://myWebSite.com", parameters: parameters)
.responseJSON(options: .MutableContainers) { (_, _, JSON, error) -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
if let err = error{
println("Error:\(error)")
return;
}
if let jsonArray = JSON as? [NSArray]{
let importer = CDImporter(incomingArray: jsonArray entity: "Artist", map: artistEntityMap);
}
});
}
这真是个好问题。你的方法是完全有效的。但是,Alamofire 实际上可以帮助您进一步简化这一过程。
您的示例代码调度队列分解
在您的示例代码中,您在以下调度队列之间跳转:
- NSURLSession 调度队列
- 用于验证和序列化程序处理的 TaskDelegate 调度队列
- 用于调用完成处理程序的主调度队列
- JSON 处理的高优先级队列
- 用于更新用户界面的主调度队列(如有必要)
如您所见,您正在到处跳来跳去。让我们看一下利用 Alamofire 内部强大功能的替代方法。
Alamofire 响应调度队列
Alamofire 在其自身的低级处理中内置了一种优化方法。最终由所有自定义响应序列化程序调用的单个 response
方法支持自定义调度队列(如果您选择使用它)。
虽然 GCD 在调度队列之间跳转非常出色,但您希望避免跳转到繁忙的队列(例如主线程)。通过消除在异步处理过程中跳回主线程,您可以大大加快处理速度。以下示例演示了如何使用开箱即用的 Alamofire 逻辑来执行此操作。
Alamofire 1.x
let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)
let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
serializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { _, _, JSON, _ in
// You are now running on the concurrent `queue` you created earlier.
println("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")
// Validate your JSON response and convert into model objects if necessary
println(JSON)
// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
println("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)
Alamofire 3.x(Swift 2.2 和 2.3)
let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)
let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")
// Validate your JSON response and convert into model objects if necessary
print(response.result.value)
// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
print("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)
Alamofire 4.x (Swift 3)
let queue = DispatchQueue(label: "com.cnoon.response-queue", qos: .utility, attributes: [.concurrent])
Alamofire.request("http://httpbin.org/get", parameters: ["foo": "bar"])
.response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(Thread.current) is main thread: \(Thread.isMainThread)")
// Validate your JSON response and convert into model objects if necessary
print(response.result.value)
// To update anything on the main thread, just jump back on like so.
DispatchQueue.main.async {
print("Am I back on the main thread: \(Thread.isMainThread)")
}
}
)
Alamofire 调度队列分解
这是此方法涉及的不同调度队列的细分。
- NSURLSession 调度队列
- 用于验证和序列化程序处理的 TaskDelegate 调度队列
- 用于 JSON 处理的自定义管理器并发调度队列
- 用于更新用户界面的主调度队列(如有必要)
总结
通过消除回到主调度队列的第一个跃点,您消除了潜在的瓶颈,并且使整个请求和处理异步进行。太棒了!
话虽如此,我怎么强调熟悉 Alamofire 内部工作原理的重要性都不为过。您永远不知道什么时候可以找到真正可以帮助您改进自己的代码的东西。
只是补充@cnoon 的完美答案,如果你像我一样正在使用 ResponseObjectSerializable
你可以将此并发行为嵌入到请求扩展本身:
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
let queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT)
return response(queue: queue, responseSerializer: responseSerializer) { response in
dispatch_async(dispatch_get_main_queue()) {
completionHandler(response)
}
}
}
}
Swift 3.0 的小更新,Alamofire (4.0.1),为@cnoon 回答编辑:
let queue = DispatchQueue(label: "com.cnoon.manager-response-queue",
qos: .userInitiated,
attributes:.concurrent)
Alamofire?.request(SERVER_URL, method: .post,
parameters: ["foo": "bar"],
encoding: JSONEncoding.default,//by default
headers: ["Content-Type":"application/json; charset=UTF-8"])
.validate(statusCode: 200..<300).//by default
responseJSON(queue: queue, options: .allowFragments,
completionHandler: { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
break
case .failure(_):
print(response.result.error)
if response.result.error?._code == NSURLErrorTimedOut{
//TODO: Show Alert view on netwok connection.
}
break
}
})
使用 AlamoFire 框架后,我注意到主线程上的 completionHandler 运行。我想知道下面的代码是否是在完成处理程序中创建核心数据导入任务的好习惯:
Alamofire.request(.GET, "http://myWebSite.com", parameters: parameters)
.responseJSON(options: .MutableContainers) { (_, _, JSON, error) -> Void in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
if let err = error{
println("Error:\(error)")
return;
}
if let jsonArray = JSON as? [NSArray]{
let importer = CDImporter(incomingArray: jsonArray entity: "Artist", map: artistEntityMap);
}
});
}
这真是个好问题。你的方法是完全有效的。但是,Alamofire 实际上可以帮助您进一步简化这一过程。
您的示例代码调度队列分解
在您的示例代码中,您在以下调度队列之间跳转:
- NSURLSession 调度队列
- 用于验证和序列化程序处理的 TaskDelegate 调度队列
- 用于调用完成处理程序的主调度队列
- JSON 处理的高优先级队列
- 用于更新用户界面的主调度队列(如有必要)
如您所见,您正在到处跳来跳去。让我们看一下利用 Alamofire 内部强大功能的替代方法。
Alamofire 响应调度队列
Alamofire 在其自身的低级处理中内置了一种优化方法。最终由所有自定义响应序列化程序调用的单个 response
方法支持自定义调度队列(如果您选择使用它)。
虽然 GCD 在调度队列之间跳转非常出色,但您希望避免跳转到繁忙的队列(例如主线程)。通过消除在异步处理过程中跳回主线程,您可以大大加快处理速度。以下示例演示了如何使用开箱即用的 Alamofire 逻辑来执行此操作。
Alamofire 1.x
let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)
let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
serializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { _, _, JSON, _ in
// You are now running on the concurrent `queue` you created earlier.
println("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")
// Validate your JSON response and convert into model objects if necessary
println(JSON)
// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
println("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)
Alamofire 3.x(Swift 2.2 和 2.3)
let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)
let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")
// Validate your JSON response and convert into model objects if necessary
print(response.result.value)
// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
print("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)
Alamofire 4.x (Swift 3)
let queue = DispatchQueue(label: "com.cnoon.response-queue", qos: .utility, attributes: [.concurrent])
Alamofire.request("http://httpbin.org/get", parameters: ["foo": "bar"])
.response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(Thread.current) is main thread: \(Thread.isMainThread)")
// Validate your JSON response and convert into model objects if necessary
print(response.result.value)
// To update anything on the main thread, just jump back on like so.
DispatchQueue.main.async {
print("Am I back on the main thread: \(Thread.isMainThread)")
}
}
)
Alamofire 调度队列分解
这是此方法涉及的不同调度队列的细分。
- NSURLSession 调度队列
- 用于验证和序列化程序处理的 TaskDelegate 调度队列
- 用于 JSON 处理的自定义管理器并发调度队列
- 用于更新用户界面的主调度队列(如有必要)
总结
通过消除回到主调度队列的第一个跃点,您消除了潜在的瓶颈,并且使整个请求和处理异步进行。太棒了!
话虽如此,我怎么强调熟悉 Alamofire 内部工作原理的重要性都不为过。您永远不知道什么时候可以找到真正可以帮助您改进自己的代码的东西。
只是补充@cnoon 的完美答案,如果你像我一样正在使用 ResponseObjectSerializable
你可以将此并发行为嵌入到请求扩展本身:
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
let queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT)
return response(queue: queue, responseSerializer: responseSerializer) { response in
dispatch_async(dispatch_get_main_queue()) {
completionHandler(response)
}
}
}
}
Swift 3.0 的小更新,Alamofire (4.0.1),为@cnoon 回答编辑:
let queue = DispatchQueue(label: "com.cnoon.manager-response-queue",
qos: .userInitiated,
attributes:.concurrent)
Alamofire?.request(SERVER_URL, method: .post,
parameters: ["foo": "bar"],
encoding: JSONEncoding.default,//by default
headers: ["Content-Type":"application/json; charset=UTF-8"])
.validate(statusCode: 200..<300).//by default
responseJSON(queue: queue, options: .allowFragments,
completionHandler: { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
break
case .failure(_):
print(response.result.error)
if response.result.error?._code == NSURLErrorTimedOut{
//TODO: Show Alert view on netwok connection.
}
break
}
})