Swift有四种自定义数据类型:structures, classes, enumerations和protocols。今天来详细介绍一下Struct,我们平时用的Int, String, Array, Dictionary等也都是以struct的形式定义的
//From Swift Library
public struct Int : FixedWidthInteger, SignedInteger {
// …
}
一、Structure的声明与instance创建
1、声明
假设我们要为公司做一个员工统计:
struct Person {
let firstName: String
let lastName: String
}
这里我们声明了一个Struct “Person”,每个person都有名字和姓,因为人的名字一般不会改,所以firstName和lastName都用了let(声明为constant)。公司的员工除了姓名外,至少还应该有个职称
struct Employee {
let identity: Person
var jobTitle: String
var salary: Double
}
这里identity的type是我们刚定义的Person,职称和薪资因为有可能会变,jobTitle、salary用var来声明。
2、创建instance
如果要创建一个Person的instance,我们可以用swift为struct自动生成的initializer
let me = Person(firstName: "Joshua", lastName: "Jiang")
值得注意的是,如果你要用这个自动生成的initializer,必须要在创建instance的时候把所有properties都赋值才行,即使你在声明的时候给了默认值(除非这个属性的类型是optional)。如果我们想创建一个Employee就可以写成
var joshua = Employee(identity: me, jobTitle: "CTO", salary: 3000)
注意因为职称可能会变,如果我们用let声明了joshua,以后他变ceo的时候,即使jobTitle是变量,修改它也会出error
let joshua = Employee(identity: me, jobTitle: "CTO", salary: 3000)
joshua.jobTitle = "CEO" //Error: cannot assign to property
二、Properties属性
1、stored properties和computed properties
我们刚刚定义的Person和Employee下的firstName、lastName、jobTitle等都属于stored property,他们都储存了我们赋给他们的值,是占用内存空间的;但是还有一种computed properties是随时用随时计算出来的,并不占用任何内存空间,没有值被储存。举个例子:
struct Employee {
let identity: Person
var jobTitle: String
var salary: Double
var tax: Double {
return salary * 0.05
}
}
我们在Employee类型里加了一个新的变量tax,代表这个员工需要交的税。如果税只需要工资就能计算出来,那么如果我们把tax声明成一个stored property,然后每次创建一个Employee的时候还需要先人工计算出他的tax再赋值,那就太傻了;这个时候computed property最适合。注意computed property必须是用var来声明。
var joshua = Employee(identity: me, jobTitle: "CTO", salary: 3000)
let tax = joshua.tax //150.0
上述例子中的tax是一个只读computed property,有需要的话我们也可以定义成可读可写的
var tax: Double {
get {
return salary * 0.05
}
set {
salary = newValue / 0.05
}
}
因为我们想让tax变成可读可写,那就需要自定义get和set这两个方法(如果是只读,swift会自动帮我们加上get)。这样的话,如果我们给tax赋值,salary就会相应的改变(在现实生活中可能没什么意义)。newValue是自动传入的参数,代表tax新被赋予的值。
joshua.tax = 300
print(joshua.salary) //6000.0
2、type properties
computed property和stored property都是instance的属性,数据类型也有自己的属性,这就是type property。比如:
struct Employee {
static var location = "青岛"
let identity: Person
var jobTitle: String
var salary: Double
}
location前面加了static,这就意味着location是Employee这个类型的属性,不用创建instance就可以读取。在实践中有时候会很有用。
let location = joshua.location //Error: you can't access a type property on an instance
let location = Employee.location //正确用法
3、property observers
store property是有observer观察者的,willSet会在属性即将更新的时候执行;didSet会在属性刚更新完的时候执行。假设薪水突然翻番儿了,那就是location变了(现实逻辑瞎扯,看懂代码逻辑就行)
var salary: Double {
didSet {
if salary >= oldValue * 2 {
Employee.location = "北京"
}
}
}
a) didSet中,salary已经是更新之后的值了
b) 改变之前的值可以用oldValue来读取更新之前的值
c) 为了区别于其他属性,你需要用Employee.location来说明location是type property
d) didSet和willSet只能用于stored property,你想监听computed property的话,直接在他们的set里实现相应逻辑就行了
e) 在创建Employee instance的时候,didSet和willSet并不会执行,只有更新的时候才会
f) 读完e),也就说明只有var声明的变量才可以用didSet和willSet
三、Struct中的methods
1、定义
首先如果一个func定义在struct的外面,那他就是个function,如果定义在struct的里面那他就是method。但因为我们已经有了computed property, 我们在需要大量计算或者需要读写数据库的时候用methods,其他情况一般用computed property就可以了。
let jobTitles = ["Developer", "CTO", "COO", "CFO", "CEO"]
struct Employee {
let identity: Person
var jobTitle: String
var salary: Double
func roomFor() -> String {
guard let index = jobTitles.firstIndex(of: self.jobTitle) else {
return "Unknow"
}
return "00\(index)"
}
}
我们定义了一个method来获取员工的房间号码,这个很简单,一个小地方说明一下,因为roomFor是在struct内部定义的method,self完全可以省略。
var joshua = Employee(identity: me, jobTitle: "CTO", salary: 3000)
joshua.roomFor() // "000"
2、自定义init()
我们之前创建instance的时候用的都是系统生成的init,但我们公司现在只缺月薪2000的程序员了,创建的时候每次输入同样的值太麻烦。这时候我们就可以自定义init()。
struct Employee {
let identity: Person
var jobTitle: String
var salary: Double
init(identity: Person) {
self.identity = identity
jobTitle = "Developer"
salary = Double(2000.0)
}
}
var joshua = Employee(identity: me) //简单多了
a) 有了自定义的init,之前自动生成那个就不能用了哦,需要的话再自己写一个
b) 这里的self是需要的,因为要区别于local variable
3、mutating methods
struct里的method是不能改变属性的值的,除非定义的时候在前面加mutating
mutating func raise() {
salary += 100
}
a) 我们知道swift中,function一般是不能改变传入参数的值的,除非传入参数被标明inout;有了mutating,swift会帮我们偷偷把self标明inout的
b) 如果一个instance想要执行raise,那他一定要是一个var声明的变量
4、type methods
和type property一样,类型可以有自己的type method,比如
let jobTitles = ["CTO", "COO", "CFO", "CEO"]
struct Employee {
let identity: Person
var jobTitle: String
var salary: Double
static func salaryOf(jobTitle: String) -> Double {
guard let index = jobTitles.firstIndex(of: jobTitle) else {
return 0.0
}
return Double(3000) * Double(index + 1)
}
}
Employee.salaryOf(jobTitle: "CEO") //12000
四、Extension
假设Employee是个第三方库,我们不能改源码,这时候就可以通过extension来填加自定义method:
extension Employee {
init(identity: Person) {
self.identity = identity
jobTitle = "Developer"
salary = Double(2000.0)
}
}
我们用extension加了一个自定义init,跟之前在struct里面加的区别是,用extension可以保留系统自动生成的那个init。
需要注意的是:
a) extension不能用来加store property,因为这样会更改已有struct内存占用大小,破坏已有代码。
b) extension不能override原struct已有的method