我正在尝试扩展 Landmark SwiftUI 教程 以保留数据。 我添加的代码
getDataFromFileURL
灵感来自持久数据教程.
我第一次期望json文件不会存在于用户文档目录中,程序将通过DispatchQueue.main.async从bundle中获取数据
print("1. ...")
然而,实际发生的情况是
getDataFromFileURL
似乎立即返回并且数据为零并且程序在 print("4. ...")
中填充数据
正如我在日志输出中得到的那样:
4. after getDataFromFileURL is still nil, loading landmarkData.json from bundle
4. after getDataFromFileURL is still nil, loading hikeData.json from bundle
1. loaded landmarkData.json from bundle
1. loaded hikeData.json from bundle
我怀疑这种行为是由于
DispatchQueue.global().async
是一个异步调用,将立即返回。由于我的加载函数取决于 getDataFromFileURL 的结果,处理它的最佳方法是什么?
代码:
import Foundation
import Combine
final class ModelData: ObservableObject {
@Published var landmarks: [Landmark] = load("landmarkData.json")
var hikes: [Hike] = load("hikeData.json")
var categories: [String: [Landmark]] {
Dictionary(
grouping: landmarks, by: {$0.category.rawValue})
}
var features: [Landmark] {
landmarks.filter{ $0.isFeatured }
}
@Published var profile = Profile.default
}
private func fileURL(filename: String) throws -> URL {
try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appending(path: filename)
}
private func getDataFromFileURL(filename: String, completion: @escaping (Result<Data?, Error>)->Void) {
DispatchQueue.global(qos: .background).async {
do {
let fileURL = try fileURL(filename: filename)
guard let file = try? FileHandle(forReadingFrom: fileURL) else {
DispatchQueue.main.async {
print("1. loaded \(filename) from bundle")
completion(.success(getDataFromBundle(filename)))
}
return
}
DispatchQueue.main.async {
print("2. loaded \(filename) from fileURL")
completion(.success(file.availableData))
}
} catch {
DispatchQueue.main.async {
print("3. failed to load \(filename)")
completion(.failure(error))
}
}
}
}
private func getDataFromBundle(_ filename: String) -> Data? {
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
return try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
}
func load<T: Decodable>(_ filename: String) -> T {
var data: Data?
getDataFromFileURL(filename: filename) { result in
switch result {
case .success(let _data):
data = _data
case .failure(_):
data = getDataFromBundle(filename)
}
}
if data == nil {
print("4. after getDataFromFileURL is still nil, loading \(filename) from bundle")
data = getDataFromBundle(filename)
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data!)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}