Swift初学 - 四大自定义数据类型之Struct

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

你可能感兴趣的:(Swift初学 - 四大自定义数据类型之Struct)