(二十六)可失败构造器

/*

 

 

 

 如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非

 常有用的。这里所指的失败是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满

 足某种必要的条件等。

 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或 多个可失败构造器。其语法为在 init 关键字后面加添问号 (init?)

 注意: 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。


 可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。你通过 return nil 语句,来表 明可失败构造器在何种情况下失败

 

 

 注意: 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了能确保对象自身能被正确构 建。所以即使你在表明可失败构造器,失败的这种情况下,用到了 return nil 。也不要在表明可失败构造器成 功的这种情况下,使用关键字 return

 

 

下例中,定义了一个名为 Animal 的结构体,其中有一个名为 species , String 类型的常量属性。同时该结构 体还定义了一个,带一个 String 类型参数 species ,可失败构造器。这个可失败构造器,被用来检查传入的参 数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。

 

 */



struct Animal {

    let species: String

    init?(species: String) {

        if species.isEmpty { return nil }

        self.species = species

    }

}



//你可以通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功。


let someCreature = Animal(species: "Giraffe") // someCreature 的类型是 Animal? 而不是 Animal

if let giraffe = someCreature {

    print("An animal was initialized with a species of \(giraffe.species)")

}

// 打印 "An animal was initialized with a species of Giraffe"





//如果你给该可失败构造器传入一个空字符串作为其参数,则该可失败构造器失败。



let anonymousCreature = Animal(species: "")

// anonymousCreature 的类型是 Animal?, 而不是 Animal

if anonymousCreature == nil {

    print("The anonymous creature could not be initialized")

}

// 打印 "The anonymous creature could not be initialized"




//注意: 空字符串( "" ,而不是 "Giraffe" )和一个值为 nil 的可选类型的字符串是两个完全不同的概 念。上例中的空字符串( "" )其实是一个有效的,非可选类型的字符串。这里我们只所以让 Animal 的可失败 构造器,构建对象失败,只是因为对于 Animal 这个类的 species 属性来说,它更适合有一个具体的值,而不是 空字符串。




//枚举类型的可失败构造器


//你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足枚

//举成员期望的条件时,构造失败。

//下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员( Kelvin , Celsius , Fahrenheit )和一个被用来找到 Character 值所对应的枚举成员的可失败构造器:


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 fahrenheitUnit = TemperatureUnit(symbol: "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(symbol: "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."





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


//带原始值的枚举类型会自带一个可失败构造器 init?(rawValue:) ,该可失败构造器有一个名为 rawValue 的默认参 ,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构 造一个带此原始值的枚举成员,否则构造失败。

//因此上面的 TemperatureUnit的例子可以重写为:






enum TemperatureUnit1: Character {

    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"

}

let fahrenheitUnit1 = TemperatureUnit1(rawValue: "F")

if fahrenheitUnit1 != nil {

    print("This is a defined temperature unit, so initialization succeeded.")

}

// prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit1 = TemperatureUnit1(rawValue: "X")

if unknownUnit1 == nil {

    print("This is not a defined temperature unit, so initialization failed.")

}

// prints "This is not a defined temperature unit, so initialization failed."




//类的可失败构造器





//值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。比如在前 面的例子中,结构体 Animal 的可失败构造器触发失败的行为,甚至发生在 species 属性的值被初始化以前。

//而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间

//的代理调用发生完后触发失败行为。

//下面例子展示了如何使用隐式解析可选类型来实现这个类的可失败构造器的要求:


class Product {

    let name: String!

    init?(name: String) {

        self.name = name

        if name.isEmpty { return nil }

    }

}




//上面定义的 Product ,其内部结构和之前 Animal 结构体很相似。 Product 类有一个不能为空字符串的 name 量属性。为了强制满足这个要求, Product 类使用了可失败构造器来确保这个属性的值在构造器成功时不为空。

//毕竟, Product 是一个类而不是结构体,也就不能和 Animal 一样了。 Product 类的所有可失败构造器必须在自己 失败前给 name 属性一个初始值。



//上面的例子中, Product 类的 name 属性被定义为隐式解析可选字符串类型( String! )。因为它是一个可选类 ,所以在构造过程里的赋值前, name 属性有个默认值 nil 。用默认值 nil 意味着 Product 类的所有属性都有 一个合法的初始值。因而,在构造器中给 name 属性赋一个特定的值前,可失败构造器能够在传入一个空字符串时 触发构造过程的失败。

//因为 name 属性是一个常量,所以一旦 Product 类构造成功, name 属性肯定有一个非 nil 的值。即使它被定义为 隐式解析可选类型,也完全可以放心大胆地直接访问,而不用考虑 name 属性是否有值。




if let bowTie = Product(name: "bow tie") {

    // 不需要检查 bowTie.name == nil

    print("The product's name is \(bowTie.name)")

}

// 打印 "The product's name is bow tie"




//构造失败的传递


//可失败构造器允许在同一类,结构体和枚举中横向代理其他的可失败构造器。类似的,子类的可失败构造器也能

//向上代理基类的可失败构造器。

//无论是向上代理还是横向代理,如果你代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过

//程都将被立即终止,接下来任何的构造代码都将不会被执行。



//注意: 可失败构造器也可以代理调用其它的非可失败构造器。通过这个方法,你可以为已有的构造过程加入构 造失败的条件。






//下面这个例子,定义了一个名为 CartItem Product 类的子类。这个类建立了一个在线购物车中的物品的模 ,它有一个名为 quantity 的常量参数,用来表示该物品的数量至少为1:





class CartItem: Product {

    let quantity: Int!

    init?(name: String, quantity: Int) {

        self.quantity = quantity

        super.init(name: name)

        if quantity < 1 { return nil }

    }

}




// Product 类中的 name 属性相类似的, CartItem 类中的 quantity 属性的类型也是一个隐式解析可选类型,只不 过由( String! )变为了( Int! )。这样做都是为了确保在构造过程中,该属性在被赋予特定的值之前能有一个 默认的初始值nil

//可失败构造器总是先向上代理调用基类, Product 的构造器 init(name:) 。这满足了可失败构造器在触发构造失 败这个行为前必须总是执行构造代理调用这个条件。



//如果由于 name 的值为空而导致基类的构造器在构造过程中失败。则整个 CartIem 类的构造过程都将失败,后面的 子类的构造过程都将不会被执行。如果基类构建成功,则继续运行子类的构造器代码。

//如果你构造了一个 CartItem 对象,并且该对象的 name 属性不为空以及 quantity 属性为 1 或者更多,则构造成 :



if let twoSocks = CartItem(name: "sock", quantity: 2) {

    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")

}

// 打印 "Item: sock, quantity: 2"




//如果你构造一个 CartItem 对象, quantity 的值 0 , CartItem 的可失败构造器触发构造失败的行为:



if let zeroShirts = CartItem(name: "shirt", quantity: 0) {

    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")

} else {

    print("Unable to initialize zero shirts")

}

// 打印 "Unable to initialize zero shirts"



//类似的, 如果你构造一个 CartItem 对象,但其 name 的值为空, 则基类 Product 的可失败构造器将触发构造失败 的行为,整个 CartItem 的构造行为同样为失败:


if let oneUnnamed = CartItem(name: "", quantity: 1) {

    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")

} else {

    print("Unable to initialize one unnamed product")

}

// 打印 "Unable to initialize one unnamed product"





//重写一个可失败构造器

//就如同其它构造器一样,你也可以用子类的可失败构造器重写基类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个基类的可失败构造器。这样做的好处是,即使基类的构造器为可失败构造器,但当子类的构造器在构造过程不可能失败时,我们也可以把它修改过来。

//注意当你用一个子类的非可失败构造器重写了一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败构造器。




//注意: 你可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。


//下例定义了一个名为 Document 的类,这个类中的 name 属性允许为 nil 和一个非空字符串,但不能是一个空字符 :



class Document {

    var name: String?

    // 该构造器构建了一个name属性值为nildocument对象

    init() {}

// 该构造器构建了一个name属性值为非空字符串的document对象


    init?(name: String) {

        if name.isEmpty { return nil }

        self.name = name

    }


}


//下面这个例子,定义了一个 Document 类的子类 AutomaticallyNamedDocument 。这个子类重写了父类的两个指定构 造器,确保不论是通过没有 name 参数的构造器,还是通过传一个空字符串给 init(name:) 构造器,生成的实例 中的 name 属性总有初始值 "[Untitled]"





class AutomaticallyNamedDocument: Document {

    override init() {

        super.init()

        self.name = "[Untitled]"

    }

    override init(name: String) {

        super.init()

        if name.isEmpty {

            self.name = "[Untitled]"

        } else {

            self.name = name

        } }

}


//AutomaticallyNamedDocument 用一个非可失败构造器 init(name:) ,重写了父类的可失败构造器 init?(name:) 。因为子类用不同的方法处理了 name 属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失败 的构造器,用一个非可失败版本代替了父类的版本。


//你可以在构造器中调用父类的可失败构造器强制解包,以实现子类的非可失败构造器。比如,下面的 UntitledDoc ument 子类总有值为 "[Untitled]" name 属性,它在构造过程中用了父类的可失败的构造器 init(name:)




class UntitledDocument: Document {

    override init() {

        super.init(name: "[Untitled]")!

    }

}




//在这个例子中,如果在调用父类的构造器 init(name:) 时传给 name 的是空字符串,那么强制解绑操作会造成运 行时错误。不过,因为这里是通过字符串常量来调用它,所以并不会发生运行时错误。




//可失败构造器 init!



//

//通常来说我们通过在 init 关键字后添加问号的方式( init? )来定义一个可失败构造器,但你也可以使用通过在 init 后面添加惊叹号的方式来定义一个可失败构造器 (init!) ,该可失败构造器将会构建一个特定类型的隐式解

//析可选类型的对象。

//你可以在 init? 构造器中代理调用 init! 构造器,反之亦然。 你也可以用 init? 重写 init! ,反之亦然。 你还可以用 init 代理调用 init! ,但这会触发一个断言: init! 构造器是否会触发构造失败?



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值