创建自己的结构
Swift可让您通过两种方式设计自己的类型,其中最常见的称为结构,或简称为structs
。可以给结构赋予它们自己的变量和常量以及它们自己的功能,然后根据需要创建和使用它们。
让我们从一个简单的示例开始:我们将创建一个Sport
将其名称存储为字符串的结构。结构内部的变量称为properties
,因此这是具有一个属性的结构:
struct Sport {
var name: String
}
定义了类型,因此现在我们可以创建和使用它的实例:
var tennis = Sport(name: "Tennis")
print(tennis.name)
我们制作了name
和tennis
变量,因此可以像常规变量一样更改它们:
tennis.name = "Lawn tennis"
属性可以像常规变量一样具有默认值,并且通常可以依靠Swift的类型推断。
计算属性
我们只是创建了一个Sport
这样的结构:
struct Sport {
var name: String
}
具有存储一个String
的属性name
。这些称为存储属性,因为Swift具有另一种称为计算属性的属性-一种运行代码以计算其值的属性。
我们将向该Sport
结构添加另一个存储的属性,然后是一个计算的属性。看起来是这样的:
struct Sport {
var name: String
var isOlympicSport: Bool
var olympicStatus: String {
if isOlympicSport {
return "\(name) is an Olympic sport"
} else {
return "\(name) is not an Olympic sport"
}
}
}
如您所见,它olympicStatus
看起来像一个普通的String
,但是它会根据其他属性返回不同的值。
我们可以通过创建一个新实例Sport
来进行尝试:
let chessBoxing = Sport(name: "Chessboxing", isOlympicSport: false)
print(chessBoxing.olympicStatus)
let gameBoxing = Sport(name: "gameBoxing", isOlympicSport: true)
print(gameBoxing.olympicStatus)
输出内容如下:
Chessboxing is not an Olympic sport
gameBoxing is an Olympic sport
属性观察器
通过属性观察器,可以在任何属性更改之前或之后运行代码。为了说明这一点,我们将编写一个Progress
跟踪任务和完成百分比的结构:
struct Progress {
var task: String
var amount: Int
}
现在,我们可以创建该结构的实例并随时间调整其进度:
var progress = Progress(task: "Loading data", amount: 0)
progress.amount = 30
progress.amount = 80
progress.amount = 100
我们想让 Swift每次amount
更改时都打印一条消息,我们可以使用didSet
属性观察器。每次amount
更改时,它将运行一些代码:
struct Progress {
var task: String
var amount: Int {
didSet {
print("\(task) is now \(amount)% complete")
}
}
}
您还可以willSet
用来在属性更改之前采取措施,但这很少使用。
方法
结构内部可以具有函数,并且这些函数可以根据需要使用结构的属性。结构内部的函数称为方法,但它们仍使用相同的func
关键字。
我们可以用一个City
结构来证明这一点。这将具有一个population
存储城市人口的属性,以及一个collectTaxes()
返回人口乘以1000的方法。由于该方法属于City
,因此可以读取当前城市的population
属性。
这是代码:
struct City {
var population: Int
func collectTaxes() -> Int {
return population * 1000
}
}
该方法属于该结构,因此我们在该结构的实例上调用它,如下所示:
let london = City(population: 9_000_000)
london.collectTaxes()
变异方法
如果一个结构具有可变属性,但是该结构的实例被创建为常量,则该属性不能更改–该结构是恒定的,因此,无论如何创建,其所有属性也是恒定的。
问题在于,当您创建结构时,Swift不知道是否将其与常量或变量一起使用,因此默认情况下采用安全方法:除非明确要求,否则Swift不会允许您编写更改属性的方法。
当您想在方法内部更改属性时,需要使用mutating
关键字对其进行标记,如下所示:
struct Person {
var name: String
mutating func makeAnonymous() {
name = "Anonymous"
}
}
因为它更改了属性,所以Swift仅允许在Person
作为变量的实例上调用该方法:
var person = Person(name: "Ed")
person.makeAnonymous()
字符串的属性和方法
到目前为止,我们已经使用了很多字符串,事实证明它们是结构-它们具有自己的方法和属性,可用于查询和操作字符串。
首先,让我们创建一个测试字符串:
let string = "Do or do not, there is no try."
您可以使用其count属性读取字符串中的字符数:
print(string.count)
他们有一个hasPrefix()
方法,如果字符串以特定字母开头,则返回true
:
print(string.hasPrefix("Do"))
您可以通过调用字符串的uppercased()
方法将其大写:
print(string.uppercased())
您甚至可以让Swift将字符串的字母排序成一个数组:
print(string.sorted())
字符串具有更多的属性和方法-尝试键入string.
以显示Xcode的代码完成选项。
数组的属性和方法
数组也是结构,这意味着它们也具有自己的方法和属性,可用于查询和操作数组。
这是一个让我们开始的简单数组:
var toys = ["Woody"]
您可以使用其count属性读取数组中的项目数:
print(toys.count)
如果要添加新项目,请使用如下append()
方法:
toys.append("Buzz")
您可以使用其firstIndex()
方法在数组内找到任何项,如下所示:
toys.firstIndex(of: "Buzz")
这将返回1,因为数组从0开始计数。
就像使用字符串一样,您可以让Swift将数组的项目按字母顺序排序:
print(toys.sorted())
最后,如果要删除项目,请使用如下remove()
方法:
toys.remove(at: 0)
数组具有更多的属性和方法–尝试键入toys.
以显示Xcode的代码完成选项。
初始化器
初始化器是特殊的方法,提供不同的方法来创建结构。默认情况下,所有结构都带有一个名为成员级初始值设定项的值,这要求您在创建结构时为每个属性提供一个值。
如果我们创建User
具有一个属性的结构,则可以看到以下内容:
struct User {
var username: String
}
创建这些结构之一时,必须提供用户名:
var user = User(username: "twostraws")
我们可以提供自己的初始化程序来替换默认的初始化程序。例如,我们可能希望将所有新用户创建为“匿名”并打印一条消息,如下所示:
struct User {
var username: String
init() {
username = "Anonymous"
print("Creating a new user!")
}
}
你不写func
初始化之前,但是你做的必要,以确保所有属性都初始化结束前的值。
现在我们的初始化程序不接受任何参数,我们需要创建如下结构:
var user = User()
user.username = "twostraws"
引用当前实例
在方法内部,您会得到一个称为的特殊常量self
,该常量指向当前正在使用的结构的任何实例。当您创建与属性具有相同参数名的初始值设定项时,此self
值特别有用。。
例如,如果您创建一个Person
带有name
属性的结构,然后尝试编写一个接受name
参数的初始化程序,则self
可以帮助您区分属性和参数– self.name
引用属性,而name
引用参数。
这就是代码中的内容:
struct Person {
var name: String
init(name: String) {
print("\(name) was born!")
self.name = name
}
}
惰性属性
作为性能优化,Swift使您仅在需要时才创建一些属性。举个例子,考虑一下这个FamilyTree
结构-它并没有做很多,但是从理论上讲,为某人创建家谱需要很长时间:
struct FamilyTree {
init() {
print("Creating family tree!")
}
}
我们可以将该FamilyTree
结构用作Person
结构内部的属性,如下所示:
struct Person {
var name: String
var familyTree = FamilyTree()
init(name: String) {
self.name = name
}
}
var ed = Person(name: "Ed")
但是,如果我们不总是需要特定人的家谱怎么办?如果将lazy
关键字添加到familyTree
属性,则Swift仅在FamilyTree
首次访问该结构时才会创建该结构:
lazy var familyTree = FamilyTree()
因此,如果您想看到“正在创建家谱!”消息,则需要至少访问一次该属性:
ed.familyTree
静态特性和方法
到目前为止,我们创建的所有属性和方法都属于struct的各个实例,这意味着,如果我们有一个Student
struct,我们可以创建多个学生实例,每个实例都具有各自的属性和方法:
struct Student {
var name: String
init(name: String) {
self.name = name
}
}
let ed = Student(name: "Ed")
let taylor = Student(name: "Taylor")
您也可以通过将Swift声明为static来要求Swift在该结构的所有实例之间共享特定的属性和方法。
为了尝试这一点,我们将向该Student
结构添加一个静态属性,以存储该班级中有多少学生。每次创建新学生时,我们都会向其中添加一个:
struct Student {
static var classSize = 0
var name: String
init(name: String) {
self.name = name
Student.classSize += 1
}
}
因为该classSize
结构属于该结构本身而不是该结构的实例,所以我们需要使用读取它Student.classSize
:
print(Student.classSize)
访问控制
访问控制允许您限制哪些代码可以使用属性和方法。这一点很重要,例如您可能希望阻止人们直接读取属性。
我们可以创建一个Person
具有id
属性以存储其社会保险号的结构:
struct Person {
var id: String
init(id: String) {
self.id = id
}
}
let ed = Person(id: "12345")
创建该人后,我们可以将其id
设为私有,因此您无法从结构外部读取它-尝试编写ed.id
根本行不通。
只需使用private
关键字,如下所示:
struct Person {
private var id: String
init(id: String) {
self.id = id
}
}
现在,只有内部的方法Person
可以读取id
属性。例如:
struct Person {
private var id: String
init(id: String) {
self.id = id
}
func identify() -> String {
return "My social security number is \(id)"
}
}
另一个常见的选择是public
,它允许所有其他代码使用属性或方法。
总结
- 1.您可以使用结构创建自己的类型,这些结构可以具有自己的属性和方法。
- 2.您可以使用存储的属性或使用计算的属性即时计算值。
- 3.如果要更改方法内的属性,则必须将其标记为
mutating
。 - 4.初始化程序是创建结构的特殊方法。默认情况下,您将获得一个成员初始化器,但是如果创建自己的初始化器,则必须为所有属性赋予一个值。
- 5.使用
self
常量来引用方法内部结构的当前实例。 - 6.
lazy
关键字告诉Swift当第一次使用,他们只能创建属性。 - 7.您可以使用
static
关键字在结构的所有实例之间共享属性和方法。 - 8.访问控制使您可以限制可以使用属性和方法的代码。