Swift中构造过程

1. 存储属性的初始赋值

1.1 构造器

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}

var f = Fahrenheit()
print("The default temperature is \(f.temperature)")
// print "The default temperature is 32.0"

上面这个结构体定义了一个不带参数的构造器init,并将存储属性temperature值初始化为32.0

1.2 默认属性值

struct Fahrenheit {
    var temperature = 32.0
}

使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型。

2 自定义构造过程

2.1 构造参数

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

第一个构造器拥有一个构造参数,外部参数名为fromFahrenheit,内部参数名为fahrenheit

2.2 不带外部名的构造器参数

我们可以用_来代替外部参数名。

2.3 可选属性类型

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}

3. 默认构造器

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}

var item = ShoppingListItem()

尽管代码中没有显式地为name属性设置默认值,但由于name是可选字符串类型,它将默认值设置为nil

3.1 结构体的逐一成员构造器

struct Size {
    var width = 0.0, height = 0.0
}

let twoByTwo = Size(width: 2.0, height: 2.0)

4. 值类型的构造器代理

构造器可以通过调用其它构造器来完成实例部分构造过程。这一过程成为构造器代理,它能减少多个构造器间的代码重复。

如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐 一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用 自动生成的构造器。

假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器 写到扩展(extension)中,而不是写在值类型的原始定义中。

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    
    init() {
        
    }
    
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

5. 类的继承和构造过程

类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。

Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利 构造器。

5.1 指定构造器和便利构造器的语法

指定构造器:

init(parameters) {
    statements
}

便利构造器:

convenience init(parameters) {
    statements
}

5.1 类的构造器代理规则

规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同类中定义的其它构造器。
规则 3
便利构造器必须最终导致一个指定构造器被调用。

一个更方便的记忆方法是:指定构造器必须总是向上代理,便利构造器必须总是横向代理。

5.2 构造器的继承和重写

跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止 一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

子类Bicycle定义了一个指定构造器init()。调用super.init()方法,可以确保Bicycle在修改属性之前,它所继承的属性numberOfWheels能被Vehicle类初始化。

注意
子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。

5.3 构造器的自动继承

假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:

规则 1
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。

规则 2
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将 自动继承所有父类的便利构造器。

5.4 指定构造器和便利构造器实践

class Food {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

class RecipeIngredient: Food {
    var quantity: Int
    
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? "√" : "×"
        return output
    }
}

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6)
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// print 
//"1 x Orange juice √
//1 x Bacon ×
//6 x Eggs ×
//"

6. 可失败构造器

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
}

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

if let giraffe = someCreature {
    print("\(giraffe.species)")
}
// print "Giraffe"

let anonymousCreature = Animal(species: "")

if anonymousCreature == nil {
    print("not initialized")
}
// print "not initialized"

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

你可以通过带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员,如果提供的参数无法匹配任何枚举成员,则构造失败。

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("initialization succeeded")
}

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("initialization failed")
}
// print "initialization failed"

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

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

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

enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("initialization succeeded.")
}
// print "initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("initialization failed.")
}
// print "initialization failed."

6.3 构造失败的传递

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty {return nil}
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name:String, quantity: Int) {
        if quantity < 1 {return nil}
        self.quantity = quantity
        super.init(name: name)
    }
}

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("initialization succeeded")
}
// print "initialization succeeded"

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("initialization succeeded")
} else {
    print("Unable to initialize")
}
// print "Unable to initialize"

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("initialization succeeded")
} else {
    print("Unable to initialize")
}
// print "Unable to initialize"

6.4 重写一个可失败构造器

class Document {
    var name: String?
    init() {
        
    }
    init?(name: String) {
        if name.isEmpty {return nil}
        self.name = name
    }
}

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
        }
    }
}

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")
    }
}

6.5 可失败构造器init!

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

一旦init!构造失败,则会触发一个断言。

7. 必要构造器

在类的构造器前添加required修饰符表明所有该类的子类都必须实现该构造器:

class SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加override修饰符。

class SomeSubclass: SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

8. 通过闭包或函数设置属性的默认值

class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
}

闭包结尾的小括号告诉 Swift 立即执行次闭包,如果忽略了括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。

注意:
如果使用闭包来初始化属性,闭包在执行时,实例的其它部分还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的self属性,或者调用任何实例方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • 本章将会介绍 存储属性的初始赋值自定义构造过程默认构造器值类型的构造器代理类的继承和构造过程可失败构造器必要构造器...
    寒桥阅读 749评论 0 0
  • 构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个...
    莽原奔马668阅读 661评论 0 3
  • 123.继承 一个类可以从另外一个类继承方法,属性和其他特征。当一个类继承另外一个类时, 继承类叫子类, 被继承的...
    无沣阅读 1,310评论 2 4
  •  构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个...
    EndEvent阅读 613评论 0 3
  • 有人说:“北京是个好地方啊,到处都是拼搏的人,稍微慢一点就赶不上了” 12号的火车,坐了一天一夜,次日来到大中华的...
    木宁夏枸杞阅读 375评论 0 0