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

问题描述 投票:0回答:1

我只想使 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) -> ())
    {


    }

}
json swift asynchronous nsurlsession httpwebresponse
1个回答
1
投票

为了将异步

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)
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.