Swift - Http get request to class

Hi, I would like to create a class with 4 parameters : IoT IP address and 3 additional strings received from the http request. the constructor will receive an IP address and create a class member with the IP address and additional 3 strings received from the request.

Appreciate anyone help. Thank you.

The standard API for running HTTP[S] requests is URLSession. Our URL Loading System has lots of good info on how to use that.

Oh, and if your accessory is on the local network, be aware that iOS 14 and later enforce local network privacy. See the Local Network Privacy FAQ for more on this.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

This is what I am trying to do:

struct shellyProperties : Codable {

    let device_id: String

    let name: String

    let app: String

    let model: String

    let stock_fw_model: String

    let host: String

    let version: String

    let fw_build: String

    let uptime: Int

    let failsafe_mode: Bool

    let auth_en: Bool

}







func GetShellyProp(ip : String)-> shellyProperties{

    

    var shelly_dev = shellyProperties(device_id: "", name: "", app: "", model: "", stock_fw_model: "", host: "", version: "", fw_build: "", uptime: 0, failsafe_mode: false, auth_en: false)

                

    // Create URL

    let url = URL(string: "http://" + ip + "/rpc/Shelly.GetInfo")

    //let url = URL(string: "http://" + "10.0.1.6" + "/rpc/Shelly.GetInfo")

    guard let requestUrl = url else { fatalError() }



    // Create URL Request

    var request = URLRequest(url: requestUrl)



    // Specify HTTP Method to use

    request.httpMethod = "GET"



    // Send HTTP Request

    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

        

        // Check if Error took place

        if let error = error {

            print("Error took place \(error)")

            return

        }

        

        // Read HTTP Response Status code

        if let response = response as? HTTPURLResponse {

            print("Response HTTP Status code: \(response.statusCode)")

        }

        

        //convert HTTP data - json to struct

        let decoder = JSONDecoder()



        do {

            let shellys = try decoder.decode(shellyProperties.self, from: data!)

            shelly_dev = shellys

        } catch {

            print(error.localizedDescription)

        }

        

    }

    task.resume()

    return shelly_dev

}

the main issue that I am having is decoding the response data into a struct / class

Hmmm, looking at your code it’s clear why that’s happening. URLSession tasks are asynchronous. When you call resume the task starts and it doesn’t complete until some time in the future (once it’s got the response off the network). So your control flow runs like this:

  1. Someone calls GetShellyProp.

  2. It sets shelly_dev to the empty value.

  3. It starts a URLSession data task.

  4. It returns the empty shelly_dev value.

  5. At some point in the future the URLSession data task completes and sets shelly_dev, but no one is looking at that value.

The fundamental problem here is that you can’t return the results of an network request synchronously because the network request might block for long periods of time (minutes!). The fix is twofold:

  • You have to change getShellyProp(ip:) to run asynchronously.

  • You have to change its caller to do something meaningful during the delay.

The first part has a number of standard solutions:

  • You can add a completion handler to getShellyProp(ip:) that you call when it has a final ShellyProperties value.

  • You can use a Combine publisher for this.

  • If you’re not shipping immediately, you can use the new Swift concurrency feature. See WWDC 2021 Session 10192 What‘s new in Swift.

The second part of the fix is trickier because it depends on your app’s UI. If, for example, this network request takes 30 seconds, what do you want your app to be doing in the interim?


Oh, some other general suggestions…

If you’re working in Swift it’s important to follow Swift’s naming conventions. For example:

  • Type names are always InterCaps starting with an uppercase letter (so ShellyProperties).

  • Other identifies are InterCaps starting with a lowercase letter (so getShellyProp(ip:)).

  • And that includes variables (so shellyDev).

For more on this, see the Swift API Design Guidelines.

Also, try to avoid building URLs using string APIS because there are many ways that it can go wrong. A better option is URLComponents. For example:

var uc = URLComponents()
uc.scheme = "http"
uc.path = "/rpc/Shelly.GetInfo"
uc.host = ip
let url = uc.url!
print(url)
// prints: http://1.2.3.4/rpc/Shelly.GetInfo

Or you can use a string literal as a placeholder:

var uc = URLComponents(string: "http://placeholder/rpc/Shelly.GetInfo")!
uc.host = ip
let url = uc.url!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Swift - Http get request to class
 
 
Q