已经完成了所有关于Swift基础的部分,感觉东西说多不多,说少也确实有一些部分理解起来有一些困难。从前天开始的三天为快速回顾(因为是快速回顾名,我只是浏览一遍唤醒记忆,直接摘录机器翻译,以后再人工校正):

属性

结构和类(统称为“类型”)可以具有自己的变量和常量,这些称为属性。这些使你可以将值附加到类型上以唯一表示它们,但是由于类型也可以具有方法,因此你可以使它们根据自己的数据来运行。

现在让我们看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Person {
var clothes: String
var shoes: String

func describe() {
print("I like wearing \(clothes) with \(shoes)")
}
}

let taylor = Person(clothes: "T-shirts", shoes: "sneakers")
let other = Person(clothes: "short skirts", shoes: "high heels")
taylor.describe()
other.describe()

如你所见,当在方法内部使用属性时,它将自动使用属于同一对象的值。

监听变量变化

使用Swift,你可以添加要在属性将要更改或已更改时运行的代码。例如,这通常是在值更改时更新用户界面的好方法。

属性观察者有两种:willSet和didSet,它们在属性改变之前或之后被调用。在willSetSwift中,你的代码提供了一个称为的特殊值newValue,其中包含新属性值将要成为的值,并在didSet其中提供oldValue了代表先前值的值。

让我们将两个属性观察器附加到结构的clothes属性上Person:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Person {
var clothes: String {
willSet {
updateUI(msg: "I'm changing from \(clothes) to \(newValue)")
}

didSet {
updateUI(msg: "I just changed from \(oldValue) to \(clothes)")
}
}
}

func updateUI(msg: String) {
print(msg)
}

var taylor = Person(clothes: "T-shirts")
taylor.clothes = "short skirts"

这将打印出以下消息:“我正在从T恤变为短裙”和“我刚刚从T恤变为短裙”。

计算属性

可以制作实际上是在幕后编码的属性。uppercased()例如,我们已经使用了字符串的方法,但是还有一个称为的属性capitalized ,可以根据需要进行计算,而不是每个字符串都始终存储其自身的大写形式。

要创建计算属性,请在属性后放置一个大括号,然后使用get或set在适当的时间进行操作。例如,如果我们想添加一个ageInDogYears属性,该属性会自动返回一个人的年龄乘以7,那么我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
struct Person {
var age: Int

var ageInDogYears: Int {
get {
return age * 7
}
}
}

var fan = Person(age: 25)
print(fan.ageInDogYears)

计算属性在Apple代码中越来越普遍,而在用户代码中则越来越少。

注意:
如果打算仅将它们用于读取数据,则可以完全删除该get部分,如下所示:

1
2
3
var ageInDogYears: Int {
return age * 7
}

静态属性

使用Swift,你可以创建属于类型而不是类型实例的属性和方法。这有助于通过存储共享数据来有意义地组织数据。

Swift将这些共享属性称为“静态属性”,而你只需使用static关键字即可创建一个。完成后,你可以使用类型的全名来访问属性。这是一个简单的例子:

1
2
3
4
5
6
7
8
9
struct TaylorFan {
static var favoriteSong = "Look What You Made Me Do"

var name: String
var age: Int
}

let fan = TaylorFan(name: "James", age: 25)
print(TaylorFan.favoriteSong)

因此,泰勒·斯威夫特(Taylor Swift)粉丝的名字和年龄属于他们,但他们都喜欢同一首歌。

因为静态方法属于该结构本身而不是该结构的实例,所以你不能使用它来访问该结构中的任何非静态属性。

访问控制

访问控制使你可以指定结构和类中的哪些数据应该公开给外界,并且可以选择四个修饰符:

  • 公开:这意味着每个人都可以读写该属性。
  • 内部:这意味着只有你的Swift代码可以读取和写入属性。如果你将代码作为框架供其他人使用,则其他人将无法读取该属性。
  • 文件私有:这意味着只有与该类型相同的文件中的Swift代码才能读取和写入属性。
  • 私有:这是限制性最强的选项,意味着该属性仅在属于该类型或其扩展名的方法内部可用。

大多数时候,你不需要指定访问控制,但是有时你会希望将属性显式设置为私有,因为它阻止其他人直接访问它。这很有用,因为你自己的方法可以使用该属性,而其他方法则不能,从而迫使它们通过你的代码来执行某些操作。

要声明私有属性,只需执行以下操作:

1
2
3
class TaylorFan {
private var name: String?
}

如果要使用“文件私有”访问控制,只需将其写为一个单词,如下所示:fileprivate。

多态和类型转换

因为类可以彼此继承(例如,CountrySinger可以从继承Singer),这意味着一个类实际上是另一个类的超集:类B具有A拥有的所有东西,还有一些额外的东西。反过来,这意味着你可以根据需要将B视为B型或A型。

困惑?让我们尝试一些代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Album {
var name: String

init(name: String) {
self.name = name
}
}

class StudioAlbum: Album {
var studio: String

init(name: String, studio: String) {
self.studio = studio
super.init(name: name)
}
}

class LiveAlbum: Album {
var location: String

init(name: String, location: String) {
self.location = location
super.init(name: name)
}
}

定义了三个类别:专辑,录音室专辑和现场专辑,后两个类别都继承自Album。因为LiveAlbum继承自的任何实例都Album可以被视为Album或LiveAlbum-两者同时处理。这称为“多态”,但是它意味着你可以编写如下代码:

1
2
3
4
5
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

在那里,我们创建了一个仅容纳专辑的数组,但在其中放置了两个工作室专辑和一个现场专辑。在Swift中,这非常好,因为它们都是Album类的后代,因此它们具有相同的基本行为。

我们可以进一步推动这一步,以真正展示多态性的工作原理。让我们getPerformance()为所有三个类添加一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Album {
var name: String

init(name: String) {
self.name = name
}

func getPerformance() -> String {
return "The album \(name) sold lots"
}
}

class StudioAlbum: Album {
var studio: String

init(name: String, studio: String) {
self.studio = studio
super.init(name: name)
}

override func getPerformance() -> String {
return "The studio album \(name) sold lots"
}
}

class LiveAlbum: Album {
var location: String

init(name: String, location: String) {
self.location = location
super.init(name: name)
}

override func getPerformance() -> String {
return "The live album \(name) sold lots"
}
}

该getPerformance()方法存在于Album类中,但是两个子类都将其覆盖。当我们创建一个保存的数组时,Albums实际上是在用专辑的子类填充它:LiveAlbum和StudioAlbum。因为它们从Album类继承而来,所以它们进入数组就很好了,但是它们永远不会丢失其原始类。因此,我们可以编写如下代码:

1
2
3
4
5
6
7
8
9
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

for album in allAlbums {
print(album.getPerformance())
}

这将自动使用覆盖版本,getPerformance()具体取决于相关子类。这就是行动中的多态性:一个对象可以同时充当其类和其父类。

使用类型转换转换类型
你通常会发现你有某种类型的对象,但实际上你知道它是另一种类型。可悲的是,如果Swift不知道你所知道的,它将无法构建你的代码。因此,有一个解决方案,称为类型转换:将一种类型的对象转换为另一种类型。

你可能会想一想为什么可能需要这样做,但是我可以举一个非常简单的例子:

1
2
3
for album in allAlbums {
print(album.getPerformance())
}

那是几分钟前的循环。该allAlbums数组保存类型Album,但我们知道子类的,真正它所持有一个:StudioAlbum或LiveAlbum。Swift不知道这一点,因此,如果你尝试编写类似的东西print(album.studio),它将被拒绝构建,因为只有StudioAlbum对象才具有该属性。

Swift中的类型转换有三种形式,但是大多数时候你只会遇到两种形式:as?和as!,称为可选向下转换和强制向下转换。前者的意思是“我认为此转换可能是正确的,但可能会失败”,第二个含义是“我知道此转换是正确的,如果我错了,我很高兴我的应用程序崩溃。”

注意:当我说“转换”时,我并不是说该对象实际上已经转换了。取而代之的是,它只是在转换Swift如何对待对象的方式–你是在告诉Swift认为它是A型的对象实际上是E型。

问号和感叹号应提示你正在发生的事情,因为这与可选区域非常相似。例如,如果你编写此代码:

1
2
3
for album in allAlbums {
let studioAlbum = album as? StudioAlbum
}

Swift将使studioAlbum具有数据类型StudioAlbum?。就是说,有一个可选的录音室专辑:转换可能有效,在这种情况下,你有可以使用的录音室专辑,或者转换失败了,在这种情况下你没有空。

这是最常用于if let自动展开可选结果的方法,如下所示:

1
2
3
4
5
6
7
8
9
for album in allAlbums {
print(album.getPerformance())

if let studioAlbum = album as? StudioAlbum {
print(studioAlbum.studio)
} else if let liveAlbum = album as? LiveAlbum {
print(liveAlbum.location)
}
}

这将遍历每张专辑并打印其演奏细节,因为那是Album该类及其所有子类所共有的。然后,它检查是否可以将album值转换为StudioAlbum,是否可以打印出工作室名称。LiveAlbum对数组中的进行相同的操作。

强制向下转换是在你确实确定一种类型的对象可以视为另一种类型时,但是如果你错了,则程序将崩溃。强制向下转换不需要返回可选值,因为你说的是转换肯定会起作用–如果输入错误,则表示你编写的代码错误。

为了以一种非狂妄的方式展示这一点,让我们去掉现场专辑,这样我们就可以在阵列中拥有工作室专辑:

1
2
3
4
5
6
7
8
9
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")

var allAlbums: [Album] = [taylorSwift, fearless]

for album in allAlbums {
let studioAlbum = album as![]StudioAlbum
print(studioAlbum.studio)
}

显然,这是一个人为的示例,因为如果确实是你的代码allAlbums,则只需进行更改,使其具有数据类型[StudioAlbum]。它仍然显示了强制向下转换的工作方式,并且该示例不会崩溃,因为它做出了正确的假设。

Swift可让你在数组循环中进行向下转换,这样可以提高效率。如果要在阵列级别编写强制降级,则应编写以下代码:

1
2
3
for album in allAlbums as![][StudioAlbum] {
print(album.studio)
}

不再需要向下转换循环中的每个项目,因为它在循环开始时发生。再一次,你最好正确设置数组中的所有项目均为StudioAlbums,否则你的代码将崩溃。

Swift还允许在数组级别进行可选的向下转换,尽管这有点棘手,因为你需要使用nil合并运算符来确保循环始终有一个值。这是一个例子:

1
2
3
for album in allAlbums as? [LiveAlbum] ?? [LiveAlbum]() {
print(album.location)
}

这意味着“尝试转换allAlbums为LiveAlbum对象数组,但是如果失败,则只需创建一个空数组的实时相册并使用它来代替” –即,什么也不做。

使用初始化程序转换常见类型
当你知道Swift不了解的内容时(例如,当你拥有ASwift认为实际上是type 的类型的对象时),类型转换非常有用B。但是,仅当这些类型确实是你所说的类型时,类型转换才有用– 如果它们实际上不相关,则不能A将类型强制为类型Z。

例如,如果你有一个名为的整数number,则无法编写如下代码使其成为字符串:

1
2
let number = 5
let text = number as![]String

也就是说,你不能将整数强制为字符串,因为它们是两种完全不同的类型。相反,你需要通过将整数提供给它来创建一个新字符串,而Swift知道如何将两者转换。区别是微妙的:这是一个新值,而不仅仅是对相同值的重新解释。

因此,该代码应这样重写:

1
2
3
let number = 5
let text = String(number)
print(text)

这仅适用于Swift的某些内置数据类型:例如,你可以将整数和浮点数转换为字符串,然后再次返回,但是,如果你创建了两个自定义结构,Swift无法神奇地将一个转换为另一个-你需要编写自己编写代码。

闭包

到目前为止,你已经遇到了整数,字符串,双精度数,浮点数,布尔值,数组,字典,结构和类,但是Swift中还广泛使用了另一种数据类型,这称为闭包。这些很复杂,但是它们是如此强大和富有表现力,以至于它们在Cocoa Touch中得到了广泛使用,因此如果不了解它们,你将走得很远。

可以将闭包视为保存代码的变量。因此,在整数保存0或500的情况下,闭包保存Swift代码行。闭包还捕获创建它们的环境,这意味着它们将复制其中使用的值。

你无需设计自己的闭包,因此如果发现以下内容非常复杂,请不要担心。但是,Cocoa和Cocoa Touch都经常会要求你编写闭包来满足他们的需求,因此你至少需要知道它们的工作原理。让我们首先以可可粉为例:

1
2
3
4
5
let vw = UIView()

UIView.animate(withDuration: 0.5, animations: {
vw.alpha = 0
})

UIView是UIKit中的iOS数据类型,代表最基本的一种用户界面容器。不必担心它现在会做什么,重要的是它是基本的用户界面组件。UIView有一个名为的方法animate(),它可以让你使用动画来更改界面的外观-你描述正在发生的变化以及经过几秒钟的时间,剩下的时间由Cocoa Touch完成。

该animate()方法在该代码中采用两个参数:要进行动画处理的秒数,以及一个包含要作为动画一部分执行的代码的闭包。我已经将半秒指定为第一个参数,对于第二个参数,我要求UIKit将视图的Alpha(即不透明度)调整为0,表示“完全透明”。

此方法需要使用闭包,因为UIKit必须做各种工作来为动画开始做准备,所以发生的事情是UIKit在花括号(这就是我们的闭包)中获取了代码的副本,将其存储起来,做了完成所有准备工作,然后在准备就绪时运行我们的代码。如果我们直接运行代码,这将是不可能的。

上面的代码还显示了闭包如何捕获其环境:我vw在闭包外部声明了常量,然后在内部使用了它。Swift会检测到这一点,并使该数据在闭包内部也可用。

Swift的自动捕获闭包环境的系统非常有帮助,但偶尔会绊倒你:如果对象A将闭包存储为属性,并且该属性还引用对象A,则你将拥有一个称为“强引用周期”的东西,不满意的用户。这是一个比你现在需要知道的要高级得多的主题,因此暂时不必太担心。

尾随封口

由于闭包的使用频率很高,因此Swift可以使用一些语法糖来使你的代码更易于阅读。规则是这样的:如果方法的最后一个参数采用了闭包,则可以消除该参数,而将其作为大括号内的代码块提供。例如,我们可以将之前的代码转换为此:

1
2
3
4
5
let vw = UIView()

UIView.animate(withDuration: 0.5) {
vw.alpha = 0
}

它的确使你的代码更短,更易于阅读,因此首选这种语法形式(称为尾随闭包语法)。

参考资料

查看下一天的SwiftUI学习笔记

关于100days英文课程