我只想使 JSON 响应与提到的类异步。 我特别想使用 NSOperation 类中的方法。这对我来说似乎非常复杂,因为我不知道我的方法“parseResponse”的参数应该来自哪里。这是我的尝试+相关代码。
我感谢任何形式的帮助。
编辑代码和注释以澄清问题。 我知道我的旧代码已经是异步的,但我想重写它,因此新的解决方案是使用 NSOperationQueue 和 NSBlockOperation。
func jsonParser() {
let urlPath = "https://api.github.com/users/\(githubName)/repos"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
let alert = UIAlertController(title: "Error Github Name Request", message: "Error creating endpoint", preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
self.navigationController?.popToRootViewControllerAnimated(true)
}
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil);
return
}
// How would I implement this part of the code, to make the response asynchronous?
let queue = NSOperationQueue()
let o = JSONParser()
var blockOperation = NSBlockOperation { () -> Void in
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
o.parseResponse(data, response: response, error: error, completionHandler: (parsedData: [ViewControllerTableView.Repository], error: NSError) -> () )
}.resume()
}
queue.addOperation(blockOperation)
// The commented code below is my old solution. And I want to take my old
//solution and rewrite it so it actually uses NSOperationQueue and
// NSBlockOperation
/*
let request = NSMutableURLRequest(URL:endpoint)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
do {
guard let data = data else {
let alert = UIAlertController(title: "Error Github Name Request", message: "\(JSONError.NoData)", preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
self.navigationController?.popToRootViewControllerAnimated(true)
}
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil);
throw JSONError.NoData
}
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options:.AllowFragments) as? [[String: AnyObject]] else {
let alert = UIAlertController(title: "Error Github Name Request", message: "\(JSONError.ConversionFailed)", preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
self.navigationController?.popToRootViewControllerAnimated(true)
}
alert.addAction(okAction)
self.presentViewController(alert, animated: true, completion: nil);
throw JSONError.ConversionFailed
}
self.owner=json[0]["owner"]!["login"]! as! String
self.allRepos.removeAll()
for a in json {
let temp:Repository = Repository(id: (a["id"] as? Int)!,name: (a["name"] as? String), size: (a["size"] as? Int)!, watchers: (a["watchers"] as? Int), created_at: (a["created_at"] as? String)!, descr: (a["description"] as? String)!)
self.allRepos.append(temp)
}
self.tableRefresh(self.allRepos)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
*/
}
class JSONParser {
func parseResponse(data: NSData?, response:
NSURLResponse?,error:
NSError?,completionHandler: (parsedData:
[Repository],error: NSError) -> ())
{
}
}
为了将异步
URLSession
任务包装在操作中,您必须使用异步的自定义 Operation
子类。不幸的是,BlockOperation
,甚至标准的 Operation
子类,都是为了处理同步任务而设计的(即,同步任务一完成,操作就完成)。
但在本例中,我们正在处理异步任务。因此,我们必须子类化
Operation
并明确告诉它它是异步的。此外,我们必须参与 isExecuting
和 isFinished
的特殊 KVO,才能使这种异步行为正常工作。有关更多信息,请参阅并发编程指南中的配置并发执行操作。
就我个人而言,我将其包含在一个抽象的
AsynchronousOperation
类中:
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `Operation` subclass. So, to develope
/// a concurrent `Operation` subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `completeOperation()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `completeOperation()` is called.
public class AsynchronousOperation : Operation {
override public var isAsynchronous: Bool { true }
private let stateLock = NSLock()
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
return stateLock.withCriticalScope { _executing }
}
set {
willChangeValue(forKey: #keyPath(isExecuting))
stateLock.withLock { _executing = newValue }
didChangeValue(forKey: #keyPath(isExecuting))
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
return stateLock.withCriticalScope { _finished }
}
set {
willChangeValue(forKey: #keyPath(isExecuting))
stateLock.withLock { _finished = newValue }
didChangeValue(forKey: #keyPath(isExecuting))
}
}
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func completeOperation() {
if isExecuting {
executing = false
}
if !isFinished {
finished = true
}
}
override public func start() {
if isCancelled {
finished = true
return
}
executing = true
main()
}
override public func main() {
fatalError("subclasses must override `main`")
}
}
一旦掌握了这些,执行网络请求的操作就非常简单了:
/// Simple network data operation
///
/// This can be subclassed for image-specific operations, JSON-specific operations, etc.
class DataOperation : AsynchronousOperation {
var request: NSURLRequest
var session: NSURLSession
weak var downloadTask: NSURLSessionTask?
var networkCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())?
init(session: NSURLSession, request: NSURLRequest, networkCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) {
self.session = session
self.request = request
self.networkCompletionHandler = networkCompletionHandler
super.init()
}
override func main() {
let task = session.dataTaskWithRequest(request) { data, response, error in
self.networkCompletionHandler?(data, response, error)
self.completeOperation()
}
task.resume()
downloadTask = task
}
override func cancel() {
super.cancel()
downloadTask?.cancel()
}
override func completeOperation() {
networkCompletionHandler = nil
super.completeOperation()
}
}
如果您想要一个解析 JSON 的操作,您只需将其子类化即可:
class GitRepoOperation: DataOperation {
typealias ParseCompletionHandler = (String?, [Repository]?, ErrorType?) -> ()
let parseCompletionHandler: ParseCompletionHandler
init(session: NSURLSession, githubName: String, parseCompletionHandler: ParseCompletionHandler) {
self.parseCompletionHandler = parseCompletionHandler
let urlPath = "https://api.github.com/users/\(githubName)/repos"
let url = NSURL(string: urlPath)!
let request = NSURLRequest(URL: url)
super.init(session: session, request: request) { data, response, error in
guard let data = data where error == nil else {
parseCompletionHandler(nil, nil, JSONError.NoData)
return
}
do {
guard let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String: AnyObject]] else {
parseCompletionHandler(nil, nil, JSONError.ConversionFailed)
return
}
guard let owner = json.first?["owner"]?["login"] as? String else {
parseCompletionHandler(nil, nil, JSONError.MissingFields)
return
}
var repos = [Repository]()
for a in json {
guard let id = a["id"] as? Int, let size = a["size"] as? Int, let createdAt = a["created_at"] as? String, let descr = a["description"] as? String else {
parseCompletionHandler(nil, nil, JSONError.MissingFields)
return
}
repos.append(Repository(id: id, name: a["name"] as? String, size: size, watchers: a["watchers"] as? Int, created_at: createdAt, descr: descr))
}
parseCompletionHandler(owner, repos, nil)
} catch {
parseCompletionHandler(nil, nil, JSONError.ConversionFailed)
}
}
}
}
注意,我已经删除了那些强制展开运算符(
!
),而是进行可选绑定以优雅地检测丢失的字段(至少对于非可选字段)。我还添加了一个额外的错误类型,以便调用者可以区分不同类型的错误。但是,最重要的是,您可能需要更明智地使用 !
运算符。
要称呼它,你只需执行以下操作:
let operation = GitRepoOperation(session: session, githubName: name) { owner, repos, error in
guard let owner = owner, let repos = repos where error == nil else {
// handle the error however you want here
return
}
// handle `owner` and `repos` however you want here
}
queue.addOperation(operation)