bhl1994
bhl1994

Reputation: 1

Array of objects cannot be modified when passed through a function?

I created a custom class called Weather and declared an array of Weather objects.

import Foundation

class Weather {
    var cityName:String
    var temperature:Double
    var temperatureMax:Double
    var temperatureMin:Double

    init(cityName: String, temperature: Double, temperatureMax: Double, temperatureMin: Double) {

        self.cityName = cityName
        self.temperature = temperature
        self.temperatureMax = temperatureMax
        self.temperatureMin = temperatureMin

    }
}

import UIKit
import SwiftyJSON

class ViewController: UIViewController {
    @IBOutlet weak var myLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        var weatherArrays = [Weather]()
        findLocation(zipCode: "11210", weatherArrays: weatherArrays)
        print(weatherArrays[0].cityName)
    }

    func findLocation(zipCode: String, weatherArrays: [Weather])
    {
        let zip = zipCode
        let appID = "245360e32e91a426865d3ab8daab5bf3"
        let urlString = "http://api.openweathermap.org/data/2.5/weather?zip=\(zip)&appid=\(appID)&units=imperial"
        let request = URLRequest(url: URL(string: urlString)!)
        URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            do
            {
                let json = try JSONSerialization.jsonObject(with: data!) as! NSDictionary
                let main = json["main"] as! [String:Any]
                let temp = main["temp"]! as! Double
                let name = json["name"]! as! String
                let tempMax = main["temp_max"]! as! Double
                let tempMin = main["temp_min"]! as! Double
                weatherArrays.append(Weather(cityName: name, temperature: temp, temperatureMax: tempMax, temperatureMin: tempMin))
            }
            catch
            {
                print("Error")
            }

            }.resume()
    }

}

I pass the array into a function and I append the values to the weatherArrays parameter. However, when I compile I get the error, "Cannot use mutating member on immutable value: 'weatherArrays' is a 'let' constant."

The Weather class was originally a struct but I got this same error and I read up and found that struct values cannot be edited in a function because it is pass by value. I changed the struct to a class and I am still getting this same error? Why is it saying "'weatherArrays' is a 'let' constant" when I declared weatherArrays as a var?

Upvotes: 0

Views: 298

Answers (3)

Sahil Manchanda
Sahil Manchanda

Reputation: 10012

Here is a better Approach for your code

import UIKit
import SwiftyJSON

struct Weather {
    let cityName:String
    let temperature:Double
    let temperatureMax:Double
    let temperatureMin:Double
}
class ViewController: UIViewController {

    @IBOutlet weak var myLabel: UILabel!

    var array = [Weather]()
    let appID = "245360e32e91a426865d3ab8daab5bf3"

    override func viewDidLoad() {
        super.viewDidLoad()

        findLocation(zipCode: "11210"){ array in
            guard let array = array else {
                print("Error")
                return
            }
            print(array.first?.cityName ?? "no city name found")
        }

    }

    func buildUrl(queryItems: [URLQueryItem]) -> URL?{
        var components = URLComponents()
        components.scheme = "http"
        components.host = "api.openweathermap.org"
        components.path = "/data/2.5/weather"
        components.queryItems = queryItems
        return components.url
    }

    func findLocation(zipCode: String, completionHandler: @escaping (_ array: [Weather]?) -> ()){

        guard let url = buildUrl(queryItems: [URLQueryItem(name: "zip", value: zipCode), URLQueryItem(name: "appID", value: appID), URLQueryItem(name: "units", value: "imperial")]) else {
            print("Error in building url")
            return
        }

        let request = URLRequest(url: url)
        URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            guard let data = data else {
                print(error?.localizedDescription ?? "")
                completionHandler(nil)
                return
            }
            do{
                var array = [Weather]()
                let json = try JSON(data: data)
                if json["cod"].intValue == 200{
                    let main = json["main"]
                    let temp = main["temp"].doubleValue
                    let name = json["name"].stringValue
                    let tempMax = main["temp_max"].doubleValue
                    let tempMin = main["temp_min"].doubleValue
                    array.append(Weather(cityName: name, temperature: temp, temperatureMax: tempMax, temperatureMin: tempMin))
                    completionHandler(array)
                }else{
                    completionHandler(nil)
                }

            } catch let error{
                print(error)
            }
            }.resume()
    }

}

Use struct for models, we don't have to write init method

Use let instead of var when you know that the data is not going to be changed

Do not use NSDictionary.

You've written print statement right after calling the function where as the array would've been filled only after the call to server is completed. so I have used completion handler

I saw you have installed SwiftyJSON but you weren't actually using the benefits of it. Look at the parsing section.

About the error you were getting is because Swift is pass by value i.e. when you are passing array object you were actually passing a copy of it not the actual array. if you want the same array to be modified, you need to us inout. A great tutorial can be found for that

Edit: As suggested by @rmaddy to make the code much safer by returning a new array. Please see his comment for more information.

Upvotes: 2

Ashish
Ashish

Reputation: 726

Because when you pass a parameter in the function it always passes let type. Type following code maybe it's help

import Foundation

class Weather {
    var cityName:String
    var temperature:Double
    var temperatureMax:Double
    var temperatureMin:Double

    init(cityName: String, temperature: Double, temperatureMax: Double, temperatureMin: Double) {

        self.cityName = cityName
        self.temperature = temperature
        self.temperatureMax = temperatureMax
        self.temperatureMin = temperatureMin

    }
}

import UIKit
import SwiftyJSON

class ViewController: UIViewController {

    var weatherArrays = [Weather]()
    @IBOutlet weak var myLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        findLocation(zipCode: "11210")

    }

    func findLocation(zipCode: String)
    {
        let zip = zipCode
        let appID = "245360e32e91a426865d3ab8daab5bf3"
        let urlString = "http://api.openweathermap.org/data/2.5/weather?zip=\(zip)&appid=\(appID)&units=imperial"
        let request = URLRequest(url: URL(string: urlString)!)
        URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            do
            {
                let json = try JSONSerialization.jsonObject(with: data!) as! NSDictionary
                let main = json["main"] as! [String:Any]
                let temp = main["temp"]! as! Double
                let name = json["name"]! as! String
                let tempMax = main["temp_max"]! as! Double
                let tempMin = main["temp_min"]! as! Double
                self.weatherArrays.append(Weather(cityName: name, temperature: temp, temperatureMax: tempMax, temperatureMin: tempMin))
            }
            catch
            {
                print("Error")
            }

            }.resume()
    }

}

Upvotes: 0

Vikky
Vikky

Reputation: 936

First of all as rmaddy said in comment you should not do this.But if you are still interested to know how to make passed parameter mutable, below is an example.

// function should accept parameter as inout
func test(arr: inout [String]){
arr.append("item3")
print(arr) // ["item1", "item2", "item3"]
}
// you should pass mutable array
var item = ["item1", "item2"]
test(arr: &item)

Note from Swift doc

Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.

For more info refer to this link. https://docs.swift.org/swift-book/LanguageGuide/Functions.html

Upvotes: 0

Related Questions