Swift - Как назначить URLSession.dataTask (с :) декодированными данными внешней переменной? - PullRequest
0 голосов
/ 07 мая 2020

Я изучаю API и практикуюсь с простым приложением для поиска биржевых тикеров. Я могу успешно получить данные JSON с помощью настраиваемого URL-адреса, запрашиваемого пользователем. У меня правильно настроена объектная модель:

    struct TCKR: Decodable {
        let symbol: String
        let delayedPrice: Double?
        let iexRealtimePrice: Double
        ...
    }

У меня также есть структура обработчика запросов API, которая обрабатывает строку символа тикера, токен API и создание URL

    struct IEXCloudRequest {
        var tckr: TCKR! //I swear I'll provide it at some point 

        let strTckr: String
        let myToken = "?token=lqj34poiruadosfnp39u2rkndaslkfnmxnip32ur"

        var reqURL: URL {
            return URL(string: "blah" + strTckr + myToken)!
        }
    }

I am в SwiftUI и хочу использовать объект tckr, созданный моим обработчиком запросов API, для отображения свойств. Вот как я хотел бы обрабатывать объектную модель.

    //ContentView.swift

    struct ContentView: View {
        let tckr: TCKR = IEXCloudRequest("AAPL").tckr

        var body: some View {
            Text(tckr.symbol)
        }
    }

Проблема в том, что я не знаю, как извлечь декодированный tckr из JSONDecoder и использовать его глобально. Текущий метод, который я использую:

    let task = URLSession.shared.dataTask(with: self.reqURL { data, response, error in
        if let data = data {
            do {
                let tckrShell = try JSONDecoder().decode(TCKR.self, from: data)
                print("object creation success for ticker: \(String(describing: tckrShell!.symbol))")
                //I consistently print from this block so my API Calls and data decoding are working
            } catch {
                print("we have an E")
                print(error)

            }
        } else {
            print("data from URL returned nil")

        }
    }

    task.resume()

Всякий раз, когда я пытаюсь назначить переменную tckrShell извне, я получаю сообщение об ошибке о том, что я не могу назначить изменяющиеся собственные переменные при использовании @escaping обработчики завершения и, следовательно, не могут использовать мой объект TCKR «глобально». Пожалуйста, боги Свифта, выручайте брата.

Ответы [ 2 ]

0 голосов
/ 07 мая 2020

Вы неправильно настроили. Вы не можете изменить строгие свойства из закрытия. Если вам нужно это сделать, используйте класс вместо структуры

Реорганизовал ваш код в MVVM:

    struct ContentView: View {

    @ObservedObject var viewModel: ContentViewModel

    var body: some View {
        Text("\(viewModel.ticker?.delayedPrice ?? 0.0)")
    }
}

class ContentViewModel: ObservableObject {

    var ticker: TCKR? = nil {
        didSet {
            objectWillChange.send()
        }
    }
    var apiService: ApiService?

    init(apiService: ApiService = ApiService()) {
        self.apiService = apiService
        getApiCount()
    }

    var objectWillChange: ObservableObjectPublisher = ObservableObjectPublisher()

    func getApiCount() {
        apiService?.getTheList(completion: { [weak self] (result) in
            switch result {
            case .success(let response):
                DispatchQueue.main.async {
                    self?.ticker = response
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        })
    }
}



struct TCKR: Decodable {
    let symbol: String
    let delayedPrice: Double?
    let iexRealtimePrice: Double

}

struct IEXCloudRequest {

    let strTckr: String
    let myToken = "?token=lqj34poiruadosfnp39u2rkndaslkfnmxnip32ur"

    var reqURL: URL {
        return URL(string: "blah" + strTckr + myToken)!
    }
}

struct ApiService {

    let session = URLSession.shared
    let request: IEXCloudRequest

    init(request: IEXCloudRequest = IEXCloudRequest(strTckr: "AAPL")) {
        self.request = request
    }

    func getTheList(completion: @escaping (Result<TCKR, Error>) -> Void) {

        let dataTask = session.dataTask(with: request.reqURL) { (data, response, error) in

            guard let responseData = data else {
                return
            }
            let jsonDecoder = JSONDecoder()
            do {
            let response = try jsonDecoder.decode(TCKR.self, from: responseData)
                completion(.success(response))
            } catch {
                print(error)
                completion(.failure(error))
            }
        }
        dataTask.resume()
    }

}


enum Result<T, E: Error> {
    case success(T)
    case failure(E)
}
0 голосов
/ 07 мая 2020

Для присвоения tckrShell внешней переменной вам нужен блок завершения в методе запроса fetch. вы можете справиться с этим примерно так:

fund doNetworkCall(Your input, completionBlock: @escaping (TCKR) -> Void)  {
   let task = URLSession.shared.dataTask(with: self.reqURL { data, response, error in
        if let data = data {
            do {
                let tckrShell = try JSONDecoder().decode(TCKR.self, from: data)
                completionBlock(tckShell)
                return
                print("object creation success for ticker: \(String(describing: tckrShell!.symbol))")
                //I consistently print from this block so my API Calls and data decoding are working
            } catch {
                print("we have an E")
                print(error)
                return
            }
        } else {
            print("data from URL returned nil")

        }
    }

    task.resume()
}
...