E·S·C 指的是 Enumeration(枚举)、Structures(结构体)和 Class(类)的首字母缩写,本文主要是记录它们之间共同拥有的一些特性。

属性

属性(Properties)能够把值关联到枚举、结构体和类,从而模拟类型所表示的实例。

  • 属性分两种
    • 存储(stored)属性:有默认值
    • 计算(computed)属性:根据已有信息返回计算结果;
  • 属性的值可以是常量和变量;
  • 枚举、类和结构体都可以有属性;

存储属性

存储(stored)属性有默认值,主要用于存储数据,不支持枚举。

struct ShapeStruct {
    // 其中的变量和常量即为属性
    var width: Int // 变量为读写属性,可修改
    let height: Int // 常量为只读属性,不可修改值
}
// height 第一次赋值之后变不可修改
// 如果 shape 声明为常量,width 也无法更改,因为结构体是值类型
var shape = ShapeStruct(width: 6, height: 7)

惰性加载

惰性加载(lazy loading)意味着属性的值只在第一次访问的时候才会出现。惰性加载使用 lazy 为关键字,且必须声明为变量。

struct ShapeStruct {
    var width: Int = 6
    var height: Int = 7

    lazy var area = width * height
}

var circle = ShapeStruct()
print(circle.area) // 返回 42

计算属性

计算(conputed)属性可以使用读取方法(getter)和写入方法(setter)设置属性的值,同样必须声明为变量。

// 计算圆面积
struct CircleStruct {
    var radius: Double = 10 
    
    var area: Double {
        get {
            return radius * radius * Double.pi
        }
        // 如果不指定变量名,默认为 newValue
        set(newArea) {
            radius = sqrt(newArea / Double.pi) // 计算圆半径
        }
    }
}

var circle = CircleStruct()
circle.area = 42
print(circle.radius) // 返回 3.656366395715726

只读计算属性

// 计算圆面积
struct CircleStruct 
    var radius: Double = 10 
    
    // 必须显式声明 area 类型
    var area: Double {
        // 只有 get 为只读属性
        get {
            // 返回圆的面积
            return radius * radius * Double.pi
        }
    }
}

var circle = CircleStruct())

print(circle.area) // 返回 314.1592653589793

属性观察者

属性观察者(property observation)对于任何自定义的存储属性和任何继承的属性,会观察并响应给定属性的变化。

  • willSet 观察属性即将发生的变化,会在被存储之前调用;
  • didSet:观察属性已经发生的变化,会在被存储之后调用;
// 计算步数
struct StepStruct {
    var total: Int = 0 {
        willSet {
            print("共走 \(newValue) 步。") 
        }
        didSet(oldTotal) { // 自定义名称
            if total > oldTotal {
                print("新增 \(total - oldTotal) 步。")
            }
        }
    }
}

var step = StepStruct()
step.total = 100 // 返回 共走 100 步。 新增 100 步
step.total = 1000 // 返回 共走 1000 步。 新增 900 步

注:

  • 自定义的计算属性不能用属性观察
  • willSet 可使用 newValue 访问新值;
  • didSet 可使用 oldValue 访问旧值;

方法

方法(Methods)是关联了特定类型的函数。

enum SwiftEnum {
    case cold
    case warm
    case off
    // 开关在三种状态之间切换
    mutating func next() {
        switch self {
        case .off:
            self = .cold
        case .cold:
            self = .warm
        case .warm:
            self = .off
        }
    }
}

var swiftEnum = SwiftEnum.off

swiftEnum.next() // 返回 cold
swiftEnum.next() // 返回 warm

注:

  • 所有方法都有一个 self 参数,用来访问对应的实例;
  • mutating 可以让值类型的方法修改 self;

下标

下标(Subscripts)可以作为访问集合、列表或序列成员元素的快捷方式,使用关键字 subscript 定义。

// 返回指定值的倍数
struct TimesStruct {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let time = TimesStruct(multiplier: 6)
print(time[7]) // 返回 42
  • 字典也是通过下标访问的;

初始化

初始化(initializer)方法可以在创建实例的时候赋予合适的值,使用关键字 init 定义。

语法

枚举、结构体和类初始化语法均相同。

init() {
    // 初始化代码
}

注:

  • 初始化方法没有返回值;
  • 可以初始化常量的属性;
  • 初始化的任务是给类型的存储属性赋值;

值类型初始化

默认初始化方法

空初始化

在定义时,如果给所有元素设置了默认值,即获得类空初始化方法(Empty initializer),即没有参数的初始化方法。

// 空初始化
struct testInit {
    number: Int = 42
    number2: Int? // 可选属性类型初始化为 nil
}
成员初始化

对于类型的每个存储属性,成员初始化(memberwise initializer)可以给于对应的参数。

struct TestInit {
    var number: Int
}
// 成员初始化
let testInit = TestInit(number: 42)

自定义初始化

初始化的参数(Parameters)同函数一样,有相同的功能和语法:

  • Parameter Names:参数名,为内部使用参数;
  • Argument Labels:参数标签,供外部调用参数;
// 计算圆的半径
struct TestInit {
    var radius: Double
    
    init(formArea, area: Double) {
        radius = sqrt(area / Double.pi)
    }
}

let testInit = TestInit(forArea: 30)
print(testInit) // 返回 3.656366395715726

注:

  • 同函数一样,可只提供参数标签;
  • 也可使用 _ 忽略参数标签;
  • 初始化时,也可以给常量赋值;

委托初始化

委托初始化(initializer delegation)可以包含对该类型其它初始化方法的调用,通常用来提供多种创建实例的路径。

struct TestInit {
    var width: Int = 6
    var height: Int?
    
    // 1. 功能同前面提到的空初始化相同,使用提供的默认值
    init() {}
    
    // 2. 同成员初始化相同,把外部参数的值赋给对应的属性
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
    
    // 3. 基于委托初始化
    init(height: Int) {
        self.init(width: 0, height: height)
    }
}

类的初始化

类的初始化方法分为指定(designated)初始化方法和便捷(convenience)初始化方法,所有类的属性(含继承),必须在初始化期间分配初始化。

类没有默认的空初始化方法,且一般情况下类不会继承父类的初始化方法,但:

  • 如果子类没有定义任何指定初始化方法,便会继承父类的指定初始化方法;
  • 或者子类实现了父类的所有指定初始化方法(无论显式还是隐式),就会继承父类的便捷初始化方法;

指定初始化方法

指定初始化方法是类的主要初始化方法,可以确保类属性在初始化之前都有值。

init(parameters) {
    statements
}

便捷初始化方法

便捷初始化方法不需要确保类的所有属性都有值,使用 convenience 关键字标记。

convenience init(parameters) {
    statements
}

必须初始化

在类的 init 前加上 required 关键字,可要其子类必须提供初始化。

class initClass {
    required init() {
        statements
    }
}

反初始化

可失败的初始化方法

在初始化枚举、结构体和类时,在可能遇到无效参数或依赖资源不可用等情况时,可以使用关键字 init? 定义可失败的初始化方法(failable initalizer)。

struct TestInit {
    let answer: String

    init?(answer: String) {
        if answer.isEmpty {
            return nil
        }
        self.answer = answer
    }
}

let testInit = TestInit(answer: "")

if testInit == nil {
    print("初始化失败")
}

扩展

扩展(Extensions)可以为现有的枚举、结构体和类增加新功能,扩展支持:

  • 增加计算属性;
  • 增加初始化方法;
  • 定义新方法;
  • 定义新下标;
  • 使类型符合协议;
  • 定义新内嵌类型;

语法

使用 extension 关键字声明扩展:

extension TestType {
    definition
}

协议

协议(Protocols)可以定义类型需要满足的接口。

语法

使用 protocol 关键字定义协议:

protocol TestProtocol {
    definition
}

// 自定义类型声明时,协议名放在冒号之后;
// 多个协议以逗号分隔;
// 如果包含父类,则父类将在协议名之前;

class TestClass: SuperClass, FirstProtocol, AnotherProtocol {
    definition
}