我可以制作自己的 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 背景,或者
- 因为它是在他们的计算机科学课程中教授的,或者
- 因为他们要从 Android
转向 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
时,会发生以下情况:
- 您访问getter
i
.
- getter
i
访问 self._i
,这是另一个 getter
- 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
我应该在 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 背景,或者
- 因为它是在他们的计算机科学课程中教授的,或者
- 因为他们要从 Android 转向 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
时,会发生以下情况:
- 您访问getter
i
. - getter
i
访问self._i
,这是另一个 getter - 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