How to get an error response with driver so I can show it in alert. When I see the trait driver is can't error out, so should I use subject or behaviourRelay to get error response when I subscribe. Actually I like how to use driver but I don't know how to passed error response using driver.
this is my network service
func getMovies(page: Int) -> Observable<[MovieItem]> {
return Observable.create { observer -> Disposable in
self.service.request(endpoint: .discover(page: page)) { data, response, error in
if let _ = error {
observer.onError(MDBError.unableToComplete)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
observer.onError(MDBError.invalidResponse)
return
}
guard let data = data else {
observer.onError(MDBError.invalidData)
return
}
if let decode = self.decode(jsonData: MovieResults.self, from: data) {
observer.onNext(decode.results)
}
observer.onCompleted()
}
return Disposables.create()
}
}
This is my viewModel
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
class PopularViewModel: ViewModelType {
struct Input {
let viewDidLoad: Driver<Void>
}
struct Output {
let loading: Driver<Bool>
let movies: Driver<[MovieItem]>
}
private let service: NetworkDataFetcher
init(service: NetworkDataFetcher = NetworkDataFetcher(service: NetworkService())) {
self.service = service
}
func transform(input: Input) -> Output {
let loading = ActivityIndicator()
let movies = input.viewDidLoad
.flatMap { _ in
self.service.getMovies(page: 1)
.trackActivity(loading)
.asDriver(onErrorJustReturn: [])
}
let errorResponse = movies
return Output(loading: loading.asDriver(),movies: movies)
}
}
this is how I bind the viewModel in viewController
let input = PopularViewModel.Input(viewDidLoad: rx.viewDidLoad.asDriver())
let output = viewModel.transform(input: input)
output.movies.drive { [weak self] movies in
guard let self = self else { return }
self.populars = movies
self.updateData(on: movies)
}.disposed(by: disposeBag)
output.loading
.drive(UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
.disposed(by: disposeBag)
You do this the same way you handled the ActivityIndicator...
The ErrorRouter type below can be found here.
This is such a common pattern that I have created an API class that takes care of this automatically.
class PopularViewModel: ViewModelType {
struct Input {
let viewDidLoad: Driver<Void>
}
struct Output {
let loading: Driver<Bool>
let movies: Driver<[MovieItem]>
let displayAlertMessage: Driver<String>
}
private let service: NetworkDataFetcher
init(service: NetworkDataFetcher = NetworkDataFetcher(service: NetworkService())) {
self.service = service
}
func transform(input: Input) -> Output {
let loading = ActivityIndicator()
let errorRouter = ErrorRouter()
let movies = input.viewDidLoad
.flatMap { [service] in
service.getMovies(page: 1)
.trackActivity(loading)
.rerouteError(errorRouter)
.asDriver(onErrorRecover: { _ in fatalError() })
}
let displayAlertMessage = errorRouter.error
.map { $0.localizedDescription }
.asDriver(onErrorRecover: { _ in fatalError() })
return Output(
loading: loading.isActive.asDriver(onErrorRecover: { _ in fatalError() }),
movies: movies,
displayAlertMessage: displayAlertMessage
)
}
}
Related
I have a Swift app that uses a web socket to download stock price information from a public API. I send a message through the socket to subscribe to various stock price changes then wait for a continuous stream of messages to be received but when I turn off wifi the call to the message receive function, webSocketTask.receive, never returns. How can I force abort of the message receive function so that I alert the user that the network connection has been lost.
Here is my NetworkServices class;
import UIKit
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices: NSObject {
static let sharedInstance = NetworkServices()
var urlSession: URLSession?
var webSocketTask: URLSessionWebSocketTask?
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var timer: Timer?
var stockSymbols: [String] = []
private let queue = DispatchQueue.global(qos: .background)
private let monitor = NWPathMonitor()
public private(set) var isConnected: Bool = false
public private(set) var connectionType: ConnectionType = .unknown
enum ConnectionType {
case wifi
case cellular
case wiredEthernet
case unknown
}
public func startMonitoring() {
monitor.start(queue: queue)
monitor.pathUpdateHandler = { path in
self.isConnected = path.status == .satisfied
self.getConnectionType(path)
print("DEBUG: isConnected = \(self.isConnected); connectionType = \(self.connectionType)")
if self.isConnected == false {
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
private func getConnectionType(_ path: NWPath) {
if path.usesInterfaceType(.wifi) {
connectionType = .wifi
} else if path.usesInterfaceType(.cellular) {
connectionType = .cellular
} else if path.usesInterfaceType(.wiredEthernet) {
connectionType = .wiredEthernet
} else {
connectionType = .unknown
}
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = false
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
webSocketTask = urlSession?.webSocketTask(with: FINNHUB_SOCKET_STOCK_INFO_URL!)
webSocketTask?.delegate = self
webSocketTask!.resume()
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
let message = URLSessionWebSocketTask.Message.string(string)
if isConnected {
webSocketTask!.send(message) { error in
if let error = error {
print("DEBUG: Error sending message: \(error)")
}
self.receiveMessage()
}
} else {
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect
// successful re-connect?
// No, keep trying. Yes, call send message method again
}
}
}
private func receiveMessage() {
if isConnected {
self.webSocketTask?.receive { result in
print("DEBUG: Inside closure.")
switch result {
case .failure(let error):
print("DEBUG: Error receiving message: \(error.localizedDescription)")
case .success(.string(let jsonData)):
guard let stockData = jsonData.data(using: .utf8) else { return }
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: stockData)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
self.receiveMessage()
} catch {
print("DEBUG: Error converting JSON: \(error)")
}
default:
print("DEBUG: default")
}
}
print("DEBUG: Got here 1")
} else {
print("DEBUG: Got here 2")
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect.
// successful reconnect?
// No, keep trying. Yes, call receive message method again.
}
}
func closeWebSocketConnection() {
webSocketTask?.cancel(with: .goingAway, reason: nil)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("DEBUG: didCompleteWithError called: error = \(error)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: URLSessionTaskDelegate, URLSessionWebSocketDelegate, URLSessionDelegate {
func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
print("DEBUG: inside taskIsWaitingForConnectivity")
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
print("DEBUG: didBecomeInvalidWithError: error = \(String(describing: error?.localizedDescription))")
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
let reasonString: String
if let reason = reason, let string = String(data: reason, encoding: .utf8) {
reasonString = string
} else {
reasonString = ""
}
print("DEBUG: didCloseWith called: close code is \(closeCode), reason is \(String(describing: reasonString))")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("DEBUG: urlSessionDidFinishEvents called")
}
}
I have tried placing code in the pathUpdateHandler closure to cancel the task on the task object and finish current tasks and cancel on the URLSession object but neither works.
I needed to post a notification when path.status == .satisfied. The notification goes to the custom UIViewController class which then calls the FetchStockInfo(symbols:delegate) method.
I have a code, that should match 2 JSON files and connect all data in one struct. I'm pretty sure that It works just fine, but I have faced very strange problem. I want to make a picker:
import SwiftUI
struct CurrencyView: View {
#ObservedObject var api = CurrencyViewModel()
#State private var pickerSelection1 = 1
var body: some View {
Text("f")
Picker("", selection: $pickerSelection1) {
ForEach(0..<self.api.currencies.fullName.count) { // Error here
let currency = api.currencies.fullName[$0] // and here
Text(currency)
}
}
.id(UUID())
.labelsHidden()
.padding(.leading)
}
}
struct CurrencyView_Previews: PreviewProvider {
static var previews: some View {
CurrencyView()
}
}
It shows this error:
Value of type '[Currency]' has no member 'fullName'
I know I'm missing smth and feel stupid, because I can't understand why. Thanks for the reply!)
Adding the rest of the code:
// Model
import Foundation
struct CurrencyModel: Codable {
var results: [String:Double]
}
struct CurrencyNewModel: Codable {
var currencies: [String:String]
}
struct Currency: Decodable {
let currencyCode: String
let fullName: String
var price: Double
}
// View Model
import SwiftUI
class CurrencyViewModel: ObservableObject {
#Published var currencies: [Currency] = []
init() {
fetchNewData { [self] (currency) in
switch currency {
case .success(let names):
print("Success")
DispatchQueue.main.async {
self.currencies = names.currencies.map {
Currency(currencyCode: $0.key, fullName: $0.value, price: 0)
}
}
fetchData { result in
switch result {
case .success(let prices):
print("Success")
for (index, value) in currencies.enumerated() {
if let price = prices.results.first(where: { $0.key == value.currencyCode }) {
DispatchQueue.main.async {
currencies[index].price = price.value
}
}
}
case .failure(let error):
print(error)
}
}
case .failure(let error):
print("Error", error)
}
}
}
func fetchData(completion: #escaping (Result<CurrencyModel,Error>) -> ()) {
guard let url = URL(string: "https://api.fastforex.io/fetch-all?from=USD&api_key=7ffe65c2ef-926f01d9e8-r7eql2") else { return }
URLSession.shared.dataTask(with: url) { data, responce, error in
if let error = error {
completion(.failure(error))
return
}
guard let safeData = data else { return }
do {
let currency = try JSONDecoder().decode(CurrencyModel.self, from: safeData)
completion(.success(currency))
}
catch {
completion(.failure(error))
}
}
.resume()
}
func fetchNewData(completion: #escaping (Result<CurrencyNewModel,Error>) -> ()) {
guard let url = URL(string: "https://api.fastforex.io/currencies?api_key=7ffe65c2ef-926f01d9e8-r7eql2") else { return }
URLSession.shared.dataTask(with: url) { data, responce, error in
if let error = error {
completion(.failure(error))
return
}
guard let safeData = data else { return }
do {
let currency = try JSONDecoder().decode(CurrencyNewModel.self, from: safeData)
completion(.success(currency))
}
catch {
completion(.failure(error))
}
}
.resume()
}
}
P.S. If you want to see the API, check the links in fetchData and fetchNewData, It's a free trial, so doesn't matter
Your error says:
Value of type '[Currency]' has no member 'fullName'
So it seems that api.currencies is an array – the array itself has no member fullName, only one single element of it has.
try this:
ForEach(api.currencies, id:\.currencyCode) { currency in
Text(currency.fullName)
}
Right now I have to call the function (calculatePortfolioGrossBalance) 3 times for the value to update, what am I doing wrong in the state logic?
In the code below, when I call in an init the function calculatePortfolioGrossBalance() it returns empty [], I have to call it 3 times for the value to update, However... if I print the values of getTokenBalancesModel in the line DispatchQueue.main.async { I can see the values are there, so how come in calculatePortfolioGrossBalance are not?
final class TokenBalancesClassAViewModel: ObservableObject {
#Published var getTokenBalancesModel: [TokenBalancesItemsModel] = [TokenBalancesItemsModel]()
#Published var portfolioGrossBalance: String = "0.0"
func calculatePortfolioGrossBalance() {
getTokenBalances()
DispatchQueue.main.async {
var totalBalance: Double = 0
for item in self.getTokenBalancesModel {
totalBalance += Double(item.quote!)
}
self.portfolioGrossBalance = String(format:"%.2f", totalBalance)
print(self.portfolioGrossBalance)
}
}
func getTokenBalances() {
guard let url = URL(string: "someUrlHeidiGaveMe") else {
print("Invalid URL")
return
}
print("Calling getTokenBalances() ...")
AF.request(url, method: .get).validate().responseData(completionHandler: { data in
do {
guard let data = data.data else {
print("Response Error:", data.error as Any)
return
}
let apiJsonData = try JSONDecoder().decode(TokenBalancesModel.self, from: data)
DispatchQueue.main.async {
self.getTokenBalancesModel = apiJsonData.data.items
}
} catch {
print("ERROR:", error)
}
})
}
}
you need to read up on using asynchronous functions, how to set them up and how to use them. This is important. Try something like this (untested):
final class TokenBalancesClassAViewModel: ObservableObject {
#Published var getTokenBalancesModel: [TokenBalancesItemsModel] = [TokenBalancesItemsModel]()
#Published var portfolioGrossBalance: String = "0.0"
func calculatePortfolioGrossBalance() {
getTokenBalances() { isGood in
if isGood {
var totalBalance: Double = 0
for item in self.getTokenBalancesModel {
totalBalance += Double(item.quote!)
}
self.portfolioGrossBalance = String(format:"%.2f", totalBalance)
print(self.portfolioGrossBalance)
}
}
}
func getTokenBalances(completion: #escaping (Bool) -> Void) {
guard let url = URL(string: "someUrlHeidiGaveMe") else {
print("Invalid URL")
completion(false)
return
}
print("Calling getTokenBalances() ...")
AF.request(url, method: .get).validate().responseData(completionHandler: { data in
do {
guard let data = data.data else {
print("Response Error:", data.error as Any)
completion(false)
return
}
let apiJsonData = try JSONDecoder().decode(TokenBalancesModel.self, from: data)
DispatchQueue.main.async {
self.getTokenBalancesModel = apiJsonData.data.items
completion(true)
}
} catch {
print("ERROR:", error)
completion(false)
}
})
}
}
So, I have a button and will make an API request upon tapping it. When the API request returns an error, if my understanding is correct, the sequence will be terminated and no subsequent action will be recorded. How do I handle this properly so that I can still make another API request when tapping the button.
My thoughts are to have two observables that I can subscribe to in ViewController and on button pressed, one of it will print the success response and one of it will print the error. Just not quite sure how I can achieve that.
PS: In Post.swift, I have purposely set id as String type to fail the response. It should have be an Int type.
Post.swift
import Foundation
struct Post: Codable {
let id: String
let title: String
let body: String
let userId: Int
}
APIClient.swift
class APIClient {
static func request<T: Codable> (_ urlConvertible: URLRequestConvertible, decoder: JSONDecoder = JSONDecoder()) -> Observable<T> {
return Observable<T>.create { observer in
URLCache.shared.removeAllCachedResponses()
let request = AF.request(urlConvertible)
.responseDecodable (decoder: decoder) { (response: DataResponse<T>) in
switch response.result {
case .success(let value):
observer.onNext(value)
observer.onCompleted()
case .failure(let error):
switch response.response?.statusCode {
default:
observer.onError(error)
}
}
}
return Disposables.create {
request.cancel()
}
}
}
}
PostService.swift
class PostService {
static func getPosts(userId: Int) -> Observable<[Post]> {
return APIClient.request(PostRouter.getPosts(userId: userId))
}
}
ViewModel.swift
class LoginLandingViewModel {
struct Input {
let username: AnyObserver<String>
let nextButtonDidTap: AnyObserver<Void>
}
struct Output {
let apiOutput: Observable<Post>
let invalidUsername: Observable<String>
}
// MARK: - Public properties
let input: Input
let output: Output
// Inputs
private let usernameSubject = BehaviorSubject(value: "")
private let nextButtonDidTapSubject = PublishSubject<Void>()
// MARK: - Init
init() {
let minUsernameLength = 4
let usernameEntered = nextButtonDidTapSubject
.withLatestFrom(usernameSubject.asObservable())
let apiOutput = usernameEntered
.filter { text in
text.count >= minUsernameLength
}
.flatMapLatest { _ -> Observable<Post> in
PostService.getPosts(userId: 1)
.map({ posts -> Post in
return posts[0]
})
}
let invalidUsername = usernameEntered
.filter { text in
text.count < minUsernameLength
}
.map { _ in "Please enter a valid username" }
input = Input(username: usernameSubject.asObserver(),
nextButtonDidTap: nextButtonDidTapSubject.asObserver())
output = Output(apiOutput: apiOutput,
invalidUsername: invalidUsername)
}
deinit {
print("\(self) dellocated")
}
}
ViewController
private func configureBinding() {
loginLandingView.usernameTextField.rx.text.orEmpty
.bind(to: viewModel.input.username)
.disposed(by: disposeBag)
loginLandingView.nextButton.rx.tap
.debounce(0.3, scheduler: MainScheduler.instance)
.bind(to: viewModel.input.nextButtonDidTap)
.disposed(by: disposeBag)
viewModel.output.apiOutput
.subscribe(onNext: { [unowned self] post in
print("Valid username - Navigate with post: \(post)")
})
.disposed(by: disposeBag)
viewModel.output.invalidUsername
.subscribe(onNext: { [unowned self] message in
self.showAlert(with: message)
})
.disposed(by: disposeBag)
}
You can do that by materializing the even sequence:
First step: Make use of .rx extension on URLSession.shared in your network call
func networkCall(...) -> Observable<[Post]> {
var request: URLRequest = URLRequest(url: ...)
request.httpMethod = "..."
request.httpBody = ...
URLSession.shared.rx.response(request)
.map { (response, data) -> [Post] in
guard let json = try? JSONSerialization.jsonObject(with: data, options: []),
let jsonDictionary = json as? [[String: Any]]
else { throw ... } // Throw some error here
// Decode this dictionary and initialize your array of posts here
...
return posts
}
}
Second step, materializing your observable sequence
viewModel.networkCall(...)
.materialize()
.subscribe(onNext: { event in
switch event {
case .error(let error):
// Do something with error
break
case .next(let posts):
// Do something with posts
break
default: break
}
})
.disposed(by: disposeBag)
This way, your observable sequence will never be terminated even when you throw an error inside your network call, because .error events get transformed into .next events but with a state of .error.
So I have also found the way to achieve what I wanted, which is assigning the success output and error output into two different observable respectively. By using RxSwiftExt, there are two additional operators, elements() and errors() which can be used on an observable that is materialized to get the element.
Here is how I did it,
ViewModel.swift
let apiOutput = usernameEntered
.filter { text in
text.count >= minUsernameLength
}
.flatMapLatest { _ in
PostService.getPosts(userId: 1)
.materialize()
}
.share()
let apiSuccess = apiOutput
.elements()
let apiError = apiOutput
.errors()
.map { "\($0)" }
Then, just subscribe to each of these observables in the ViewController.
As reference: http://adamborek.com/how-to-handle-errors-in-rxswift/
I successfully fetched and decoded data from an API and now have access to all the data I need to be used in the algorithm I want to write in my App.
The issue is that I don't know how to access this data after I decoded it, I can print it immediately after it's decoded but I have no idea how to use it in another function or place in my app.
Here is my Playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError : Error {
case FoundNil(String)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
print(marketData.data[0].open)
print(marketData.data[1].open)
print("Average=", (marketData.data[0].open + marketData.data[1].open) / 2)
//completion(marketData, nil)
throw MyError.FoundNil("data")
}
} catch {
print(error)
}
}
task.resume()
}
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
How can I use .data[0], .data[1], ..., somewhere else?
You data will be available in your fecthData() call. Probably what you want is your items variable, where you're printing it. But make sure to call the completion in your fetchData implementation.
WARNING: Untested code.
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case FoundNil(String)
case DecodingData(Data)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, MyError.FoundNil("data"))
}
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
completion(marketData, nil)
} else {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
} catch {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
}
task.resume()
}
fetchData() { items, error in
if let error == error {
switch(error) {
case .foundNil(let whatsNil):
print("Something is nil: \(whatsNil)")
case .decodingData(let data):
print("Error decoding: \(data)")
}
} else {
if let items = items {
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
print(items)
} else {
print("No items to show!")
}
}
}
I don't understand what is your real issue, because you have written everything you need here, but as far I understand , to pass data
just uncomment this line completion(marketData, nil)
and in
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
items is an object of your struct Response. You can pass this anywhere in your other class , by just creating an another variable like:
var items : Response!
for example :
class SomeOtherClass : NSObject{
var items : Response!
func printSomeData()
{
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
}
}
and in fetchData method write this:
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let otherObject = SomeOtherClass()
otherObject.items = items
otherObject.printSomeData()
}