Swift-枚举、结构体、类

Swift-枚举、结构体、类

学习如下教程的记录

Swift中的type system:

type system

枚举

参考:

明确指定后备存储(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(两段式构造过程)

这里写图片描述

如下:

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关键字

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值