RxSwift | Best way to make consecutive network requests? - ios

I've been practicing RxSwift recently, but I'm running into a problem in making network requests.
The question is how can I make consecutive network requests .
For example, in Github api, I should use https://api.github.com/user/starred/{\owner}/{\repository_name} to check if the user starred the repository or not.
It should be sent after I received the data requested but I'm having a hard time to implement this.
Here's what I've tried so far:
import RxSwift
// Struct used to encode response
struct RepositoryResponse: Codable {
let items: [Item]
enum CodingKeys: String, CodingKey {
case items
}
struct Item: Codable {
let fullName: String
enum CodingKeys: String, CodingKey {
case fullName = "full_name"
}
}
}
// Actual data for further use
struct Repository {
let item: RepositoryResponse.Item
var fullName: String {
return item.fullName
}
var isStarred: Bool
init(_ item: RepositoryData, isStarred: Bool) {
self.item = item
self.isStarred = isStarred
}
}
// Url components
var baseUrl = URLComponents(string: "https://api.github.com/search/repositories") // base url
let query = URLQueryItem(name: "q", value: "flutter") // Keyword. flutter for this time.
let sort = URLQueryItem(name: "sort", value: "stars") // Sort by stars
let order = URLQueryItem(name: "order", value: "desc") // Desc order
baseUrl?.queryItems = [query, sort, order]
// Observable expected to return Observable<[Repository]>
Observable<URL>.of((baseUrl?.url)!)
.map { URLRequest(url: $0) }
.flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
return URLSession.shared.rx.response(request: request)
}
.filter { response, data in
return 200..<300 ~= response.statusCode
}
.map { _, data -> [RepositoryResponse.Item] in
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(RepositoryResponse.self, from: data) {
return decoded.items
} else {
print("ERROR: decoding")
return [RepositoryResponse.Item]()
}
}
.map { items -> [Repository] in
let repos = items.map { item -> Repository in
var isStarred: Bool?
/// I want to initialize Repository with isStarred value
/// What should I do in here?
return Repository(item, isStarred: isStarred)
}
return repos
}
What I planned to do is getting repositories by Github search api and then checking if the user has starred each repository.
So I made Repository struct which has two variables containing the name of repository and star status each.
A problem occurs right here. To initialize the Repository struct, I should get star status.
I've tried a completion way, but it seems return before completion returns value.
private func isRepoStarred(name: String, completion: #escaping (Bool) -> Void) {
let isStarredCheckerUrl = URL(string: "https://api.github.com/user/starred/\(name)")!
URLSession.shared.dataTask(with: isStarredCheckerUrl) { _, response, _ in
guard let response = response as? HTTPURLResponse else {
return
}
let code = response.statusCode
if code == 404 {
return completion(false)
} else if code == 204 {
return completion(true)
} else {
return completion(false)
}
}
}
Another way I've tried is making Single observable but don't know how to use this exactly.
func isRepoStarredObs(name: String) -> Single<Bool> {
return Single<Bool>.create { observer in
let isStarredCheckerUrl = URL(string: "https://api.github.com/user/starred/\(name)")!
let task = URLSession.shared.dataTask(with: isStarredCheckerUrl) { _, response, _ in
guard let response = response as? HTTPURLResponse else {
return
}
let code = response.statusCode
if code == 404 {
observer(.success(false))
} else if code == 204 {
observer(.success(true))
} else {
observer(.failure(NSError(domain: "Invalid response", code: code)))
}
}
task.resume()
return Disposables.create { task.cancel() }
}
}
If you have any ideas, please let me know. Thanks.

This gets the starred status:
func isRepoStarred(name: String) -> Observable<Bool> {
URLSession.shared.rx.data(request: URLRequest(url: URL(string: "https://api.github.com/user/starred/\(name)")!))
.map { data in
var result = false
// find out if repo is starred here and return true or false.
return result
}
}
and this is your search.
func searchRepositories() -> Observable<RepositoryResponse> {
var baseUrl = URLComponents(string: "https://api.github.com/search/repositories") // base url
let query = URLQueryItem(name: "q", value: "flutter") // Keyword. flutter for this time.
let sort = URLQueryItem(name: "sort", value: "stars") // Sort by stars
let order = URLQueryItem(name: "order", value: "desc") // Desc order
baseUrl?.queryItems = [query, sort, order]
return URLSession.shared.rx.data(request: URLRequest(url: baseUrl!.url!))
.map { data in
try JSONDecoder().decode(RepositoryResponse.self, from: data)
}
}
That's all you need to make requests.
To combine them you would do this:
let repositories = searchRepositories()
.flatMap {
Observable.zip($0.items.map { item in
isRepoStarred(name: item.fullName).map { Repository(item, isStarred: $0) }
})
}
In general, it's best to reduce the amount of code inside a flatMap as much as possible. Here's a version that breaks the code up a bit better. This version might also be a bit easier to understand what's going on.
let repositories = searchRepositories()
.map { $0.items }
let starreds = repositories
.flatMap { items in
Observable.zip(items.map { isRepoStarred(name: $0.fullName) })
}
let repos = Observable.zip(repositories, starreds) { items, starreds in
zip(items, starreds)
.map { Repository($0, isStarred: $1) }
}

Related

SwiftUI JSON Decoder not Working (using async await)

I am creating a new version of an existing app and want to use the new async await
format for a web request. Placing a break at the JSONDecoder().decode line I see that I do have data -
but the decoding does not work. (The url and my key DO work in the old version)
Here's the JSON format of the web source (shortened - there are many more items in
a fuel_station):
{
"station_locator_url":"https://afdc.energy.gov/stations/",
"total_results":110,
"station_counts":{},
"fuel_stations":[
{
"access_code":"public",
"access_days_time":"24 hours daily; call 866-809-4869 for Clean Energy card",
"access_detail_code":"KEY_ALWAYS",
"cards_accepted":"CleanEnergy",
"date_last_confirmed":"2021-09-10",
}
]
}
I created the following models from the above:
enum CodingKeys: String, CodingKey {
case fuelStations = "fuel_stations"
case accessCode = "access_code"
case accessDaysTime = "access_days_time"
case accessDetailCode = "access_detail_code"
case cardsAccepted = "cards_accepted"
case dateLastConfirmed = "date_last_confirmed"
}
struct TopLevel: Codable {
let fuelStations: [FuelStation]
}
struct FuelStation: Codable {
let accessCode, accessDaysTime, accessDetailCode, cardsAccepted: String
let dateLastConfirmed: String
let id: String
}
I put a simplified version of the initial view in one file for testing:
struct SiteListView: View {
#State private var fuelStations: [FuelStation] = []
#State private var topLevel: TopLevel = TopLevel(fuelStations: [])
var body: some View {
NavigationView {
VStack {
List(fuelStations, id: \.id) { item in
VStack {
Text(item.accessCode)
Text(item.accessDaysTime)
}
}
}
.navigationTitle("Site List View")
.task {
await loadData()
}
}//nav
}
func loadData() async {
//I believe the DEMO_KEY in the url will allow limited retrievals
guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY") else {
print("Invalid URL")
return
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
print("response status code is 200")
if let decodedResponse = try? JSONDecoder().decode(TopLevel.self, from: data) {
topLevel = decodedResponse
print("in decoding: topLevel.fuelStations.count is \(topLevel.fuelStations.count)")
//I would iterate through topLevel here and add to the fuelStations
//array but I never get here
}
} catch {
print("Invalid Data")
}
}//load data
}//struct
Any guidance would be appreciated. Xcode 13.2.1 iOS 15.2
First you should remove ? from try? for the catch to work when there is a problem in decoding like this
func loadData() async {
//I believe the DEMO_KEY in the url will allow limited retrievals
guard let url = URL(string: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?api_key=DEMO_KEY") else {
print("Invalid URL")
return
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { return }
print("response status code is 200")
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode(TopLevel.self, from: data)
print("in decoding: topLevel.fuelStations.count is \(decodedResponse.fuelStations.count)")
//I would iterate through topLevel here and add to the fuelStations
//array but I never get here
} catch {
print(error)
}
}
After you do this , you'll find that some attributes in your struct are coming null in response so you should change string to string? to finally be
struct TopLevel: Codable {
let fuelStations: [FuelStation]
}
struct FuelStation: Codable {
let accessCode, accessDaysTime, accessDetailCode, cardsAccepted,dateLastConfirmed: String?
let id: Int
}
In addition note use of
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
instead of hard-coding the enum

Storing API data into UserDefaults and printing to a list

In my app, users scan a barcode and the information about the product is fetched from an API.
I want to create a history section, where users can view the last 10 products.
The result from the API data is stored in a Result type, which for it to be able to be shown in a list, has to be identifiable.
Result is a custom data type that I'm using to store the details of the products from the API call in.
Result
struct Result: Codable, Identifiable {
var id = UUID()
var description: String?
var brand: String?
var ingredients: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
enum CodingKeys: String, CodingKey {
case description, brand, ingredients, image, upc_code, return_message, return_code
}
}
This data types store the array of Result which I'll display as a list
History
struct History: Codable {
var results: [Result]
}
Here's the API call:
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void ) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let defaults = UserDefaults.standard
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(data) {
var sizeCheck = defaults.object(forKey:"productHistory") as? [Data] ?? [Data]()
if (sizeCheck.count == 10) { //Check if there's more than 10 products already on the history list
sizeCheck.removeLast()
}
sizeCheck.append(encoded) //Add new product to list
defaults.set(sizeCheck, forKey: "productHistory") //Add new list to userDefaults
}
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result) //Used elsewhere to display the scanned product after it's been added to the history list
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
This is my view that shows the last 10 products in a list when a button is pressed.
The last 10 products should be stored in UserDefaults with the key productHistory. This is done in the API call LoadData()
struct historyView: View {
#Binding var showingHistory: Bool
#State private var results = [Result]()
var body: some View {
let defaults = UserDefaults.standard
if let products = defaults.object(forKey: "productHistory") as? Data {
if let decodedResponse = try? JSONDecoder().decode(History.self, from: products) {
self.results = decodedResponse.results
}
}
return List(self.results, id: \.id) { item in
Text(item.description!)
}
}
}
To my understanding, the issue is that UserDefaults can't store JSON data. So when the API data is fetched, I store the data as it is, into userdefualts. Then decode it when I need it, like storing it in history or displaying it.
Currently I'm getting a blank list and the if statement below isn't passing.
if let decodedResponse = try? JSONDecoder().decode(History.self, from: products) {
Here's the JSON data from the API if I paste the URL into the browser:
EDIT
Here's my APICall():
func callAPI() -> String {
if (scannedCode.barcode == "") {
return "noneScanned"
}
else {
let hashedValue = scannedCode.barcode.hashedValue("API ID")
//print(hashedValue!)
loadData(url: "URL") { error, result in
if let err = error {
self.APIresult = err.localizedDescription
print(APIresult)
//output error
}
else if (result?.ingredients == nil) {
DispatchQueue.main.async {
self.APIresult = "noIngredients"
}
}
else if (result?.description == nil) {
DispatchQueue.main.async {
self.APIresult = "noDescription"
}
}
else {
DispatchQueue.main.async {
self.APIresult = "success"
}
}
DispatchQueue.main.async {
product.result = result!
//updates view that show's the scanned product, as it's #Published
}
}
return APIresult
}
}
In this section, I want to find what data I have about the product and process it accordingly. Therefore with the solution above, I return a different value depending on if it's got a image or a description etc...
With vadian solution, I've changed it to this:
loadData(url: "URL") { result in
switch result {
case .success(product):
print("success")
case .failure(error):
print("failure")
}
}
As mentioned in the comments you are mixing up Data and Result
First of all drop History and rename Result as Product. We are going to save an array of Product to UserDefaults
struct Product: Codable, Identifiable {
var id = UUID()
var description: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
private enum CodingKeys: String, CodingKey {
case description, image, upc_code, return_message, return_code
}
}
In loadData use the generic Result type as closure parameter. After receiving the data decode it to a Product instance, then load the saved array, remove the first(!) item (if necessary) append the new item, save the array back and call completion with the new Product. All potential errors are passed in the failure case.
func loadData(url: String, completion: #escaping (Result<Product,Error>) -> Void ) {
guard let url = URL(string: url) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { completion(.failure(error)); return }
do {
let decoder = JSONDecoder()
let product = try decoder.decode(Product.self, from: data!)
let defaults = UserDefaults.standard
var history = [Product]()
if let readData = defaults.data(forKey:"productHistory") {
do {
history = try decoder.decode([Product].self, from: readData)
if history.count == 10 { history.removeFirst() }
} catch { print(error) }
}
history.append(product)
let saveData = try JSONEncoder().encode(history)
defaults.set(saveData, forKey: "productHistory")
completion(.success(product))
}
catch {
print(error)
completion(.failure(error))
}
}
task.resume()
}
and call it
loadData(url: "URL") { result in
switch result {
case .success(let product):
if product.ingredients == nil {
self.APIresult = "noIngredients"
} else if product.description == nil {
self.APIresult = "noDescription"
} else {
self.APIresult = "success"
}
product.result = product
case .failure(let error):
self.APIresult = error.localizedDescription
print(APIresult)
}
}
In HistoryView (please name structs with starting uppercase letter) get the data from UserDefaults and decode the Product array.
struct HistoryView: View {
#Binding var showingHistory: Bool
#State private var results = [Product]()
var body: some View {
let defaults = UserDefaults.standard
if let historyData = defaults.data(forKey: "productHistory") {
do {
self.results = try JSONDecoder().decode([Product].self, from: historyData)
} catch { print(error) }
}
return List(self.results, id: \.id) { item in
Text(item.description ?? "n/a")
}
}
}
Note: Be aware that the UUID is not being encoded and saved.
And please use more descriptive variable names.

How do I get multiple JSON objects from api call?

I am trying to make an API call to the GitLab API to get the projects that are available to a particular user.
I can get one project of an index of my choosing, put it into a ProjectModel with the projectId and the projectName but I can not figure out how to get all of them into an array of ProjectModels.
By printing then I can see them all being printed in the console but it will not let me append them to an array.
It is in the parseJSON function that I am trying to get a hold of all of the projects.
Does anyone have any suggestions?
This is my manager to fetch the projects:
protocol FetchProjectsManagerDelegate {
func didUpdateProjects(_ fetchProjectsManager: FetchProjectsManager, project: ProjectModel?)
func didFailWithError(error: Error)
}
struct FetchProjectsManager {
let projectsURL = "secret"
var delegate: FetchProjectsManagerDelegate?
func fetchProjects(privateToken: String) {
let privateTokenString = "\(projectsURL)projects?private_token=\(privateToken)"
performRequest(with: privateTokenString)
}
func performRequest(with privateTokenString: String) {
// Create url
if let url = URL(string: privateTokenString) {
// Create URLSession
let session = URLSession(configuration: .default)
// Give the session a task
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let project = self.parseJSON(safeData) {
self.delegate?.didUpdateProjects(self, project: project)
}
}
}
// Start the task
task.resume()
}
}
func parseJSON(_ projectData: Data) -> ProjectModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([Project].self, from: projectData)
for project in decodedData {
print(project)
}
let projectId = decodedData[0].id
let projectName = decodedData[0].name
let project = ProjectModel(projectId: projectId, projectName: projectName)
return project
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
}
This is my project model
struct ProjectModel {
let projectId: Int
let projectName: String
}
Your parseJson method only returns a single project instead of all of them, change it to
func parseJSON(_ projectData: Data) -> [ProjectModel]? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([Project].self, from: projectData)
let projects = decodedData.map { ProjectModel(projectId: $0.id,
projectName: $0.name) }
return projects
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
and you of course need to update didUpdateProjects so that it takes an array of ProjectModel or call it in a loop

How to save data from Json with Alamofire and SwiftyJSON?

I am trying to save "author" data to global variable named "authors" from json(Link:"https://learnappmaking.com/ex/books.json") with these two libraries. But it only works at the trailing closure of func Alamofire.request(url).responseJSON. When I access the global variable named "authors" from somewhere except the trailing closure, what I get is an empty array of string.
Can someone explain the reason behind this werid situation?
Thanks a lot.
class ViewController: UIViewController {
var authors = [String]()
let url = "https://learnappmaking.com/ex/books.json"
func getAuthorsCount() {
print("the number of authors : \(authors.count)") // I hope that here, the number of authors should be 3 too! actually, it is 0. Why?
// this for loop doesn't get excuted
for author in authors {
print(author)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
print("authors.count: \(self.authors.count)")
}
}
}
}
getAuthorsCount()
print("-------------")
}
}
the actual output is:
Update:
I adjusted my code:
class ViewController: UIViewController {
var authors = [String]()
let url = "https://learnappmaking.com/ex/books.json"
func getAuthorsCount() {
print("the number of authors : \(authors.count)")
// this for loop doesn't get excuted
for author in authors {
print(author)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
//print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
//print("authors.count: \(self.authors.count)")
}
self.getAuthorsCount() // I added this line of code.
}
}
}
getAuthorsCount()
print("-------------")
}
}
But why does the func getAuthorsCount() (not self. version) still print an empty array of strings ? I think the result should be the same as the result which
func self.getAuthorsCount() printed.
I am so confused now...
Again, I want to use the data kept in the variable named "authors", but what I only got is an empty array of strings.
I'll try to answer all your questions :
The data is persistant
You are doing the following : Alamo.request (Network call) -> getAuthors(print result - empty) ->
response (receive response) -> self.authors.append(save response) -> self.authors (print result)
You need to do : Alamo.request (Network call) -> response (receive response) -> self.authors.append(save response) -> self.getAuthors or getAuthors(same) (inside the response {})
You need to call getAuthors once you have your result, inside the response callback :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
print("authors.count: \(self.authors.count)")
}
self.getAuthorsCount()
print("-------------")
//Do whatever you want from here : present/push
}
}
}
Then you can use the saved data :
To send the data to another ViewController you can use various methods (present/push, closure/callback, ...)
Usually you will have a loading spinner to wait for the network to
answer then you will show your next controller
As requested via direct message: a Swift-only approach. Just paste this in a blank Playground:
import Foundation
final class NetworkService {
enum ServiceError: LocalizedError {
case invalidUrl
case networkingError(error: Error)
case parsingError
var localizedDescription: String? { return String(describing: self) }
}
func request(completion: #escaping (Result<[UserObject], Error>) -> Void ) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
completion(.failure(ServiceError.invalidUrl))
return
}
let dataTask = URLSession.shared.dataTask(with: url) { (jsonData, response, error) in
if let jsonData = jsonData {
let jsonDecoder = JSONDecoder()
do {
let users = try jsonDecoder.decode([UserObject].self, from: jsonData)
completion(.success(users))
} catch {
completion(.failure(ServiceError.parsingError))
}
} else if let error = error {
completion(.failure(ServiceError.networkingError(error: error)))
}
}
dataTask.resume()
}
}
struct UserObject: Codable {
let id: Int
let name: String
let username: String
let email: String?
let website: String?
}
let networkService = NetworkService()
networkService.request { result in
switch result {
case .success(let users):
debugPrint("Received \(users.count) users from REST API")
debugPrint(users)
case .failure(let error):
debugPrint(error.localizedDescription)
}
}

Cannot append data to array from GET request

I am trying to load data from a GET request using Alamofire library in swift and cannot append data from the requests. I am trying to populate an array of orders to load into a UITableView.
I have tried a few various ways of solving this issue but nothing is working for me. I have commented out the method I tried because with 2 separate calls to fetchAll...Orders and the second call always overwrites the first and then the tableView is loaded with missing items.
class DrinkOrdersTableViewController: UITableViewController {
var orders: [Order] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Current Orders"
}
override func viewWillAppear(_ animated: Bool) {
// fetchAllBeerOrders { orders in
// self.orders = orders!
// //print("Beer fetch: ", self.orders)
// self.tableView.reloadData()
// }
// fetchAllCocktailOrders { orders in
// self.orders = orders!
// //print("Cocktail fetch: ", self.orders)
// self.tableView.reloadData()
// }
fetchAllBeerOrders { orders in
self.orders.append(orders)
self.tableView.reloadData()
}
fetchAllCocktailOrders { orders in
self.orders.append(orders)
self.tableView.reloadData()
}
}
private func fetchAllCocktailOrders(completion: #escaping([Order]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/orders", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let currentOrders = rawInventory.compactMap { ordersDict -> Order? in
guard let orderId = ordersDict!["id"] as? String,
let orderStatus = ordersDict!["status"] as? String,
var pizza = ordersDict!["cocktail"] as? [String: Any] else { return nil }
pizza["image"] = UIImage(named: pizza["image"] as! String)
return Order(
id: orderId,
pizza: Pizza(data: pizza),
status: OrderStatus(rawValue: orderStatus)!
)
}
completion(currentOrders)
}
}
private func fetchAllBeerOrders(completion: #escaping([Order]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/orders", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let currentOrders = rawInventory.compactMap { ordersDict -> Order? in
guard let orderId = ordersDict!["id"] as? String,
let orderStatus = ordersDict!["status"] as? String,
var pizza = ordersDict!["pizza"] as? [String: Any] else { return nil }
pizza["image"] = UIImage(named: pizza["image"] as! String)
return Order(
id: orderId,
pizza: Pizza(data: pizza),
status: OrderStatus(rawValue: orderStatus)!
)
}
completion(currentOrders)
}
}
As of right now I am getting this error with code above: Cannot convert value of type '[Order]?' to expected argument type 'Order'. The ideal outcome of this code is to have the data that is gathered from each GET request to append to the array of Orders. I have verified that the GET requests are working and giving back the correct data. Please Help :]
You declared orders of type [Order] and your fetch methods compilation blocks return [Order]?. As you can see, you cannot convert value of type [Order]? to expected argument type Order when you wrote self.orders.append(orders).
To fix these, put a guard unwrap in fetch method invocations.
fetchAllBeerOrders { orders in
guard let _orders = orders else { return }
self.orders.append(_orders)
self.tableView.reloadData()
}
fetchAllCocktailOrders { orders in
guard let _orders = orders else { return }
self.orders.append(_orders)
self.tableView.reloadData()
}
Now, you have a potential memory leak in your code. fetchAllBeerOrders and fetchAllCocktailOrders are async methods with compilation blocks. You cannot use a strong reference to self here. Use weak to avoid a memory leak, like:
fetchAllBeerOrders { [weak self] orders in
guard let _orders = orders else { return }
self?.orders.append(_orders)
self?.tableView.reloadData()
}
fetchAllCocktailOrders { [weak self] orders in
guard let _orders = orders else { return }
self?.orders.append(_orders)
self?.tableView.reloadData()
}

Resources