Swift-枚举、结构体、类
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.rawValue
为black
关联值
颜色处了有使用名字表示外,还可以使用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
也是一个protocol
,DrawingContext
知道如何绘制纯几何类型:Circle
,Rectangle
和其他类型。实际的绘制方法要自己实现
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 init
和self.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不会在初始化阶段调用
因此,didSet
和willSet
对常量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
}
}
进行如下的初始化,会发现fontColor
和backgroundColor
为nil
:
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
关键字