在上一章中,我们学习了属性,它是结构的常数和变量。方法,是结构中的函数。
现在,你将进一步了解方法和初始化器。与属性一样,你将开始设计更复杂的结构。在本章中学习的内容将适用于所有命名类型的方法,包括类和枚举,你将在后面的章节中看到。
方法复习
记得Array.removeLast()?它会删除一个数组实例的最后一个元素:
var numbers = [1, 2, 3]
numbers.removeLast()
numbers // [1, 2]
QQ20180518-142632@2x.png
像removeLast()这样的方法可以帮助你控制结构中的数据。
比较 方法和计算属性
通过计算属性,你可以从结构内部运行代码。这听起来很像一种方法。有什么区别呢?这实际上是一种风格的问题,但有一些好的想法可以帮助你做出决定。属性包含你可以得到和设置的值,而方法执行工作。
有时,当方法的唯一目的是返回一个值时,这种区分会变得模糊。
QQ20180518-142928@2x.png
问问自己,你是否希望能够设置一个值,并得到值。计算属性可以在内部设置一个setter组件来写入值。另一个需要考虑的问题是计算是否需要大量的计算或从数据库读取。即使对于一个简单的值,一个方法在调用时间和计算资源上是昂贵的。所以如果调用的代价很小(如常量时间O(1)),则使用计算属性。
将函数转换为方法
为了探索方法和初始化器,你创建一个简单的名为SimpleDate的模型。请注意,苹果的基础库包含一个健壮的的日期类,它能正确地处理日期和时间。
在下面的代码中,你如何将monthsUntilWinterBreak(date:) 转换为一个方法?
let months = ["January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"]
struct SimpleDate {
var month: String
}
func monthsUntilWinterBreak(from date: SimpleDate) -> Int {
return months.index(of: "December")! - months.index(of: date.month)!
}
制作一个方法就像在结构定义中移动函数一样简单:
struct SimpleDate {
var month: String
func monthsUntilWinterBreak(from date: SimpleDate) -> Int {
return months.index(of: "December")! - months.index(of: date.month)!
}
}
方法中没有标识关键字;它实际上只是一个命名类型中的函数。使用点语法调用方法,就像处理属性一样。就像属性一样,当你开始键入一个方法名称时,Xcode将提供补全。你可以用键盘上的上下箭头键来选择一个,你可以通过按Tab自动完成:
QQ20180518-145521@2x.pnglet date = SimpleDate(month: "October")
date.monthsUntilWinterBreak(from: date) // 2
如果你仔细思考一下这个代码,你会发现该方法的定义是笨拙的。必须有一种方法来访问实例存储的内容,而不是将实例本身作为参数传递给方法。如果我们这样调用它更好:
date.monthsUntilWinterBreak() // Error!
介绍 self
结构定义就像一个蓝图,而实例是一个真实的对象。要访问实例的值,可以在结构中使用关键字self。Swift编译器将它作为秘密参数传递给你的方法。方法定义转换为:
// 1
func monthsUntilWinterBreak() -> Int {
// 2
return months.index(of: "December")! - months.index(of: self.month)!
}
这就是改变:
1,方法定义中没有参数。
2,在实现中,self将替换旧的参数名。
现在你可以调用该方法而无需传递参数:
date.monthsUntilWinterBreak() // 2
看起来干净多了! 你还可以做一件事来简化代码,消除 self .。
self是你对实例的引用,但是大多数情况下你不需要使用它,因为如果你只是使用变量名,Swift会理解你的意图。虽然你可以一直使用self来访问当前实例的属性和方法,但大多数情况下你不需要这样做。在冬天,你可以只说month而不是self.month:
return months.index(of: "December")! - months.index(of: month)!
大多数程序员只在需要时才使用self,例如,在局部变量和同名属性之间消除歧义。稍后你会得到更多的练习。
初始化器
在前面的章节中,你已经了解了初始化器,但是让我们再来看看这些方法的新知识。
初始化器是用来创建新实例的特殊方法。它们省略了func关键字甚至名称。相反,他们使用init。初始化器可以有参数,也可以不要。
现在,当你创建SimpleDate结构的新实例时,你必须为month属性指定一个值:
let date = SimpleDate(month: "January")
你可能会发现有一个方便的无参数初始化器更有效。可以创建一个具有合理默认值的新SimpleDate实例:
let date = SimpleDate() // Error!
虽然编译器现在给你一个错误,但是你可以提供无参数初始化器。通过实现init,可以使用默认值创建最简单的初始化方法:
struct SimpleDate {
var month: String
init() {
month = "January"
}
func monthsUntilWinterBreak() -> Int {
return months.index(of: "December")! - months.index(of: month)!
}
}
下面是代码中发生的事情:
1 init()定义既不需要func关键字,也不需要名称。使用类型的名称来调用初始化器。
2 像一个函数一样,初始化器必须有一个参数列表,即使它是空的。
3 在初始化器中,为结构的所有存储属性赋值。
4 初始化器永远不会返回值。它的任务只是初始化一个新实例。
现在你可以使用简单的初始化器来创建实例:
let date = SimpleDate()
date.month // January
date.monthsUntilWinterBreak() // 11
您可以在初始化器中测试更改的值:
init() {
month = "March"
}
冬季的值将因此而改变:
let date = SimpleDate()
date.month // March
date.monthsUntilWinterBreak() // 9
考虑到这里的实现,良好的用户体验优化将初始化器使用今天日期作为默认值。
以后,你能够检索当前日期,可以使用Foundation库中的Date类来处理日期。在你获得这些库提供的所有功能之前,让我们继续实现你自己的SimpleDate类型。
结构的初始化
初始化器确保实例在准备使用之前设置所有属性:
struct SimpleDate {
var month: String
var day: Int
init() {
month = "January"
day = 1
}
func monthsUntilWinterBreak() -> Int {
return months.index(of: "December")! - months.index(of: month)!
}
}
如果你试图在不设置day属性的情况下创建初始化器,那么编译器会抱怨。
通过创建一个自定义初始化器,你放弃了使用自动成员初始化器的选项。回想一下,自动生成的成员初始化器接受所有的属性作为参数,比如SimpleDate结构的init(month:day:)。当你编写一个自定义初始化器时,编译器会将自动创建的初始化丢弃。
所以这段代码现在无法运行:
let valentinesDay = SimpleDate(month: "February", day: 14) // Error!
然后, 你必须定义自己的初始化器参数:
init(month: String, day: Int) {
self.month = month
self.day = day
}
在该代码中,你将传入的参数分配给结构的属性。注意,self是如何用来告诉编译器你在引用属性而不是本地参数。
但是这在简单的初始化器中是没有必要的:
init() {
month = "January"
day = 1
}
在该代码中,没有具有与属性相同名称的任何参数。因此,编译器不需要self来理解你所指的属性。
有了这个初始化器,你可以像调用自动生成的初始化器一样调用新的初始化器:
let valentinesDay = SimpleDate(month: "February", day: 14)
valentinesDay.month // February
valentinesDay.day // 14
网友评论