Swift3.0 构造过程整理

构造过程

  • 概念:在使用类、结构体或者枚举类型的实例之前的准备过程称之为构造过程。
  • 操作内容:设置每个储存型属性的初始值和其他的必要的初始化工作
  • 实现方式:定义构造器

构造器

  • 概念:创建某个特定类型的新实例的一种特殊方法。
//最简形式
init() {
    
    //此处进行构造过程

}

注意:Swift的构造器无需返回值,OC有

存储属性的初始赋值

  • 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态
  • 赋值方式:
  • 定义属性时直接设置默认值
  • 在构造器中为其赋初始值
//示例代码:
struct Test {

   let attribute = "bluajack"
   
}

struct Test {

    let attribute: String
    
    init(value: String) {
    
        //attribute = "bluajack"
        attribute = value
        
    }

}
let t = Test(value: "bluajack")
  1. 枚举是没有存储属性的,但是有计算属性。2.常量属性(let声明)一旦赋值后,就不能更改了。在本类的常量属性,其子类也不能修改这个常量属性。

默认构造器

  • 结构体或类的所有属性值都有默认值,没有自定义构造器,系统会自动提供一个默认构造器
//示例:
class Person {

    let name = "bluajack"
    
    var sex: String?//可选属性默认值为nil
    
}
let p = Person() //默认构造器 init(){}

结构体的逐一成员构造器

//示例
struct Test {

    let attribute = "bluajack"
    let attribute2: String
}
let t = Test(attribute2: "sb")
  • 看到这里,有人或许会不明白。不是说,所有的存储属性都必要要有合适的初始值么?那为什么attribute2没有赋值,却不报错呢?

  • 那么下面我就介绍下,专属于结构体的————逐一成员构造器

  • 先举例,后总结

//范例1
struct Test {
    
    let attribute = "bluajack"
    let attribute2: String
    
}

//1.结构体test的存储属性全用常量声明
//2.存储属性中有属性没有赋初始值

//结果
let t = Test(attribute2: "sb")
//自动生成一个逐一成员构造器,但只有attribute2参数

//范例2
struct Test2 {
    
    let attribute = "bluajack"
    let attribute2: String = "sb"

}

//1.结构体test的存储属性全用常量声明
//2.存储属性全部赋值完毕

//结果
let t2 = Test2()
//没有生成逐一成员构造器,只有生成了默认的构造器

//范例3
struct Test3 {
    
    var attribute = "bluajack"
    var attribute2: String = "sb"
    
}

//1.结构体test的存储属性全用变量声明
//2.存储属性全部赋值完毕

//结果
let t3 = Test3(attribute: "s", attribute2: "b")
let t_3 = Test3()
//生成了逐一成员构造器,并且所有参数都存在,同时也生成了默认构造器

//范例4
struct Test4 {
    
    var attribute = "bluajack"
    var attribute2: String
    
}
//1.结构体test的存储属性全用变量声明
//2.存储属性中有属性没有赋初始值

//结果
let t4 = Test4(attribute: "s", attribute2: "b")
//生成了逐一成员构造器,并且所有参数都存在,但没有生成默认构造器

//范例5
struct Test5 {
    
    let attribute = "bluajack"
    var attribute2: String
    
}

let t5 = Test5(attribute2: "sb")

//范例6
struct Test6 {
    
    var attribute = "bluajack"
    let attribute2: String
    
}

let t6 = Test6(attribute: "s", attribute2: "b")

  • 归纳

  • 1.也就是说,结构体中,只要有变量存储属性存在,并且没有提供自定义的构造器的话,那么一定会生成一个结构体逐一成员构造器。

  • 2.结构体中没有变量存储属性存在,没有提供自定义的构造器,但常量存储属性没有赋值的话,也会生成一个结构体逐一成员构造器,但需要注意的是,生成的构造器中的构造参数,只会与没赋值的常量属性一一对应。

  • 总结

总结:结构体未提供自定义构造器 ->「 存在变量属性 -> 生成
                              不存在变量属性「 常量属性全部赋值 ->不生成
                                            存在常量属性未赋值 -> 生成
                                            」

值类型的构造器代理

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

  • 对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用self.init

千万记住,这种横向代理,只对值类型有效,类类型无效,类型有自己的规则。

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

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)
    }
}
//第三个Rect构造器init(center:size:)。它先通过center和size的值计算出origin的坐标,
//然后再调用(或者说代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中。

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

指定构造器

  • 简介:类中最主要的构造器,初始化类中提供的所有属性,并根据父类链往上调用父类的指定构造器来实现父类的初始化。
  • 每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

便利构造器

  • 简介:简介:类中比较次要的、辅助型构造器。
  • 作用:可以创建一个特殊用途或特定输入值的实例,某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
  • 写法:便利构造器也采用相同样式的写法,但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开

class A {

    var a:String
    
    //指定构造器
    init() { a = "123"}
    
    //便利构造器
    convenience init(value: String) {
    
        self.init()
    }

}

class B:A {

    //指定构造器
    init(hah: String) {
        
        
//        super.init()
        
//        a = hah
        
        //如果我们不需要改变a的话,我们可以不用显示的调用super.init()
        //因为这里是初始化的最后了,Swift替我们自动完成了。不过我一般还是加上,看的顺眼
    
    }


}

let b = B(hah: "567657")
print(b.a)

类的构造器代理

  • 1.指定构造器必须调用其直接父类的的指定构造器。(没父类就不用调用了)
  • 2.便利构造器必须调用同类中定义的其它构造器。(可以是便利构造器,也可以指定构造器)
  • 3.便利构造器必须最终导致一个指定构造器被调用。

总儿言之,言儿总之
指定构造器必须总是向上代理
便利构造器必须总是横向代理

initializerDelegation01_2x.png

二段式构造过程

  • 阶段一
  • 某个指定构造器或便利构造器被调用。
  • 完成新实例内存的分配,但此时内存还没有被初始化。
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
  • 指定构造器将调用父类的构造器,完成父类属性的初始化。
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
  • 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
twoPhaseInitialization01_2x.png
  • 阶段二
  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。
twoPhaseInitialization02_2x.png

构造器的继承和重写

  • Swift中的子类默认情况下不会继承父类的构造器。
  • 重写父类的这个指定构造器,必须带上override修饰符。因为便利构造器是横向代理,子类不能调用父类的便利构造器。所以你在子类写一个和父类便利构造器一模一样的方法。不需要加override,也不算重写。
  • 当你重写一个父类的指定构造器时,你总是需要写override修饰符,即使你的子类将父类的指定构造器重写为了便利构造器。
class C {
    
    init(a: String) {print("?")}

    init(b: String) {}
}

class D:C {
    
    override init(a: String) {
    
        super.init(a: a)
        
    }
    override convenience init(b: String) {
    
        self.init(a: b)
        
    }

}

构造器的自动继承

  • 子类在默认情况下不会继承父类的构造器,满足以下条件,父类的构造器是可以被继承的。

规则1:如果子类没有定义任何指定构造器,它将自动继承父类的所有指定构造器。
规则2:如果子类提供了所有父类的指定构造器的实现,还是自定义重写提供了实现(将父类的指定构造器重写为便利构造器也可以)————它将自动继承父类所有的便利构造器。

  • 即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

可失败构造器

  • 构造失败:给构造器传入无效的参数值,缺少某种所需的外部资源,又或是不满足某种必要的条件等。为了妥善处理这种构造过程中可能会失败的情况。用可失败构造器即可。语法:init?

注意:可失败构造器不能与其他非可失败构造器同名。

  • 可失败构造器创建的对象是自身类型的可选类型的对象。可以通过在构造器里面 return nil来表示构造失败。

注意:严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用return nil 表明可失败构造器构造失败,而不要用关键字return来表明构造成功。

struct Person {
    let name: String
    init?(value: String) {
        if value.isEmpty { return nil }
        name = value
    }
}

let p = Person(value: "bluajack")
//p的类型是 Person?而不是Person

if let someone = p {
    print("一个名为\(someone.name)的Person对象被创建成功了!")
}

let p2 = Person(value: "")
//p2的类型是 Person?而不是Person

if p2 == nil {
    print("Person对象初始化失败")
}

枚举类型的可失败构造器

enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(value: Character) {
        switch value {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}

let a = TemperatureUnit(value: "F")
if a != nil {
    print("这是一个定义温度的单位,所以初始化成功")
}

let unknownUnit = TemperatureUnit(value: "X")
if unknownUnit == nil {
    print("这不是一个定义温度的单位,所以初始化失败")
}

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

⚠️:带原始值的枚举类型会自带一个可失败构造器( init?(rawValue:) ),该可失败构造器有一个名为rawValue的参数,其类型和枚举类型的原始值的类型一致。

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

let b = Temperatureunit2(rawValue: "F")
if b != nil {
    print("这是一个定义温度的单位,所以初始化成功")
}

let unknownunit = Temperatureunit2(rawValue: "X")
if unknownunit == nil {
    print("这不是一个定义温度的单位,所以初始化失败")
}

构造失败的传递

  • 类、结构体、枚举可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。有点屌,既可以横向代理,也可以向上代理。
  • 无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造内部的代码都不会再被执行。

注意:可失败构造器也可以代理到其他的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。可失败构造器和类的构造器自动继承规则一样。

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

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

重写一个可失败构造器

  • 子类可以重写父类的可失败构造器。也可以用子类的非可失败构造器重写一个父类的可失败构造器,这样,你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。

注意:子类的非可失败构造器重写父类的可失败构造器时,向上代理到 父类的可失败构造器的唯一方式是 对父类的可失败构造器的返回值进行强制解包。

注意:非可失败构造器可以重写可失败构造器,反过来不行。

class Document {
    var name: String?
    // 该构造器创建了一个 name 属性的值为 nil 的 document 实例
    init() {}
    // 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
    init?(value: String) {
        if value.isEmpty { return nil }
        name = value
    }
}

class AutoDocument: Document {
    override init() {
        super.init()
        name = "[Untitled]"
    }
    override init(value: String) {
        super.init(value: value)!
    }
}

let d = AutoDocument(value: "")
//发生运行时错误

必要构造器

  • 在类的构造器前添加required修饰符表明所有子类都必须实现该构造器,子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符。在重写父类中必要的指定构造器,不需要添加override修饰符。
class SomeClass {
    required init() {
        // 构造器的实现代码
    }
}

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

注意:如果子类继承的构造器能满足必要构造器的要求,则无须在子类的必要构造器中显式提供父类必要构造器的实现

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

  • 这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
class SomeClass {
    let someProperty: SomeType = {
        // 在这个闭包中给 someProperty 创建一个默认值
        // someValue 必须和 SomeType 类型相同
        return someValue
    }()
}

注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。

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

OVER

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

推荐阅读更多精彩内容