Предполагается, что SwiftUI упрощает работу — я немного расстроен, так как несколько недель работаю над URLSession+JSONDecoder, мне действительно нужна помощь!
У меня есть функция загрузки данных JSON из файла в Swift, и она работает так, как ожидалось. Я скопировал / вставил функцию и обновил ее, чтобы получить данные через API, однако я получаю ошибку времени компиляции: «Неожиданное непустое возвращаемое значение в функции void». Является ли мой подход неправильным, чтобы использовать функцию для JSON через Интернет?
JSON-ответ:
{
"T":"CSU",
"v":468303,
"vw":1.2838,
"o":1.31,
"c":1.24,
"h":1.38,
"l":1.2001,
"t":1607374800000,
"n":994
}
struct Root2: Codable {
var T: String
var v: Double
var vw: Double
var o: String
var c: String
var h: Double
var l: Double
var t: Double
}
Эта файловая функция работает так, как ожидалось:
let symbolData: [Root2] = load("symbolData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
Для веб-версии я получаю ошибку времени компиляции: «Неожиданное непустое возвращаемое значение в функции void». Строка: return try decoder.decode(T.self, from: data)
func loadURL<T: Decodable>() -> T {
guard let url = URL(string: """)
else {
fatalError("Invalid URL in main bundle.")
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
do {
if let data = data {
let stringData = String(decoding: data, as: UTF8.self)
print("1 Fetched: \(url)")
print("2 Response: \(stringData)")
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
}
}
catch {
fatalError("Couldn't parse as :\n\(error)")
}
}.resume()
}
Рабочая версия после помощи Лео!
class Manager: ObservableObject {
@Published var symbols: [Symbol] = []
func loadURL<T: Decodable>(using decoder: JSONDecoder = .msSince1970, completion: @escaping ((T?, Error?) -> Void)) {
let url = URL(string: """)!
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Response:", String(data: data, encoding: .utf8) ?? "")
_ = Data("""
[
{
"open": {
"price": 124.02,
"time": 1657105851499
},
"close": {
"price": 124.96,
"time": 1618647822184
},
"high": 124.64,
"low": 124.65,
"volume": 75665274,
"symbol": "AAPL"
}
]
""".utf8)
do {
completion(try decoder.decode(T.self, from: data), nil)
//completion(try decoder.decode(T.self, from: tempDataForTesting), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
Посмотрите документацию для URLSession.dataTask, замыкание возвращает Void, это ничто, поэтому вы ничего не можете вернуть из него. Прочитайте дубликат ссылки в предыдущем вопросе.
Привет Дэвид, спасибо за ваше сообщение. Возможно, я не понимаю подход stackoverflow, так как я относительно новичок. Тем не менее, я считаю, что публикация вопроса о разнице между декодированием JSON с использованием файла и веб-версией быстрого JSONDecoder является интересным вопросом для сообщества. Вы бы не согласились?
Разница в том, что URLSession
работает асинхронно, в отличие от Data(contentsOf
. Этот вопрос задавался много-много раз.
Основываясь на отзывах, я добавил вторую версию функции выше в вопросе. Теперь я присваиваю результаты .decode и возвращаю переменную foo (также удалил дженерики, так как это излишне усложняло функцию). Теперь выскакивает ошибка, что переменная присваивается до инициализации.
Вы не можете ждать, пока асинхронная задача завершится и вернет результат. Что вам нужно, так это обработчик завершения. Вам также нужно будет явно установить результирующий тип, если вы не передадите результирующий тип своему методу декодирования, и вам нужно вызвать возобновление, чтобы запустить задачу данных сеанса URL-адреса:
import SwiftUI
struct ContentView: View {
@ObservedObject var manager = Manager()
@State var string: String = "Hello, world!"
var body: some View {
Text(manager.symbol)
.padding()
.onAppear {
manager.load(symbol: manager.symbol) { (symbols: [Symbol]?, error: Error?) in
guard let symbols = symbols else {
print("error:", error ?? "")
string = "JSON could not be parsed"
return
}
for symbol in symbols {
print(symbol.open.price)
print(symbol.open.time)
print(symbol.close.price)
print(symbol.close.time)
print(symbol.high)
print(symbol.low)
print(symbol.volume)
print(symbol.symbol)
DispatchQueue.main.async {
manager.symbols = symbols
}
}
string = "JSON was successufly parsed"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class Manager: ObservableObject {
@Published var symbols: [Symbol] = []
@Published var symbol: String = "IBM"
func load<T: Decodable>(symbol: String, using decoder: JSONDecoder = .msSince1970, completion: @escaping ((T?, Error?) -> Void)) {
guard let url = URLComponents(symbol: symbol).url else {
completion(nil, URL.Error.invalidURL)
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("ops")
completion(nil, error)
return
}
print("1 Fetched: \(url)")
print("2 Symbol: \(symbol)")
print("3 Response:", String(data: data, encoding: .utf8) ?? "")
do {
completion(try decoder.decode(T.self, from: data), nil)
} catch {
completion(nil, error)
}
}.resume()
}
}
struct Symbol: Codable {
let open, close: Price
let high, low: Double
let volume: Int
let symbol: String
}
struct Price: Codable {
let price: Double
let time: Date
}
extension JSONDecoder {
static let msSince1970: JSONDecoder = {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
return decoder
}()
}
extension URLComponents {
init(scheme: String = "https",
host: String = "sandbox.iexapis.com",
path: String = "/stable/stock/market/ohlc",
symbol: String,
token: String = "YOUR_API_TOKEN") {
self.init()
self.scheme = scheme
self.host = host
self.path = path
self.queryItems = [URLQueryItem(name: "symbols", value: symbol),
URLQueryItem(name: "token", value: token)]
}
}
extension URL {
enum Error: String, Swift.Error {
case invalidURL = "Invalid URL"
}
}
Это напечатает
1 Получено: https://sandbox.iexapis.com/stable/stock/market/ohlc?symbols=IBM&token=YOUR_API_TOKEN
2 Символ: IBM
3 Ответ: [{"открыть":{"цена":128,9,"время":1636600302693},"закрыть":{"цена":131,44,"время":1662259300134},"высокий":132,517,"низкий" :130.074,"том":3403359,"символ":"IBM"}]
128,9
2021-11-11 03:11:42 +0000
131,44
2022-09-04 02:41:40 +0000
132.517
130.074
3403359
IBM
Комментарии не для расширенного обсуждения; этот разговор был перемещен в чат.
Я немного сбит с толку: этот вопрос идентичен последнему вопросу, который вы разместили, здесь, за исключением просьбы не закрывать как дубликат (которую я удалил, так как это не относится к заголовку или описанию вопрос). Если вы считаете, что ваш первоначально закрытый вопрос не является дубликатом того, который был помечен как дубликат, вам следует включить это в свой вопрос и объяснить, почему он не является дубликатом, чтобы он не был закрыт снова. Но публиковать идентичный вопрос не очень полезно.