如何使用 Swift 中的 OperationQueue 下载和解析 JSON
How to download and parse JSON with OperationQueue in Swift
我陷入了一个概念上简单的问题。发生的事情是解析操作在下载操作的完成处理程序完成之前执行。因此,没有要解析的数据。您可以将以下代码直接放入文件中,然后 运行 它。
如何确保在解析操作 运行s 之前完成下载?
import UIKit
let search = "https://api.nal.usda.gov/ndb/search/?format=json&q=butter&sort=n&max=25&offset=0&api_key=DEMO_KEY"
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let fetch = FetchNBDNumbersOperation()
let parse = NDBParseOperation()
// 1
let adapter = BlockOperation() { [unowned parse, unowned fetch] in
parse.data = fetch.data
}
// 2
adapter.addDependency(fetch)
parse.addDependency(adapter)
// 3
let queue = OperationQueue()
queue.addOperations([fetch, parse, adapter], waitUntilFinished: true)
}
}
class FetchNBDNumbersOperation: Operation {
var data: Data?
override func main() {
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
}
dataTask.resume()
}
}
class NDBParseOperation: Operation {
var data: Data?
var nbdNumbers = [NBDNumber]()
override func main() {
let decoder = JSONDecoder()
do {
guard let jsonData = self.data else {
fatalError("No Data")
}
let dictionary = try decoder.decode( [String: USDAFoodSearch].self, from: jsonData )
for (_, foodlist) in dictionary {
for food in foodlist.item {
print("\(food.name) \(food.ndbno) \(food.group)")
let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno)
nbdNumbers.append(nbdNumber)
}
}
} catch {
print(error.localizedDescription)
}
}
}
struct NBDNumber {
var name: String
var nbdNo: String
}
struct USDAFoodSearch: Decodable {
let q: String
let sr: String
let ds: String
let start: Int
let end: Int
let total: Int
let group: String
let sort: String
let item: [USDAFood]
struct USDAFood: Decodable {
let offset: Int //Position in Array
let group: String
let name: String
let ndbno: String
let ds: String
}
}
你太复杂了,甚至不需要使用 OperationQueue,因为数据任务将异步完成。
你可以这样做:
class FetchAndParse {
var data: Data?
var nbdNumbers = [NBDNumber]()
func fetch() {
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
self.parse()
}
dataTask.resume()
}
func parse() {
let decoder = JSONDecoder()
do {
guard let jsonData = self.data else {
fatalError("No Data")
}
let dictionary = try decoder.decode( [String: USDAFoodSearch].self, from: jsonData )
for (_, foodlist) in dictionary {
for food in foodlist.item {
print("\(food.name) \(food.ndbno) \(food.group)")
let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno)
nbdNumbers.append(nbdNumber)
}
}
print ("Finished With \(nbdNumbers.count) Items")
} catch {
print(error.localizedDescription)
}
}
}
然后你可以这样使用:
let fp = FetchAndParse()
print ("Before Fetch")
fp.fetch()
print ("After Fetch")
如果您 运行 您将看到 'Before Fetch' 和 'After Fetch' 消息都在下载和解析完成之前显示,并且解析发生在正确提取之后。
当然,您可能需要更新它,以便 class 通知某些事情一切都已完成,所以可能是完成处理程序或委托,但我会把它留给您。
在您的提取操作中,您恢复了 URLSessionDataTask
。在这一点上,操作认为它已经完成,因为它不知道其他线程上发生了什么,并且本质上不关心并发操作,并且它依赖于 (adapter
) 启动。同时 URLSessionDataTask
仍在另一个线程上执行。
来自 NSOperation
上的 Apple 文档
For non-concurrent operations, you typically override only one method:
main()
URLSessionDataTask
s 运行 并发,所以你需要做更多的工作来将它们包装在 NSOperation
中。为了包装并发操作,您需要:
...override the following methods and properties at a minimum:
start()
isAsynchronous
isExecuting
isFinished
NSOperation
docs 对此进行了相当多的详细介绍,但总而言之:您需要重写 start()
而不是 main()
并进行实施保持操作状态为最新。
你只是在使用main
操作队列的方法。
而且您没有通知操作队列有关操作状态
因为 jjatie Operation Queue
需要一个 isExecuting 和 isFinished KVC 来通知关于操作完成或正在执行的队列
我给你的建议是,在进行如此复杂的操作之前,请先阅读文档。
这是一个示例代码
class WSOperations: Operation {
private var _executing = false
private var _finished = false
private var showHUD:HUDFlag = .show
override var isExecuting: Bool {
get {
return _executing
} set {
willChangeValue(forKey: "isExecuting")
_executing = newValue
didChangeValue(forKey: "isExecuting")
}
}
override var isFinished: Bool {
get {
return _finished
} set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}
override func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
func completeOperation() {
isFinished = true
isExecuting = false
Logger.log(message: "Operation finished")
}
//Your request
request = DataManager.sharedManager.getRequest(showHUD: showHUD, success: { (success, response) in
if let t = self.finishedBLock {
t.success(success, response)
}
completeOperation()
}, failure: { (error) in
if let t = self.finishedBLock {
t.failure(error)
}
completeOperation()
})
}
override func cancel() {
super.cancel()
if isExecuting {
isFinished = true
isExecuting = false
}
request?.cancel()
}
}
希望对您有所帮助
答案在这里。 Subclass 与下面的 class 的获取操作。并告诉它操作在 Fetch Op 完成处理程序结束时完成。
class FetchNBDNumbersOperation: AsynchronousOperation {
var data: Data?
override func main() {
super.main()
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
self.state = .finished
}
dataTask.resume()
}
}
在此处找到异步子class:https://gist.github.com/Sorix/57bc3295dc001434fe08acbb053ed2bc
/// Subclass of `Operation` that add support of asynchronous operations.
/// ## How to use:
/// 1. Call `super.main()` when override `main` method, call `super.start()` when override `start` method.
/// 2. When operation is finished or cancelled set `self.state = .finished`
class AsynchronousOperation: Operation {
override var isAsynchronous: Bool { return true }
override var isExecuting: Bool { return state == .executing }
override var isFinished: Bool { return state == .finished }
var state = State.ready {
willSet {
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: oldValue.keyPath)
}
}
enum State: String {
case ready = "Ready"
case executing = "Executing"
case finished = "Finished"
fileprivate var keyPath: String { return "is" + self.rawValue }
}
override func start() {
if self.isCancelled {
state = .finished
} else {
state = .ready
main()
}
}
override func main() {
if self.isCancelled {
state = .finished
} else {
state = .executing
}
}
}
我陷入了一个概念上简单的问题。发生的事情是解析操作在下载操作的完成处理程序完成之前执行。因此,没有要解析的数据。您可以将以下代码直接放入文件中,然后 运行 它。
如何确保在解析操作 运行s 之前完成下载?
import UIKit
let search = "https://api.nal.usda.gov/ndb/search/?format=json&q=butter&sort=n&max=25&offset=0&api_key=DEMO_KEY"
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let fetch = FetchNBDNumbersOperation()
let parse = NDBParseOperation()
// 1
let adapter = BlockOperation() { [unowned parse, unowned fetch] in
parse.data = fetch.data
}
// 2
adapter.addDependency(fetch)
parse.addDependency(adapter)
// 3
let queue = OperationQueue()
queue.addOperations([fetch, parse, adapter], waitUntilFinished: true)
}
}
class FetchNBDNumbersOperation: Operation {
var data: Data?
override func main() {
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
}
dataTask.resume()
}
}
class NDBParseOperation: Operation {
var data: Data?
var nbdNumbers = [NBDNumber]()
override func main() {
let decoder = JSONDecoder()
do {
guard let jsonData = self.data else {
fatalError("No Data")
}
let dictionary = try decoder.decode( [String: USDAFoodSearch].self, from: jsonData )
for (_, foodlist) in dictionary {
for food in foodlist.item {
print("\(food.name) \(food.ndbno) \(food.group)")
let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno)
nbdNumbers.append(nbdNumber)
}
}
} catch {
print(error.localizedDescription)
}
}
}
struct NBDNumber {
var name: String
var nbdNo: String
}
struct USDAFoodSearch: Decodable {
let q: String
let sr: String
let ds: String
let start: Int
let end: Int
let total: Int
let group: String
let sort: String
let item: [USDAFood]
struct USDAFood: Decodable {
let offset: Int //Position in Array
let group: String
let name: String
let ndbno: String
let ds: String
}
}
你太复杂了,甚至不需要使用 OperationQueue,因为数据任务将异步完成。
你可以这样做:
class FetchAndParse {
var data: Data?
var nbdNumbers = [NBDNumber]()
func fetch() {
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
self.parse()
}
dataTask.resume()
}
func parse() {
let decoder = JSONDecoder()
do {
guard let jsonData = self.data else {
fatalError("No Data")
}
let dictionary = try decoder.decode( [String: USDAFoodSearch].self, from: jsonData )
for (_, foodlist) in dictionary {
for food in foodlist.item {
print("\(food.name) \(food.ndbno) \(food.group)")
let nbdNumber = NBDNumber(name: food.name, nbdNo: food.ndbno)
nbdNumbers.append(nbdNumber)
}
}
print ("Finished With \(nbdNumbers.count) Items")
} catch {
print(error.localizedDescription)
}
}
}
然后你可以这样使用:
let fp = FetchAndParse()
print ("Before Fetch")
fp.fetch()
print ("After Fetch")
如果您 运行 您将看到 'Before Fetch' 和 'After Fetch' 消息都在下载和解析完成之前显示,并且解析发生在正确提取之后。
当然,您可能需要更新它,以便 class 通知某些事情一切都已完成,所以可能是完成处理程序或委托,但我会把它留给您。
在您的提取操作中,您恢复了 URLSessionDataTask
。在这一点上,操作认为它已经完成,因为它不知道其他线程上发生了什么,并且本质上不关心并发操作,并且它依赖于 (adapter
) 启动。同时 URLSessionDataTask
仍在另一个线程上执行。
来自 NSOperation
For non-concurrent operations, you typically override only one method:
main()
URLSessionDataTask
s 运行 并发,所以你需要做更多的工作来将它们包装在 NSOperation
中。为了包装并发操作,您需要:
...override the following methods and properties at a minimum:
start()
isAsynchronous
isExecuting
isFinished
NSOperation
docs 对此进行了相当多的详细介绍,但总而言之:您需要重写 start()
而不是 main()
并进行实施保持操作状态为最新。
你只是在使用main
操作队列的方法。
而且您没有通知操作队列有关操作状态
因为 jjatie Operation Queue
需要一个 isExecuting 和 isFinished KVC 来通知关于操作完成或正在执行的队列
我给你的建议是,在进行如此复杂的操作之前,请先阅读文档。
这是一个示例代码
class WSOperations: Operation {
private var _executing = false
private var _finished = false
private var showHUD:HUDFlag = .show
override var isExecuting: Bool {
get {
return _executing
} set {
willChangeValue(forKey: "isExecuting")
_executing = newValue
didChangeValue(forKey: "isExecuting")
}
}
override var isFinished: Bool {
get {
return _finished
} set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}
override func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
func completeOperation() {
isFinished = true
isExecuting = false
Logger.log(message: "Operation finished")
}
//Your request
request = DataManager.sharedManager.getRequest(showHUD: showHUD, success: { (success, response) in
if let t = self.finishedBLock {
t.success(success, response)
}
completeOperation()
}, failure: { (error) in
if let t = self.finishedBLock {
t.failure(error)
}
completeOperation()
})
}
override func cancel() {
super.cancel()
if isExecuting {
isFinished = true
isExecuting = false
}
request?.cancel()
}
}
希望对您有所帮助
答案在这里。 Subclass 与下面的 class 的获取操作。并告诉它操作在 Fetch Op 完成处理程序结束时完成。
class FetchNBDNumbersOperation: AsynchronousOperation {
var data: Data?
override func main() {
super.main()
let url = URL(string: search)!
let urlSession = URLSession.shared
let dataTask = urlSession.dataTask(with: url) { (jsonData, response, error) in
guard let jsonData = jsonData, let response = response else {
debugPrint(error!.localizedDescription)
return
}
self.data = jsonData
print("Response URL: \(String(describing: response.url?.absoluteString))")
self.state = .finished
}
dataTask.resume()
}
}
在此处找到异步子class:https://gist.github.com/Sorix/57bc3295dc001434fe08acbb053ed2bc
/// Subclass of `Operation` that add support of asynchronous operations.
/// ## How to use:
/// 1. Call `super.main()` when override `main` method, call `super.start()` when override `start` method.
/// 2. When operation is finished or cancelled set `self.state = .finished`
class AsynchronousOperation: Operation {
override var isAsynchronous: Bool { return true }
override var isExecuting: Bool { return state == .executing }
override var isFinished: Bool { return state == .finished }
var state = State.ready {
willSet {
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: oldValue.keyPath)
}
}
enum State: String {
case ready = "Ready"
case executing = "Executing"
case finished = "Finished"
fileprivate var keyPath: String { return "is" + self.rawValue }
}
override func start() {
if self.isCancelled {
state = .finished
} else {
state = .ready
main()
}
}
override func main() {
if self.isCancelled {
state = .finished
} else {
state = .executing
}
}
}