构造器(下)
可失败的构造器
如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init
关键字后面加添问号(init?)
。
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过return nil
语句,来表明可失败构造器在何种情况下“失败”。
struct Animal { let species: String init?(species: String){ if species.isEmpty { return nil } self.species = species } } // 生成someAnimal这个实例,需要注意的是这个实例的类型是可选类型(Animal?) let someAnimal = Animal(species: "Giraffe") if let giraffe = someAnimal { print(giraffe.species) } // 生成一个构造过程会失败的实例 let anonymousCreature = Animal(species: "") if anonymousCreature == nil { print("anonymousCreature 是空的") }
枚举类型的可失败构造器
你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足你所期望的条件时,导致构造失败。
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 } } } let kelvinUnit = TemperatureUnit(symbol: "K") // kelvin let unknowUnit = TemperatureUnit(symbol: "X") // nil
带原始值的枚举类型的可失败构造器
带原始值的枚举类型会自带一个可失败构造器init?(rawValue:)
,该可失败构造器有一个名为rawValue
的默认参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,否则构造失败。
enum TemperatureUnit: Character { case Kelvin = "K", Celsius = "C", Fahrenheit = "F" } let kelvinUnit = TemperatureUnit(rawValue: "K") // kelvin let unknowUnit = TemperatureUnit(rawValue: "X") // nil
类的可失败构造器
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前面的例子中,结构体Animal
的可失败构造器触发失败的行为,甚至发生在species
属性的值被初始化以前。而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。
class Product { let name: String! init?(name: String) { self.name = name // 类的可失败构造器触发失败条件之前,必须确定所有属性都被初始化 if name.isEmpty { return nil } } }
构造失败的传递
可失败构造器同样满足在构造器链中所描述的构造规则。其允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能向上代理基类的可失败构造器。
无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。
可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构造失败的条件。
class Product { let name: String! init?(name: String) { self.name = name if name.isEmpty { return nil } } } class CartItem: Product { let quantity: Int! init?(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) if quantity < 1 { return nil } } } if let item = CartItem(name: "shirt", quantity: 0) { print("Item: (item.name), quantity: (item.quantity)") } else { print("zero shirt") // 打印出这句 }
重写一个可失败构造器
就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。
注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。
注意: 你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。
class Document { var name: String? // 这个构造器构造了一个name属性为空的实例 init() {} // 相反 这个构造器构建了name不为空的实例 init?(name: String){ if name.isEmpty { return nil } self.name = name } } class AutomaticallyNamedDocument: Document { // 重写了父类的构造器,保证name属性永远有值 override init() { super.init() name = "[没有命名]" } // 将父类的可失败构造器重写为普通自定义构造器,保证了name属性永远有值 override init(name: String) { // 由于将可失败构造器重写为普通自定义构造器,所以这个构造器不能向上代理可失败构造器了 super.init() if name.isEmpty { self.name = "[没有命名]" } else { self.name = name } } }
可失败构造器 init!
通常来说我们通过在init
关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在init
后面添加惊叹号的方式来定义一个可失败构造器(init!)
,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
你可以在 init?
构造器中代理调用 init!
构造器,反之亦然。 你也可以用 init?
重写 init!
,反之亦然。 你还可以用 init
代理调用init!
,但这会触发一个断言:是否 init!
构造器会触发构造失败?
必要构造器
在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器
注意: 如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
class SomeClass { required init() { // 添加必要构造器的实现代码 } } class SubClass: SomeClass { // 当子类重写基类的必要构造器时,必须在子类的构造器前同样添加required修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加override修饰符 required init() { // 添加子类必要构造器的实现代码 } }
通过闭包和函数来设置属性的默认值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。
class SomeClass { let list: [String : Double] = { var scoreList = ["ASK" : 103.5, "Alice" : 115.8, "Alisa" : 118.5] return scoreList }() // 注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。 }