加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Swift-枚举、结构体、类

发布时间:2020-12-14 06:09:39 所属栏目:百科 来源:网络整理
导读:Swift-枚举、结构体、类 学习如下教程的记录 Getting to Know Enums,Structs and Classes in Swift Swift Tutorial: Initialization In Depth,Part 1/2 Swift中的type system: 枚举 参考: 官方文档-枚举 明确指定后备存储(backing store)类型的枚举被称为R

Swift-枚举、结构体、类

学习如下教程的记录

  • Getting to Know Enums,Structs and Classes in Swift
  • Swift Tutorial: Initialization In Depth,Part 1/2

Swift中的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,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关键字

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读