如何使用NSOperationQueue和NSBlockOperation在Swift中异步生成JSON响应?

时间:2016-07-21 22:54:47

标签: json swift asynchronous nsurlsession httpwebresponse

我想只让JSON Response与提到的类异步。 我特别想使用我在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) -> ())
    {


    }

}

1 个答案:

答案 0 :(得分:1)

为了在操作中包装异步NSURLSession任务,您必须使用异步的自定义NSOperation子类。不幸的是,NSBlockOperation,甚至是标准的NSOperation子类,都是为处理同步任务而设计的(即一旦同步任务完成,操作就会完成)。

但在这种情况下,我们正在处理异步任务。因此,我们必须子类NSOperation并明确告诉它它是异步的。此外,我们必须使用isExecutingisFinished的特殊KVO,以使此异步行为正常工作。有关更多信息,请参阅并发编程指南中的Configuring Operations for Concurrent Execution

就个人而言,我在一个抽象的AsynchronousOperation类中包含了这个:

/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation 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 : NSOperation {

    override public var asynchronous: Bool { return true }

    private let stateLock = NSLock()

    private var _executing: Bool = false
    override private(set) public var executing: Bool {
        get {
            return stateLock.withCriticalScope { _executing }
        }
        set {
            willChangeValueForKey("isExecuting")
            stateLock.withCriticalScope { _executing = newValue }
            didChangeValueForKey("isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var finished: Bool {
        get {
            return stateLock.withCriticalScope { _finished }
        }
        set {
            willChangeValueForKey("isFinished")
            stateLock.withCriticalScope { _finished = newValue }
            didChangeValueForKey("isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func completeOperation() {
        if executing {
            executing = false
        }

        if !finished {
            finished = true
        }
    }

    override public func start() {
        if cancelled {
            finished = true
            return
        }

        executing = true

        main()
    }

    override public func main() {
        fatalError("subclasses must override `main`")
    }
}

extension NSLock {

    /// Perform closure within lock.
    ///
    /// An extension to `NSLock` to simplify executing critical code.
    ///
    /// - parameter block: The closure to be performed.

    func withCriticalScope<T>(@noescape block: Void -> T) -> T {
        lock()
        let value = block()
        unlock()
        return value
    }
}

完成后,进行执行网络请求的操作非常简单:

/// 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)