【问题标题】:Array of objects cannot be modified when passed through a function?通过函数传递时不能修改对象数组?
【发布时间】:2019-08-07 05:04:34
【问题描述】:

我创建了一个名为 Weather 的自定义类并声明了一个 Weather 对象数组。

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()
    }

}

我将数组传递给一个函数,并将值附加到 weatherArrays 参数。但是,当我编译时出现错误,“不能在不可变值上使用变异成员:'weatherArrays' 是一个 'let' 常量。”

Weather 类最初是一个结构,但我遇到了同样的错误,我阅读并发现结构值无法在函数中编辑,因为它是按值传递的。 我将结构更改为一个类,但我仍然遇到同样的错误?当我将weatherArrays 声明为var 时,为什么它说“'weatherArrays' 是'let' 常量”?

【问题讨论】:

  • 不要尝试修改传入的数组。使用完成处理程序返回一个新数组。有关使用完成处理程序的详细信息,请参阅stackoverflow.com/questions/25203556/…
  • 如果你在全局范围内定义了任何东西(比如weatherArrays),你不需要将它作为参数传递

标签: swift class struct constants


【解决方案1】:

这是您的代码的更好方法

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()
    }

}

模型用struct,我们不用写init方法

当您知道数据不会被更改时,使用 let 而不是 var

不要使用 NSDictionary。

您在调用函数后立即编写了打印语句,因为只有在调用服务器完成后才会填充数组。所以我使用了完成处理程序

我看到您已经安装了 SwiftyJSON,但您实际上并没有使用它的好处。看解析部分。

关于您遇到的错误是因为 Swift 是按值传递的,即当您传递数组对象时,您实际上传递的是它的副本,而不是实际的数组。如果你想修改同一个数组,你需要我们inout。 that 可以找到一个很棒的教程

编辑:正如@rmaddy 所建议的那样,通过返回一个新数组来使代码更安全。请参阅他的评论以获取更多信息。

【讨论】:

  • 实际上,更好的方法是创建并返回(通过完成处理程序)一个仅包含新条目的新数组。然后让调用者对新值做任何需要的事情。在这种情况下,可能是将它们添加到现有数组中。它使代码更加模块化。它还使代码更安全,因为现在您的代码正在后台修改数组,而其他一些代码可能正在尝试使用主队列上的数组。这可能会导致崩溃。
  • 另一个更新建议。让完成处理程序提供一个可选数组。然后有错误时可以返回nil。然后调用者可以区分“无数据”和“错误”。
  • @SahilManchanda 另一个建议。使用URLComponents 而不是硬编码网址。 var components = URLComponents() components.scheme = "http" components.host = "api.openweathermap.org" components.path = "/data/2.5/weather" components.queryItems = [URLQueryItem(name: "zip", value: zipCode), URLQueryItem(name: "appID", value: appID), URLQueryItem(name: "units", value: "imperial")] guard let url = components.url else { return }
【解决方案2】:

首先,正如 rmaddy 在评论中所说,您不应该这样做。但是如果您仍然想知道如何使传递的参数可变,下面是一个示例。

// 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)

来自 Swift 文档的注释

函数参数默认为常量。尝试从该函数的主体内更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为 in-out 参数。

有关更多信息,请参阅此链接。 https://docs.swift.org/swift-book/LanguageGuide/Functions.html

【讨论】:

  • 由于问题中调用的异步性质,这不是一个好的解决方案。
  • @rmaddy 你已经在 OP 的评论中提供了解决方案,我只是想解释他对如何使传递的参数可变的另一个担忧
  • @Vikky 但是 OP 的 findLocation 包含一个异步 API 调用。所以即使他使用你的解决方案print(weatherArrays[0].cityName) 也会崩溃
  • @RajeshKumarR 我认为你评论的地方不对,这不是我的解决方案
  • @Vikky 您的解决方案适用于不包含任何异步 API 调用的普通方法。但在这个问题上它会崩溃
【解决方案3】:

因为当你在函数中传递一个参数时,它总是传递 let 类型。输入以下代码可能有帮助

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()
    }

}

【讨论】:

  • 这仍然有问题。 print(weatherArrays[0].cityName) 行将在任何数据添加到数组之前很久就被调用。
  • 去掉打印行,获取数据后添加
猜你喜欢
  • 1970-01-01
  • 2020-08-11
  • 1970-01-01
  • 2015-07-10
  • 2010-12-21
  • 1970-01-01
  • 2021-12-27
  • 2021-01-01
  • 1970-01-01
相关资源
最近更新 更多