最初,类看起来与结构体非常相似,因为我们使用它们来创建具有属性和方法的新数据类型。但是,它们引入了一个新的,重要的且复杂的功能,即继承——使一个类在另一个类的基础上构建的能力。
这是一项强大的功能,毫无疑问,当您开始构建真实的iOS应用程序时,也无法避免使用类。但是请记住要保持代码简单:仅因为功能存在,并不意味着您需要使用它。正如马丁·福勒(Martin Fowler)所说,“任何傻瓜都可以编写计算机可以理解的代码,但是优秀的程序员可以编写人类可以理解的代码。(any fool can write code that a computer can understand, but good programmers write code that humans can understand.)”
我已经说过,SwiftUI在其UI设计中广泛使用结构体。嗯,它在数据中广泛使用类:当您在屏幕上显示某个对象的数据,或者在布局之间传递数据时,通常会使用类。
我应该补充一点:如果您以前曾经使用过UIKit,这对您来说将是一个不一样的转变——在UIKit中,我们通常使用类进行UI设计并使用结构进行数据处理。因此,如果您认为您可以在这里跳过一天,那么抱歉,您不能这样做:这是必需的。
今天,您有8个一分钟的视频可供观看,并且您将遇到方法重写,最终类和反初始化器等问题。观看完每个视频后,我们会进行一次简短的测试,以帮助您了解所教的内容。
1. 创建自己的类 Creating your own classes – test
类与结构体相似,它们使您可以使用属性和方法创建新类型,但是它们有五个重要的区别,我将逐步介绍每个区别。
类和结构体之间的是类永远不会带有成员初始化器。这意味着,如果您的类中有属性,则必须创建自己的初始化程序。
例如:
class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
创建该类的实例看起来就像是一个结构体一样:
let poppy = Dog(name: "Poppy", breed: "Poodle")
2. 类继承 Class inheritance – test
类和结构之间的是,您可以基于现有类创建一个类——它继承了原始类的所有属性和方法,并且可以在其顶部添加自己的属性。
这称为类继承或子类,您从其继承的类称为“父”或“超级”类,新类称为“子”类。
我们刚刚创建了Dog
类,我们可以基于一个名为Poodle
的类创建一个新类。默认情况下,它将继承与Dog
相同的属性和初始化程序:
class Poodle: Dog {
}
但是,我们也可以给Poodle
提供自己的初始化程序。我们知道它将始终具有品种“ Poodle”,因此我们可以创建一个仅需要name
属性的新初始化程序。更好的是,我们可以使Poodle
初始化程序直接调用Dog
初始化程序,以便进行所有相同的设置:
class Poodle: Dog {
init(name: String) {
super.init(name: name, breed: "Poodle")
}
}
为了安全起见,Swift总是让您从子类中调用super.init()
,以防万一父类在创建时会做一些重要的工作。
3. 方法重写 Overriding methods – test
子类可以使用其自己的实现替换父方法——这个过程称为重写。这是一个带有makeNoise()
方法的简单Dog类:
class Dog {
func makeNoise() {
print("Woof!")
}
}
如果我们创建一个从Dog
继承的新Poodle
类,它将继承makeNoise()
方法。因此,这将显示“ Woof!”:
class Poodle: Dog {
}
let poppy = Poodle()
poppy.makeNoise()
方法重写允许我们更改Poodle
类的makeNoise()
实现。
Swift要求我们在重写方法时使用override func
而不是仅使用func
——它阻止您意外重写方法,并且如果您尝试覆盖父类中不存在的内容,则会收到错误消息:
class Poodle: Dog {
override func makeNoise() {
print("Yip!")
}
}
进行此更改后,poppy.makeNoise()
将显示“ Yip!”。而不是“ Woof!”。
4. 最终类 Final classes – test
尽管类继承非常有用——实际上苹果平台的大部分都需要您使用它——有时您希望禁止其他开发人员根据您的类来构建自己的类。
Swift为此提供了一个final
关键字:当您将一个类声明为final
时,其他任何类都不能从该类继承。这意味着他们无法重写您的方法来更改您的行为——他们需要以编写类的方式使用您的类。
要使类为final,只需将final
关键字放在其前面,如下所示:
final class Dog {
var name: String
var breed: String
init(name: String, breed: String) {
self.name = name
self.breed = breed
}
}
5. 拷贝对象 Copying objects – test
类和结构体之间的是如何复制它们。复制结构体时,原始结构体和复制结构体都是不同的——更改一个结构体不会更改另一个结构体。复制一个类时,原始副本和复制副本都指向同一事物,因此更改一个类会同时更改另一个。
例如,这是一个简单的Singer
类,其名称属性具有默认值:
class Singer {
var name = "Taylor Swift"
}
如果我们创建该类的实例并打印其名称,则会得到“ Taylor Swift”:
var singer = Singer()
print(singer.name)
现在,从第一个变量创建第二个变量,并更改其名称:
var singerCopy = singer
singerCopy.name = "Justin Bieber"
由于类的工作方式,singer
和singerCopy
都指向内存中的同一对象,因此当我们再次打印歌手名称时,将看到“ Justin Bieber”:
print(singer.name)
另一方面,如果Singer
是结构,那么我们将第二次打印“ Taylor Swift”:
struct Singer {
var name = "Taylor Swift"
}
6. 反初始化器 Deinitializers – test
类和结构之间的是类可以具有反初始化器——当类的实例被销毁时运行的代码。
为了说明这一点,这里是一个Person
类,它具有name
属性,一个简单的初始化程序以及一个用于打印消息的printGreeting()
方法:
class Person {
var name = "John Doe"
init() {
print("\(name) is alive!")
}
func printGreeting() {
print("Hello, I'm \(name)")
}
}
我们将在一个循环中创建Person
类的一些实例,因为每次循环时都会创建一个新的person
并销毁它:
for _ in 1...3 {
let person = Person()
person.printGreeting()
}
现在来看反初始化器。当Person
实例被销毁时将调用此方法:
deinit {
print("\(name) is no more!")
}
7. 可变性 Mutability – test
类和结构体之间的是它们处理常量的方式。如果您具有带有可变属性的常量结构,则该属性无法更改,因为结构体本身是常量。
但是,如果您的常量类具有可变属性,则可以更改该属性。因此,类不需要使用可更改属性的方法的mutating
关键字;只有结构体才需要。
这种差异意味着即使将类创建为常量,也可以更改类的任何变量属性——这是完全有效的代码:
class Singer {
var name = "Taylor Swift"
}
let taylor = Singer()
taylor.name = "Ed Sheeran"
print(taylor.name)
如果要阻止这种情况发生,则需要使该属性为常量:
class Singer {
let name = "Taylor Swift"
}
8. 类:总结 Classes summary – test
您已经完成了本系列第八部分,所以让我们总结一下:
1、类和结构体相似,它们都可以让您使用属性和方法创建自己的类型。
2、一个类可以从另一个类继承,并获得父类的所有属性和方法。讨论类层次结构很常见——一个类基于另一个类,而另一个类本身又基于另一个类。
3、您可以使用final
关键字标记一个类,这将阻止其他类从该类继承。
4、通过方法重写,子类可以使用新的实现替换其父类中的方法。
5、当两个变量指向同一类实例时,它们都指向同一块内存——改变一个会改变另一个。
6、类可以具有一个反初始化器,该反初始化器是在销毁该类的实例时运行的代码。
7、类对常量的强制性不如结构强——如果将属性声明为变量,则无论如何创建类实例,都可以对其进行更改。
< Previous: 结构体(下) | Next: 协议和扩展> |
---|
网友评论