简体   繁体   中英

iOS: How to access and use API response data from REST SwiftUi

firstly I am really new to iOS development and Swift (2 weeks coming here from PHP:))

I am creating a application that has a callout to my very simple api. I am using the below code but an unable to access the 'if let response' part of the code and unable to get the value of 'Comments' from the returned data from api. Wondering if anyone can help on this.

The data come back fine as

//Print out fine
print(str)

will print my response of {"Comments":"success"}

so just wondering how I can properly use this data and check if success etc

Thanks


        func loadData() {
            guard let url = URL(string: "myapi.php") else {
                print("Your API end point is Invalid")
                return
            }
            let request = URLRequest(url: url)
            print("hello1")
    
            URLSession.shared.dataTask(with: request) { data, response, error in
                if let data = data {
                    print("hello2")
                    let str = String(decoding: data, as: UTF8.self)

                    //Print out fine
                    print(str)
    
                    //  Cannot enter this if statement
                    if let response = try? JSONDecoder().decode([TaskEntry].self, from: data) {
                        DispatchQueue.main.async {
                            print("hello3")
    
                            print(response)
                        }
                        return
                    }
                }
            }.resume()
        }
        
    struct TaskEntry: Codable {
        public var Comments: String
    }

With the description provided by you, the JSON that returns your API is the following:

{ 
    "Comments": "success"
}

Is that right?

If it is, this part of your code

if let response = try? JSONDecoder().decode([TaskEntry].self, from: data)

Marks that you are decoding data fetched from your app as Array. You specify an array of TaskEntry right here [TaskEntry].self for your data that's not true, you need to decode json with try? JSONDecoder().decode(TaskEntry.self, from: data) try? JSONDecoder().decode(TaskEntry.self, from: data)

If you hope for an array your api must to return something like this

[
  {
    "Comments": "success"
  }
]

For check if it is right with status code I provide a restructuration of your code explained.

First, protocol Codable it's used to decode and encode data with Encoders and Decoders. In that case, you are working with JSONs so you use JSONDecoder to decode data. You only need decode, so with Decodable protocol It's enough. You don't need to mark Comments with public, because you are working with TaskEntry in your project, so by default (no specified) It's internal. Moreover you don't need var, because, you are not making changes of the property. Also, properties as standard starts with lowercase, so you can use CodingKeys to keep a quality code standard.

You can check it in Apple Developer site

struct TaskEntry: Decodable {
    let comments: String

    enum CodingKeys: String, CodingKey {
        case comments = "Comments"
    }
}

Then, you should to think about your Api errors and defined it. I give you an example:

enum NetworkErrors: Error {
    case responseIsNotHTTP
    case noDataProvided
    case unknown
}

Your load function, should to communicate to external class or struct the TaskEntry or the error. You could use a closure with a Result. Please check the link Hacking with Swift . This has two cases with associated values, failure and success.

Further the response of your UrlSessionTask need to be cast to HTTPURLResponse to get the response http status code. I provide an example of that with your code.

func loadData(result: @escaping (Result<TaskEntry, Error>) -> Void) {
    guard let url = URL(string: "myapi.php") else {
        result(.failure(URLError(.badURL)))
        return
    }

    let request = URLRequest(url: url) // your method is Get, if not you need to set the http method of the request
    URLSession.shared.dataTask(with: request) { data, response, error in

        guard let response = response as? HTTPURLResponse else {
            if let error = error {
                print(error.localizedDescription)
                result(.failure(error))

            }  else {
                result(.failure(NetworkErrors.responseIsNotHTTP))
            }
            return
        }

        switch response.statusCode {
            case 200...299:
                if let data = data {
                    do {
                        let taskEntry = try JSONDecoder().decode(TaskEntry.self, from: data)
                        result(.success(taskEntry))
                    } catch {
                        result(.failure(error))
                    }
                } else {
                    result(.failure(NetworkErrors.noDataProvided))
                }

            default:
                if let error = error {
                    print(error.localizedDescription)
                    result(.failure(error))

                }  else {
                    result(.failure(NetworkErrors.unknown))
                }

        }
    }.resume()
}

Additionally, you could pass this code to async/await form with a new function in iOS 15 or MacOS 12 (async/await it's a feature of swift 5.5, but the urlsession function that i've used it's only from ios 15 and macos 12 to up):

@available(macOS 12.0, iOS 15.0, *)
func loadData() async throws -> Result<TaskEntry,Error> {
    guard let url = URL(string: "myapi.php") else {
        throw URLError(.badURL)
    }

    do {
        let (data, urlResponse) = try await URLSession.shared.data(from: url, delegate: nil)

        guard let response = urlResponse as? HTTPURLResponse else {
            return .failure(NetworkErrors.responseIsNotHTTP)
        }

        switch response.statusCode {
            case 200...299:
                do {
                    let taskEntry = try JSONDecoder().decode(TaskEntry.self, from: data)
                    return .success(taskEntry)
                } catch {
                    return .failure(error)
                }

            default:
                return .failure(NetworkErrors.unknown)

        }
    } catch {
        return .failure(error)
    }
}

Please, vote up if it's clarify you.

Have a good day:)

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