我可以制作自己的 getter 和 setter 吗?

Can I make my own getters and setters and should I?

我应该在 Swift 中创建自己的 getter 和 setter 吗?我对内置的 getters 广告设置器感到困惑......这甚至需要吗?


    //properties for the resident
    private var name: String!
    var apartmentNumber: String!
    var email : String!
    var phoneNumber : String!


    public func getName()->String{
        return self.name
    }

    public func setName(name : String){
        self.name = name
    }
}

不需要为 Swift 中的存储属性创建 setters 和 getter,您也不应该创建它们。

您可以在声明 属性 时单独控制 getters/setters 的可访问性。

public private(set) var name: String // public getter, private setter

如果您想在 setter 中实现一些自定义逻辑,您应该使用 属性 观察器,即 didSet/willSet.

var name: String {
    didSet {
        // This is called every time `name` is set, so you can do your custom logic here
    }
}

我写了an article for exactly this。我会把它贴在这里。


停止在 Swift

中写入 getter 和 setter

看到这个我再看一遍,是时候在一个地方写一篇文章来巩固一下我的所有想法了。如果您发现自己编写的代码看起来像这样,请注意:

public class C {
    private var _i: Int = 0
    public var i: Int {
        get {
            return self._i
        }
        set {
            self._i = newValue
        }
    }
}

这种模式* 在Swift中完全没有意义,我会解释原因,但首先我们需要绕过Java土地。为什么 Java?因为大多数我 运行 这样写 Swift 的人都有某种 Java 背景,或者

  1. 因为它是在他们的计算机科学课程中教授的,或者
  2. 因为他们要从 Android
  3. 转向 iOS 开发

getters 和 setters 有什么意义?

假设我们在Java中有以下class:

public class WeatherReport {
    public String cityName;
    public double temperatureF;

    public WeatherReport(String cityName, double temperatureF) {
        this.cityName = cityName;
        this.temperatureF = temperatureF;
    }
}

如果您向任何 CS 教授展示此 class,他们肯定会因为您破坏了封装而对您大吼大叫。但这到底意味着什么?好吧,想象一下如何使用这样的 class 。有人会写一些看起来像这样的代码:

WeatherReport weatherReport = weatherAPI.fetchWeatherReport();
weatherDisplayUI.updateTemperatureF(weatherReport.temperatureF);

现在假设您想升级 class 以将数据存储在更合理的温度单位中(击败英制死马,我很有趣吗?),如摄氏度或开尔文。当您将 class 更新为如下所示时会发生什么:

public class WeatherReport {
    public String cityName;
    public double temperatureC;

    public WeatherReport(String cityName, double temperatureC) {
        this.cityName = cityName;
        this.temperatureC = temperatureC;
    }
}

您更改了 WeatherReport class 的实施细节,但您还进行了 API 重大更改。因为 temperatureF 是 public,所以它是 class' API 的一部分。现在你已经删除了它,你将在每个依赖于 temperatureF 实例变量的 exitense 的消费者中导致编译错误。

更糟糕的是,您更改了构造函数的第二个双参数的语义,不会导致编译错误,但 运行 处的行为错误时间(因为人们试图将基于华氏度的旧值用作摄氏值)。但是,这不是我将在本文中讨论的问题。

这里的问题是这个 class 的消费者将与您的 class 的实现细节紧密耦合。为了解决这个问题,你在你的实现细节和你的接口之间引入了一个分离层。假设我们的 class 的 Farenheit 版本是这样实现的:

public class WeatherReport {
    private String cityName;
    private double temperatureF;

    public WeatherReport(String cityName, double temperatureF) {
        this.cityName = cityName;
        this.temperatureF = temperatureF;
    }

    public String getCityName() {
        return this.cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public double getTemperatureF() {
        return this.temperatureF;
    }

    public void setTemperatureF(double temperatureF) {
        this.temperatureF = temperatureF;
    }
}

getter 和 setter 是访问或更新实例变量的真正基本方法。请注意这次,我们的实例变量是 private,只有 getter 和 setter 是 public。消费者将使用此代码,如下所示:

WeatherReport weatherReport = weatherAPI.fetchWeatherReport();
weatherDisplayUI.updateTemperatureF(weatherReport.getTemperatureF());

这一次,当我们升级到 celcius 时,我们可以自由更改我们的实例变量,并调整我们的 class 以保持向后兼容:

public class WeatherReport {
    private String cityName;
    private double temperatureC;

    public WeatherReport(String cityName, double getTemperatureC) {
        this.cityName = cityName;
        this.temperatureC = temperatureC;
    }

    public String getCityName() {
        return this.cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    // Updated getTemperatureF is no longer a simple getter, but instead a function that derives
    //  its Farenheit value from the Celcius value that actuallyed stored in an instance variable.
    public double getTemperatureF() {
        return this.getTemperatureC() * 9.0/5.0 + 32.0;
    }

    // Updated getTemperatureF is no longer a simple setter, but instead a function
    // that updates the celcius value stored in the instance variable by first converting from Farenheit
    public void setTemperatureF(double temperatureF) {
        this.setTemperatureC((temperatureF - 32.0) * 5.0/9.0);
    }

    // Mew getter, for the new temperatureC instance variable
    public double getTemperatureC() { 
        return this.temperatureC;
    }

    // New setter, for the new temperatureC instance variable
    public void setTemperatureC(double temperatureC) {
        this.temperatureC = temperatureC;
    }
}

我们添加了新的 getters 和 setters 以便新消费者可以处理摄氏温度。但重要的是,我们重新实现了用于 temperatureF(不再存在)的 getters 和 setters 的方法,以进行适当的转换并转发到 Celcius getters 和 setters。因为这些方法仍然存在,并且行为与以前相同,所以我们已经成功地进行了实现更改(存储 F 到存储 C),没有 破坏我们的 API。 API 的消费者不会注意到差异。

那么为什么这不能转化为 Swift?

确实如此。但简单地说,它已经为您完成了。你看,Swift 中存储的属性不是实例变量。事实上,Swift 并没有为您提供创建或直接访问实例变量的方法

要理解这一点,我们需要更全面地了解什么是属性。有两种类型,stored 和 computed,两者都不是"instance variables".

  • 存储的属性:是编译器合成的实例变量(您永远看不到、听不到、摸不到、尝到或闻到的)以及 getter 和 setter 的组合你用来和他们互动。
  • Computed proepties:只是getter和setter,没有任何实例变量作为后备存储。实际上,它们的行为就像 () -> T(T) -> Void 类型的函数,但具有令人愉快的点符号语法:

    print(weatherReport.temperatureC)
    weatherReport.temperatureC = 100
    

    而不是函数调用 synax:

    print(weatherReport.getTemperatureC())
    weatherReport.setTemperatureC(100)
    

所以事实上,当你写:

class C {
    var i: Int
}

i 是编译器为您创建的实例变量的 getter 和 setter 的名称。让我们调用实例变量 $i(这不是合法的 Swift 标识符)。没有办法直接访问$i。您只能通过调用 getter i 来获取它的值,或者通过调用它的 setter i.

来更新它的值

那么让我们看看 WeatherReport 迁移问题在 Swift 中是什么样子的。我们的初始类型如下所示:

public struct WeatherReport {
    public let cityName: String
    public let temperatureF: Double
}

消费者可以使用 weatherReport.temperatureF 访问温度。现在,这看起来像是对实例变量的直接访问,但请记住,这在 Swift 中根本不可能。相反,此代码调用编译器综合 getter temperatureF,这是访问实例变量 $temperatureF.

现在让我们升级到 Celcius。我们将首先更新我们存储的 属性:

public struct WeatherReport {
    public let cityName: String
    public let temperatureC: Double
}

这打破了我们的 API。新消费者可以使用temperatureC,但依赖temperatureF的老消费者将无法使用。为了支持它们,我们只需添加一个新的计算 属性,它会在摄氏度和华氏度之间进行转换:

public struct WeatherReport {
    public let cityName: String

    public let temperatureC: Double
    public var temperatureF: Double {
        get { return temperatureC * 9/5 + 32 }
        set { temperatureC = (newValue - 32) * 5/9 }
    }
}

因为我们的 WeatherReport 类型仍然有一个名为 temperatureF 的 getter,消费者的行为将和以前一样。他们无法判断他们访问的 属性 是存储的 属性 的 getter 还是以其他方式得出其值的计算 属性。

所以让我们看一下原始的 "bad" 代码。这有什么不好的?

public class C {
    private var _i: Int = 0
    public var i: Int {
        get {
            return self._i
        }
        set {
            self._i = newValue
        }
    }
}

当您调用 c.i 时,会发生以下情况:

  1. 您访问getter i.
  2. getter i 访问 self._i,这是另一个 getter
  3. getter_i访问"hidden"实例变量$i

setter 也类似。你有两层"getterness"。看看 Java:

会是什么样子
public class C {
    private int i;

    public C(int i) {
        this.i = i;
    }

    public int getI1() {
        return this.i;
    }

    public void setI1(int i) {
        this.i = i;
    }

    public int getI2() {
        return this.getI1();
    }

    public void setI2(int i) {
        this.setI1(i);
    }
}

太傻了!

但是如果我想要一个私人的 setter 怎么办?

而不是这样写:

public class C {
    private var _i: Int = 0

    public var i: Int {
        get {
            return self._i
        }
    }
}

您可以使用这个巧妙的语法,为 setter:

指定单独的访问级别
public class C {
    public private(set) var i: Int = 0
}

现在不是很干净吗?

您几乎不会发现需要创建自己的 getter 和 setter。 Swift 的计算 属性 允许您以非常简单的方式使用 getters 和 setters。

例如:下面定义的是计算的 属性 circleArea,returns 圆的面积取决于半径。

var radius: Float = 10

var circleArea: Float {
get {
    return .pi * powf(radius, 2)
}
set {
    radius = sqrtf(newValue / .pi)
}

}

虽然您可以观察存储值并使用 属性 观察者执行某些任务:

var radius: Float = 10 {
willSet {
    print("before setting the value: \(value)")
}
didSet {
    print("after the value is set: \(value)")
}

}

radius += 1 

//设置前值:10.0 // 设置值后:11.0

不过,如果您想使用 getter setter,您可以为此定义适当的函数。下面定义的是 Integer 的扩展,用于获取和设置属性值。

extension Int {
func getValue() -> Int {
    return self
}
mutating func setValue(_ val: Int) {
    self = val
}

}

var aInt: Int = 29
aInt.getValue()
aInt.setValue(45)
print(aInt)

// aInt = 45