简体   繁体   中英

How do I refactor the code for making the API call using async await in swift

I want to refactor the API call that is made using async and await but I am getting the error as the publishing needs to be done on the main thread.

The below is the code that I wrote in the file named LogIn View: -

@State private var quotes = [Quote]()
var body: some View {
    NavigationView {
            List(quotes, id:\.quote_id) { quote in
                VStack(alignment: .leading) {
                    Text(quote.author)
                        .font(.headline)
                    Text(quote.quote)
                        .font(.body)
                }
            
        }
        .padding()
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Log out") {
                    authentication.updateValidation(success: false)
                }
            }
        }
        .navigationTitle("Dashboard Screen ")
    }
  
    .task {
        await fetchData()
    }
}

func fetchData() async {
    //create url
    guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
        print("URL does not work")
        return
    }
    
    //fetch data from url
    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        
        //decode that data
        
        if let decodeResponse = try? JSONDecoder().decode([Quote].self, from: data) {
            quotes = decodeResponse
        }
    } catch {
        print("Data not valid")
    }
}

I want to write the function fetchData() in a separate file and use it here in LogIn View but upon trying to do so I am getting the error mentioned above. Can anyone Please help me with this.

PS:- all the variables are defined inside another file named variables . The code for that is as follows:-

import Foundation

struct Quote: Codable {
    var quote_id: Int
    var quote: String
    var author: String
    var series: String
}

A nice place is in an extension of NSURLSession , eg

extension NSURLSession {
    func fetchQuotes() async throws -> [Quote] {
        //create url
        guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
            print("URL does not work")
            return
        }
        
        //fetch data from url
        let (data, _) = try await data(from: url)
            
        //decode that data
        return try JSONDecoder().decode([Quote].self, from: data)
    }
}

Then you can simply do:

.task {
    do {
        quotes = try await URLSession.shared.fetchQuotes()
    } catch {
        errorMessage = error.description
    }      
}

This has the advantage you can use it with a different kind of URLSession , eg for API requests we usually use an ephemeral session. Another good place would be a static async func in the Quote struct.

to put your func fetchData() in a model and avoid the error, try this approach:

class QuotesModel: ObservableObject {
    
    @Published var quotes = [Quote]()

    @MainActor  // <-- here
    func fetchData() async {
        guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
            print("Invalid URL")
            return
        }
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            quotes = try JSONDecoder().decode([Quote].self, from: data)
        } catch {
            print(error)
        }
    }
 
} 


struct ContentView: View {
    
    @StateObject var model = QuotesModel()
    
    var body: some View {
        NavigationView {
            List(model.quotes, id: \.quote_id) { quote in
                VStack(alignment: .leading) {
                    Text(quote.author)
                        .font(.headline)
                    Text(quote.quote)
                        .font(.body)
                }
                
            }
            .padding()
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Log out") {
                      //  authentication.updateValidation(success: false)
                    }
                }
            }
            .navigationTitle("Dashboard Screen ")
        }
        .task {
            await model.fetchData()
        }
    }
    
}

struct Quote: Codable {
    var quote_id: Int
    var quote: String
    var author: String
    var series: String
}

depending on your code it may just be as simple as putting the code giving you the error inside await MainActor.run {... }

That said, as a general rule async code is easier to manage when it returns values to use, rather than setting variables from inside functions.

class SomeViewModel:ObservableObject {
   func fetchData() async -> [Quotes] {
    ...
        if let decodeResponse = try? JSONDecoder().decode([Quote].self, from: data) {
            return decodeResponse
        }
    ...

   }
}
struct TheView: View {
@StateObject var viewModel = SomeViewModel()
@State private var quotes = [Quote]()
  var body: some View {
    NavigationView {
            
    }
    .task {
        quotes = await viewModel.fetchData()
    }
  }
}

Make @MainActor method with @Published in ObservableObject class. and for model use codable.

struct QuoteView: View {
    @State var quotes: [Quote] = []
    @ObservedObject var quoteStore = QuoteStore()

    var body: some View {
        NavigationView {
            List(quotes, id:\.quote_id) { quote in
                VStack(alignment: .leading) {
                    Text(quote.author)
                        .font(.headline)
                    Text(quote.quote)
                        .font(.body)
                }
            }
            .navigationTitle("Quotes")
        }
        .task {
            quotes = try! await quoteStore.fetchData()
        }
    }
}

struct Quote: Codable {
    let quote_id = UUID()
    let quote: String
    let author: String
}

class QuoteStore: ObservableObject {
    @Published var quotes: [Quote] = []

    @MainActor
    func fetchData() async throws -> [Quote] {
        guard var url = URL(string: "https://breakingbadapi.com/api/quotes") else { throw AppError.invalidURL }
        let (data, _) = try await URLSession.shared.data(from: url)
        let repos = try JSONDecoder().decode([Quote].self, from: data)
        return repos
    }
}

enum AppError: Error {
    case invalidURL
}

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM