欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Swift-枚举、结构体、类

程序员文章站 2022-04-11 17:05:04
...

Swift-枚举、结构体、类

学习如下教程的记录

Swift中的type system:

Swift-枚举、结构体、类

枚举

参考:

明确指定后备存储(backing store)类型的枚举被称为RawRepresentable,因为它们自动采用RawRepresentable协议。

如下定义一个颜色枚举ColorName:

enum ColorName: String {
    case black
    case silver
    case gray
    case white
    case maroon
    case red
}

当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。

所以let black = ColorName.black.rawValueblack

关联值

颜色处了有使用名字表示外,还可以使用RGB或者HSL来表示
如下定义CSSColor

enum CSSColor {
  case named(ColorName)
  case rgb(UInt8, UInt8, UInt8)
}

如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做rawValue的参数,参数类型即为原始值类型,返回值则是枚举成员或nil

枚举中的方法和协议

在swift中,枚举与其它named type一样,可以采用协议
如果想输出CSSColor对象,可以采用CustomStringConvertible协议

extension CSSColor: CustomStringConvertible
{
    var description: String {
        switch self {
        case .named(let colorName):
            return colorName.rawValue
        case .rgb(let red, let green, let blue):
            return String(format: "#%02X%02X%02X", red,green,blue)
        }
    }
}

枚举的初始化方法
枚举也可以添加自定义的初始化方法

extension CSSColor {
    init(gray: UInt8) {
        self = .rgb(gray, gray, gray)
    }
}

枚举类型的可失败构造器

如果提供的参数无法匹配任何枚举成员,则构造失败。

enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

带原始值的枚举类型的可失败构造器

带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的参数

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."

结构体

在枚举的extension中不能添加新的case,但结构体和类是可以的

Swift Standard Library团队建议当创建一个新的model的时候,先使用protocol创建一个interface。在这里图形要可绘制,所以定义如下的protocol:

protocol Drawable {
  func draw(with context: DrawingContext)
}

DrawingContext也是一个protocolDrawingContext知道如何绘制纯几何类型:CircleRectangle和其他类型。实际的绘制方法要自己实现

protocol DrawingContext {
  func draw(circle: Circle)
  // more primitives will go here ...
}

如下,定义一个结构体Circle采用Drawable协议:

struct Circle : Drawable {
    var strokeWidth = 5
    var strokeColor = CSSColor.named(.red)
    var fillColor = CSSColor.named(.yellow)
    var center = (x: 80.0, y: 160.0)
    var radius = 60.0

    // 实现Drawable协议
    func draw(context: DrawingContext) {
        context.draw(self)
    }
}

结构体与类类似。结构体与类的区别是,结构体是值类型,类是引用类型

与类不同,结构体的方法不允许修改存储属性,如果要修改的话使用mutating声明

mutating func shift(x: Double, y: Double) {
  center.x += x
  center.y += y
}

结构体构造过程

默认构造器

如果没有存储属性,或者存储属性都有默认值时,可直接使用默认的构造器。同样,如果有可选类型的属性的变量,由于可选类型的存储属性默认初始化为nil,所以也看直接使用默认的构造器

struct RocketConfiguration {
    let name: String = "Athena 9 Heavy"
    let numberOfFirstStageCores: Int = 3
    let numberOfSecondStageCores: Int = 1
    var numberOfStageReuseLandingLegs: Int?
}

let athena9Heavy = RocketConfiguration()

但如果把var numberOfStageReuseLandingLegs: Int?,改为常量let

let numberOfStageReuseLandingLegs: Int?

这时let athena9Heavy = RocketConfiguration()就会编译报错

结构体的逐一成员构造器

处理默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。

逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。

struct RocketStageConfiguration {
    let propellantMass: Double
    let liquidOxygenMass: Double
    let nominalBurnTime: Int
}

let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,liquidOxygenMass: 276.0, nominalBurnTime: 180)

要注意的是:

  • 如果调整存储属性的顺序,上面的构造方法也会报错,因为逐一构造器的方法的参数的顺序与存储属性的顺序是一致的
  • 如果存储属性有默认值,如let nominalBurnTime: Int = 180,上面的构造方法也会报错。这是因为逐一构造器的参数只针对没有默认值的存储顺序
  • 如果添加了自定义的构造方法,原来自动生成的逐一构造器方法就会无效

    struct RocketStageConfiguration {
        let propellantMass: Double
        let liquidOxygenMass: Double
        let nominalBurnTime: Int
    
        init(propellantMass: Double, liquidOxygenMass: Double) {
            self.propellantMass = propellantMass
            self.liquidOxygenMass = liquidOxygenMass
            self.nominalBurnTime = 180
        }
    }
    
    let stageOneConfiguration = RocketStageConfiguration(propellantMass: 119.1,liquidOxygenMass: 276.0, nominalBurnTime: 180)//报错

但如果任然需要自动生成的逐一构造器方法,该怎么办呢?答案是使用extension,即把自定义的初始化方法放在extension

struct RocketStageConfiguration {
  let propellantMass: Double
  let liquidOxygenMass: Double
  let nominalBurnTime: Int
}

extension RocketStageConfiguration {
  init(propellantMass: Double, liquidOxygenMass: Double) {
    self.propellantMass = propellantMass
    self.liquidOxygenMass = liquidOxygenMass
    self.nominalBurnTime = 180
  }
}

自定义构造方法

一个初始化方法必须给每个不带默认值的存储属性指定值,否则的话报错
可以给构造方法的参数指定默认值

struct Weather {
    let temperatureCelsius: Double
    let windSpeedKilometersPerHour: Double
    init(temperatureFahrenheit: Double = 72, windSpeedMilesPerHour: Double = 5) {
        self.temperatureCelsius = (temperatureFahrenheit - 32) / 1.8
        self.windSpeedKilometersPerHour = windSpeedMilesPerHour * 1.609344
    }
}

值类型的构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

要注意的是,构造器代理中不能实例化任何属性,原因是你调用的改造方法,可能会修改你的属性,这样是不安全的,如下会报错

init(zAngularVelocityDegreesPerMinute: Double) {
  self.needsCorrection = false//报错
  self.init(zAngularVelocityDegreesPerMinute: zAngularVelocityDegreesPerMinute,
    needsCorrection: self.needsCorrection)
}

Two-Phase Initialization(两段式构造过程)

Swift-枚举、结构体、类

如下:

struct CombustionChamberStatus {
  var temperatureKelvin: Double
  var pressureKiloPascals: Double

  init(temperatureKelvin: Double, pressureKiloPascals: Double) {
    print("Phase 1 init")
    self.temperatureKelvin = temperatureKelvin
    self.pressureKiloPascals = pressureKiloPascals
    print("CombustionChamberStatus fully initialized")
    print("Phase 2 init")
  }

  init(temperatureCelsius: Double, pressureAtmospheric: Double) {
    print("Phase 1 delegating init")
    let temperatureKelvin = temperatureCelsius + 273.15
    let pressureKiloPascals = pressureAtmospheric * 101.325
    self.init(temperatureKelvin: temperatureKelvin, pressureKiloPascals: pressureKiloPascals)
    print("Phase 2 delegating init")
  }
}

CombustionChamberStatus(temperatureCelsius: 32, pressureAtmospheric: 0.96)

调试区域输出如下:

Phase 1 delegating init
Phase 1 init
CombustionChamberStatus fully initialized
Phase 2 init
Phase 2 delegating init

可见Phase 1 delegating initself.init(.....)之间,不可以使用self

可失败构造器

你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面添加问号(init?)

注意:你只是用return nil表明可失败构造器构造失败,而不要用关键字return来表明构造成功。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

除了返回nil,还可以抛出异常,如下:

struct Astronaut {
    let name: String
    let age: Int

    init(name: String, age: Int) throws {
        if name.isEmpty {
            throw InvalidAstronautDataError.EmptyName
        }
        if age < 18 || age > 70 {
            throw InvalidAstronautDataError.InvalidAge
        }
        self.name = name
        self.age = age
    }
}

enum InvalidAstronautDataError: Error {
    case EmptyName
    case InvalidAge
}

在调用可抛出异常的构造器方法时,使用try关键字,或者也可以使用try?或者try!

try、try?、try!的区别?
参考try, try! & try? what’s the difference, and when to use each?

try?会试图执行一个可能会抛出异常的操作。如果成功抛出异常,执行的结果就会包裹在可选值(optional)里;如果抛出异常失败(比如:已经在处理 error),那么执行的结果就是nil,而且没有 error。try?配合if let和guard一起使用效果更佳

let johnny = try? Astronaut(name: "Johnny Cosmoseed", age: 17) // nil

属性

计算属性

extension Circle {
  var diameter: Double {
    get {
      return radius * 2
    }
    set {
      radius = newValue / 2
    }
  }
}

更多的时候,需要的是一个getter方法:

var area: Double {
  return radius * radius * Double.pi
}

属性观察器

属性观察器可以用来限制值或者格式
willSet在赋值之前处理一些逻辑,使用newValue获取新值
当调用didSet的时候,属性的值已经变成了新值,要想获取原值可以使用oldValue

    var current = 0 {

        // 可以不声明新的变量名,使用newValue
        willSet(newCurrent){
            // 此时,current还是以前的值
            print("Current value changed. The change is \(abs(current-newCurrent))")
        }

        // property observer可以用来限制值或者格式
        // 也可以用来做关联逻辑
        // 可以不声明新的变量名,使用oldValue获取原来的值
        didSet(oldCurrent){
            // 此时,current已经是新的值
            if current == LightBulb.maxCurrent{
                print("Pay attention, the current value get to the maximum point.")
            }
            else if current > LightBulb.maxCurrent{
                print("Current too high, falling back to previous setting.")
                current = oldCurrent
            }

            print("The current is \(current)")
        }
    }

注意:didSet和willSet不会在初始化阶段调用
因此,didSetwillSet对常量let没有意义,因为let只在初始化阶段赋值

如下的定义:

enum Theme{
    case DayMode
    case NightMode
}

class UI{

    var fontColor: UIColor!
    var backgroundColor: UIColor!
    var themeMode: Theme = .DayMode{

        didSet{
            switch(themeMode){
            case .DayMode:
                fontColor = UIColor.blackColor()
                backgroundColor = UIColor.whiteColor()
            case .NightMode:
                fontColor = UIColor.whiteColor()
                backgroundColor = UIColor.blackColor()
            }
        }
    }

    init(){
        self.themeMode = .DayMode
    }
}

进行如下的初始化,会发现fontColorbackgroundColornil

let ui = UI()
ui.themeMode
ui.fontColor//为nil
ui.backgroundColor//为nil

便利构造函数和指定构造函数

要注意的点:

1.构造函数参数可以有默认值
2.构造函数可以重载

两种构造器:

指定构造器-指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

```
init(parameters) {
    statements
}
```

便利构造器-便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值

```
convenience init(parameters) {
    statements
}
```

注意:

1.只有指定构造函数可以调用super.init(),便利构造函数不能调用super.init()
2.便利构造函数最终一定要调用一个指定的构造函数

可失败构造器

一般来说,要让指定构造器不可失败,而让便利构造器可失败,如下的RocketComponent

class RocketComponent {
    let model: String
    let serialNumber: String
    let reusable: Bool

    // Init #1a - Designated
    init(model: String, serialNumber: String, reusable: Bool) {
        self.model = model
        self.serialNumber = serialNumber
        self.reusable = reusable
    }

    // Init #1b - Convenience
    convenience init(model: String, serialNumber: String) {
        self.init(model: model, serialNumber: serialNumber, reusable: false)
    }

    // Init #1c - Designated
    convenience init?(identifier: String, reusable: Bool) {
        let identifierComponents = identifier.components(separatedBy: "-")
        guard identifierComponents.count == 2 else {
            return nil
        }
        self.init(model: identifierComponents[0], serialNumber: identifierComponents[1],
                  reusable: reusable)
    }
}

类的两段式构造

类的两段式构造需要注意的是:

1.关于父类的属性必须通过父类的构造函数来进行构造
2.在swifi语言中,必须将子类相关的所有量进行初始化之后,才能调用super.init()
3.在swift中整个构造函数可以分为2部分,顺序不能混淆

  • 构造初值,在类没有构造完成的时候,不能使用self。但是静态的方法和静态量在第一阶段是可以使用的

        convenience init(group: String = ""){
            let name = User.generateUserName()
            self.init(name:name , group: group)
        }
    
        static func generateUserName() -> String{
            return "Player" + String(rand()%1_000_000)
        }
  • 进一步完成类相关属性的值

如下:

    init(name: String, group: String){

        // Phase1: 从子类向父类初始化所有的变量
        self.group = group

        super.init(name: name)

        // Phase2: 所有成员变量初始化完成以后,进行成员变量相关的逻辑调整
        if self.group == ""{
            getScore(-10)
            self.life -= 5
        }
    }

继承

final表示类不可继承

构造函数的继承

如果子类实现了父类所有的指定构造函数,则自动继承父类的所有便利构造函数

required关键字
required表示必须被子类所实现的构造函数

    //父类
    required init(name: String){
        self.name = name
    }
    //子类
    convenience required init(name: String){
        self.init(name: name , group: "")
    }

注意:
1.required关键字标注的的构造函数,不需要写override关键字