Swift基础9

下标脚本

下标脚本 可以定义在类、结构体和枚举这些目标中,可以认为是访问集合(collection),列表(list)或序列(sequence)的快捷方式,使用下标脚本的索引设置和获取值,不需要在调用实例的特定的赋值和访问方法。

对于同一个目标可以定义多个下标脚本,通过索引值类型的不同来进行重载,下标脚本不限于单个纬度,我们可以定义多个入参的下标脚本满足自定义类型的需求。

下标脚本语法

下标脚本允许我们通过在实例后面的方括号中传入一个或者多个索引值来对实例进行访问和赋值。语法类似于实例方法和计算型属性的混合。与定义实例方法类似,定义下标脚本使用subscript关键字,显式声明入参(一个或多个)和返回类型。与实例方法不同的是下标脚本可以设定为读写和只读。这种方式又有点像计算型属性的getter和setter:

subscript(index: Int) -> Int {
    get {
        //返回入参匹配的Int类型的值
    }
    
    set(newValue){
        //执行赋值操作
    }

}

其中newValue的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明newValue就算不写,在set代码块中依然可以使用默认的newValue这个变量来访问新赋的值。

与只读计算型属性一样,可以直接将原本应该写在get代码块中的代码写在subscript中:

subscript(index:Int) -> {
    //返回与入参匹配的Int类型的值
    return ......
}

eg:

struct TimesTable {
    var time : Int
    
    subscript(index:Int) ->Int {
        
        return index * time
    }
    
}

let theTime = TimesTable(time: 20)
print(theTime[20])

下标脚本用法

根据使用场景不同下标脚本也具有不同的含义。通常下标脚本是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。我们可以在我们自己特定的类或结构体中自由的实现下标脚本来提供合适的功能。

下标脚本选项

下标脚本允许任意数量的入参索引,并且每一个入参类型也没有限制。下标脚本的返回值也可以是任何类型。下标脚本可以使用参数和可变参数,但使用写入读出参数或给参数设置默认值都是不允许的。

一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过入参的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本进行运行,这就是下标脚本的重载。

struct Test  {
    var x = 0, y = 0, z = 0
    
    
    subscript(x:Int) ->Int {
        
        get{
            switch x {
            case 0:
                return self.x
            case 1:
                return self.y
            case 2:
                return self.z
                
            default:
                return 0
            }
        }
        
        set(newValue){
            switch x {
            case 0:
                self.x = newValue
            case 1:
                self.y = newValue
            case 2:
                self.z = newValue
                
            default:
                print("error")
            }
        }
        
    }
    
    subscript(x:String) -> Int{
        
        get{
            switch x {
            case "0":
                return self.x
            case "1":
                return self.y
            case "2":
                return self.z
                
            default:
                return 0
            }
        }
        
        set(newValue){
            switch x {
            case "0":
                self.x = newValue
            case "1":
                self.y = newValue
            case "2":
                self.z = newValue
                
            default:
                print("error")
            }
        }
        
    }
    
    subscript(x:Int ,y:Int )-> Int{
        return 10000
    }
    
}


var instance = Test(x: 10, y: 20, z: 30)
instance[0] = 100
print(instance)

instance["0"] = 200
print(instance)


instance[1,2]

继承

一个类可以继承另一个类的方法,属性和其他特性。当一个类继承其他类时,继承类叫子类,被继承类叫超类。在Swift中,继承是区分 类 与其他类型的一个基本特征。

在Swift中,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写这些方法,属性和下标脚本来优化或修改它们的行为。Swift会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。

可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。

定义一个基类

不继承于其它类的类,称之为基类。

note: Swift中的类并不是从一个通用的基类继承而来。如果我们不为我们定义的类指定一个超类的话,这个类就自动成为基类。

class Vehicle {
    
    var currentSpeed = 0.0
    
    var description: String {
        return "traveling at \(currentSpeed) mils per hour"
    }
    
    func makeNoise(){
        //什么也不做因为车辆不一定都会有噪音
    }
    
    
}

子类生成

子类生成 指的是在一个已有类的基础上创建一个新的子类。子类继承超类的特征,并且可以优化或改变它。我们还可以为子类添加新的特征。

为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:

class someClass : someSuperClass {
//  类定义
}
class Bicycle : Vehicle {
     var hasBaseket = false
    
}

重写

子类可以为继承而来的实例方法,类方法,实例属性或下标脚本提供自己定制的实现。我们把这种行为叫重写。

如果要重写某个特性,我们需要在重写定义的前面加上override关键字。这么做,我们就可以明确我们是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override关键字的重写都会在编译时被诊断为错误。

override关键字会提醒Swift编译器去检查该类的超类是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。

访问超类的方法,属性以及下标脚本

当我们在子类中重写超类的方法,属性或下标脚本时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,我们可以优化已有实现的行为,或在一个继承来的变量中存储一个修改过的值。

在合适的地方,我们可以使用super前缀来访问超类版本的方法、属性或下标脚本:
*.在方法someMethod的重写实现中,可以通过super.someMethod()来调用超类版本的someMethod方法。
*.在属性someProperty的getter或setter的重写实现中,可以通过super.someProperty来访问超极版本的someProperty的属性。
*.在下标脚本的重写实现中,可以通过super[xxx]来访问超类版本中的相同下标脚本。

重写方法

在子类中,我们可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。

class Train: Vehicle {
    
    override func makeNoise() {
        print("uuuuuuuuuu")
    }
    
}

重写属性

我们可以重写继承来的实例属性或类属性,提供自己定制的getter和setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。

1.重写属性的getters和setters
我们可以提供定制的getter或setter来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。我们重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。

2.我们可以将一个继承来的只读属性重写为一个读写属性,只需要我们在重写版本的属性里提供getter和setter即可。但是,我们不可以将一个继承来的读写属性重写为一个只读属性。

note:如果我们在重写属性中提供了setter,那么我们也一定要提供getter。如果我们不想在重写版本的getter里修改继承来的属性值,我们就可以直接通过super.someProperty来返回继承来的值,其中someProperty是我们要重写的属性名字。

class Car : Vehicle {
    
    var gear = 1
    
    override var description: String {
        
        return super.description + " in gear \(gear)"
        
    }
    
}

let car = Car()
car.currentSpeed =  100
car.gear = 3
print("\(car.description)")

3.重写属性观察器
我们可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,我们就会被通知到,无论那个属性原本是如何实现的。

note:我们不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供观察器是不恰当的。此外还要注意,我们不可以同时提供重写的setter和重写的属性观察器。如果我们想观察属性值的变化,并且我们已经为那个属性提供了定制的setter,那么我们在setter中就可以观察到任何值的变化了。

class AutoCar : Car {
    
    
    override var currentSpeed: Double {
        
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
    
    
}

防止重写

我们可以通过把方法,属性或下标脚本标记为final来防止它们被重写,只需要在声明关键字前加上final特性即可。(eg: final var , final func ,final class func 以及final subscript)

如果我们重写了final方法,属性或下标脚本,在编译时会报错。在类扩展中的方法,属性或下标脚本也可以在扩展定义里记为final。

我们可以通过在关键字class前添加 final 特性 来将整个类标记为final的,这样的类时不可被继承的,任何子类试图继承此类时,在编译时会报错。

构造过程

构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必需执行这个过程,具体操作包括设置实例中每个存储属性的初始值和执行其他必须的设置或初始化工作。

通过定义构造器来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与objective-c中的构造器不同,Swift的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完全正确的初始化。

类的实例也可以通过定义 析构器 在实例释放之前执行特定的清除工作。

存储属性的初始赋值

类和结构体在创建实例时,必须为所有的存储属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
我们可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
note:当我们为存储型属性设置默认值或者在构造器中为其赋值时,它们时被直接复制的,不会触发任何属性观察器。

构造器

构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

init(){
//在此处执行构造过程
}

下面例子中定义了一个用来保存华氏温度的结构体 Fahrenheit ,它拥有一个Double类型的存储类型temperature:

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

var f = Fahrenheit()
print("the default temperature is \(f.temperature)")

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

默认属性值

如前所述,我们可以在构造器中为存储型属性设置初始化值。同样,我们也可以在属性声明时为其设置默认值。
note:如果一个属性总是使用相同的初始值,哪么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的效果是一样的,只不过是用默认值让属性的初始化和声明结合的更紧密。使用默认值能让我们的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性。

struct Fahrenheit {
    
    var temperature =  32.0

}

自定义构造过程

我们可以通过输入参数和可选属性类型来自定义构造过程,也可以在构造过程中修改常量属性。

1.构造参数:
自定义 构造器 时,可以在定义中提供构造器参数,指定所需值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。


struct Test {
    
    var tmpValue : Double
    var tmpValue2: Double
    
    init(tmpValue value:Double){
        tmpValue = value
        tmpValue2 = 0.0
    }
    
    init(tmpValue2 value:Double){
        tmpValue2 = value
        tmpValue = 0.0
    }
    
}

let test1 = Test(tmpValue: 100)
let test2 = Test(tmpValue2: 200)

2.参数的内部名称和外部名称
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。

然而,狗仔器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中参数名和类型来确定需要调用的构造器。正因为参数如此重要,如果我们在定义构造器时没有提供参数的外部名字,swift会为每一个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每一个构造参数之前加了一个哈希符号。


struct Color {
    
    let red,green,blue:Double
    
    init(red:Double,green:Double,blue:Double){
        
        self.red = red
        self.green = green
        self.blue = blue
    }
    
    init(white:Double){
        
        red = white
        green = white
        blue = white
    }
    
    
}

let color1 = Color(red: 1.0, green: 0.0, blue: 1.0)
let color2 = Color(white: 0.5)

3.不带外部名的构造器参数
如果你不希望为构造器的某个参数提供外部名字,我们可以使用下划线(_)来显示描述它的外部名,以此重写上面所说的默认行为。

struct Color {
    
    let red,green,blue:Double
    
    init(red:Double,green:Double,blue:Double){
        
        self.red = red
        self.green = green
        self.blue = blue
    }
    
    init(white:Double){
        
        red = white
        green = white
        blue = white
    }
    
    init(_ color : Color){
        self = color
    }
    
}

let color1 = Color(red: 1.0, green: 0.0, blue: 1.0)
let color2 = Color(white: 0.5)
let color3 = Color(color1)

4.可选属性类型
如果我们定制的类型包含一个逻辑上允许取值为空的存储型属性---不管时因为它无法在初始化时赋值,还说因为它可以在之后某个时间点可以赋值为空---我们都需要将它定义为可选类型optional type。可选类型的属性将自动初始化为空nil,表示这个属性时故意在初始化时设置为空的。

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

let question = Question(text: "Do you like China")
question.ask()
question.response = "Yes , i like"

调查问题在问题提出之后,我们才能得到答案。所以我们将属性回答response声明为String?类型。或者说是可选字符串类型optional String。当Question实例化时,它将自动赋值为空nil,表明暂时还不存在此字符串。

5.构造过程中常量属性的修改
我们可以在构造工程中的任意时间点修改常量属性的值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。

note:对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改:不能在子类中修改。

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

let question = Question(text: "Do you like China")
question.ask()
question.response = "Yes , i like"

默认构造器

如果结构体和类的所有属性都有默认值,同时没有自定义的构造器,那么Swift会给这些构造器和类创建一个默认构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。

class ShoppingListItem {
    var name: String?
    var num = 1
    var price = 10.0
}

var item = ShoppingListItem()

结构体的逐一成员构造器

除了上面提到的默认构造器,如果构造器对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。

逐一成员构造器是用来初始化结构体新实例成员属性的快捷方式。我们在调用逐一成员构造器时,通过与属性名相同的参数名进行传值类完成对成员属性的初始化赋值。

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

let theSize = Size(width: 2.5, height: 3.5)

值类型的构造器代理

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

构造器代理的实现规则和形式在值类型和类类型中有所不同,值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其他构造器。类则不同,他可以继承自其他类,这意味着类有责任保证其所有继承的存储属性在构造时也能正确的初始化。

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

如果我们为某个值类型定义了一个定制的构造器,哪么我们将无法访问到默认的构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止我们在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还说错误的使用了那个自动生成的构造器。

note:如果我们像通过默认构造器、逐一对象构造器以及我们自己定义的构造器为值类型创建实例,我们建议将自己定制的构造器写到扩展(extension)中,而不是跟值类型定义混在一起。

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

struct Size {
    var width = 0.0, height = 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.0
        let originY = center.y - size.height / 2.0
        
        let origin = Point(x: originX, y: originY)
        
        self.init(origin:origin,size:size)
    }
    
}

类的继承和构造过程

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

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

指定构造器和便利构造器

1.指定构造器 是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

2.便利构造器时类中比较次要的,辅助型的构造器。我们可以定义便利构造器来调用同一个类中指定构造器,并为其参数提供默认值。我们也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

我们应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。

自定构造器和便利构造器的语法

类的指定构造器的写法跟值类型简单构造器一样:

init(parameters){
    //statements
}

便利构造器也采用相同样式的写法,但需要在init关键字前放置convenience关键字

convenience init(parameters){
    //statements
}

类的构造器代理规则

为了简化指定构造器和便利构造器之间的调用关系,Swift采用以下三条规则来限制构造器之间的代理调用:

  • 指定构造器必须调用其直接父类的指定构造器。
  • 便利构造器必须调用同一类中定义的其他构造器。
  • 便利构造器必须最终以调用一个指定构造器结束。

一个便于记忆的方法是:

  • 指定构造器必须宗师向上代理
  • 便利构造器必须总是横行代理

两段式构造过程

Swift中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层结构中给予了每个类完全的灵活性。两端式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器以外地赋予不同的值。

note:swift的两段式构造过程跟Objective-c中的构造过程类似。最主要的区别在于阶段 1,Objective-C给每一个属性赋值0或空值(比如说nil)。Swift的构造流程则更加灵活,它允许哦嘛设置定制的初始值,并自如应对某些属性不能以0或者nil作为合法默认值的情况。

安全检查:
1.指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。

2.指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没有这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。

3.便利构造器必须先代理调用同一类中的其他构造器,然后再为任意属性赋新值。如果没有这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

4.构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,self的值不能被引用。



以下是两段式构造过程中基于上述安全检查的构造流程展示:

阶段1:

  • 某个指定构造器或便利构造器被调用;
  • 完成新实例内存的分配,但此时内存还没有被初始化;
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
  • 指定构造器调用父类的构造器,完成父类属性的初始化;
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
  • 当到达了构造器链的最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完成初始化。此时阶段1完成。

阶段2:

  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。

构造器的继承和重写

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

note:父类的构造器仅在确定和安全的情况下被继承。

假如我们希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,我们可以在定制的子类中提供和重写父类型相同的构造器。

当我们重写一个父类中带有指定构造器的子类构造器时,我们需要重写这个指定的构造器。因此,我们必须在定义子类构造器时带上override修饰符。即使我们重写系统提供的默认构造器也需要带上override修饰符。

相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,每个规则都是在上文类的构造器代理规则有所描述。因此,我们的子类不必(严格意义上来讲)提供了一个父类构造器的重写。这样的结果就是,你不需要在子类中提供一个匹配的父类便利构造器实现。

自动构造器的继承

如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器时可以被自动继承的。在实践中,这意味着对许多常见场景你不必重写父类的构造器,并且尽可能安全的情况下以最小的代价来继承父类的构造器。

假设要为子类中引入的任何新属性提供默认值:

  • 如果子类没有定义任何构造器,它将自动继承所有父类的指定构造器。
  • 如果子类提供了所有父类指定构造器的实现---不管时同哟规则1继承过来的,还说通过自定义实现的---它将自动继承所有父类的便利构造器。

指定构造器和便利构造器实例

接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。它定义了包含三个类Food、RecipeIngredient以及ShoppingListItem的类层次结构,并将演示它们的构造器是如何相互作用的。

类层次中的基类是Food,它是一个简单的用来封装食物名字的类。Food类引入了一个叫做name的String属性,并提供了两个构造器来创建Food实例。


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

类没有提供一个默认的逐一成员构造器,所以Food提供了一个接受单一参数name的指定构造器。这个构造器可以使用一个特定的名字来创建新的Food实例

let nameMeat = Food(name:"bacon")

Food类中的构造器init(name:String)被定义为一个指定构造器,因为它能确保所有新Food实例中存储型属性都被初始化。Food类没有父类,所以init(name:String)构造器不需要调用super.init()来完成构造。

Food类同样提供了一个没有参数的便利构造器init()。这个init()构造器为新食物提供了一个默认占位名字,通过代理调用同一类中定义的构造器init(name:String)并给参数name传值[Unnamed]来实现:

let food = Food()

类层级中的第二个类是Food的子类RecipeIngredient。RecipeIngredient类构建了食谱中的一味调味剂。它引入了Int类型的数量属性quantity,并且定义了两个构造器来创建RecipeIngredient实例:

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

RecipeIngredient类拥有一个指定构造器init(name:String, quantity:Int),它可以用来产生新的实例。这个构造器一开始先将传入的quantity参数赋值给quantity属性,这个属性也是唯一在RecipeIngredient中新引入的属性。随后,构造器在任务向上代理给Food的init(name:String).这个过程满足两段式构造过程中的安全检查1。

RecipeIngredient也定义了一个便利构造器init(name:String),它只通过name来创建RecipeIngredient实例。这个变量构造器假设任意RecipeIngredient实例的quantity为1,所以不需要显示指明数量即可创建出实例。这个便利构造器的定义可以让创建实例更加方便和快捷,并且避免了使用重复代码创建多个quantity为1的实例。这个便利构造器之时简单的将任务代理给了同一个类里提供的指定构造器。

在这个例子中,RecipeIngredient的父类是Food,它有一个便利构造器init()。这个构造器因此也被RecipeIngredient继承。这个继承的init()函数版本跟Food提供的版本一样的,除了任务代理给RecipeIngredient版本的init(name:String)而不是Food提供的版本。

所有的这三种构造器都可以用来创建新的RecipeIngredient实例:

let one = RecipeIngredient()
let two = RecipeIngredient(name:"bacon")
let three = RecipeIngredient(name:"eggs",quantity:5)

类层次中第三个也是最后一个类是RecipeIngredient子类,叫做ShoppingListItem。这个类构建了购物单中出现的某一种调味料。

购物单中每一项都是从未购买状态开始的。

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

由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,ShoppingListItem将自动继承所有父类中指定构造器和便利构造器。

可失败构造器

如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败的构造器,是非常有用的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或者是不满足某种必要的条件等。

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

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

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

枚举类型的可失败构造器

我们可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。还能在参数不满足枚举成员期望的条件时,构造失败。

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.")
 }

类的可失败构造器

值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。

而对类而言,就没有那么幸运了。类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。

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

构造失败的传递

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

无论时向上代理还氏横向代理,如果我们代理的可失败构造器,在构造过程中触发了构造失败的行为,整个构造过程都将被立即终止,接下来任何的构造代码都将不会被执行。

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


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

    } 

  }

重写一个可失败构造器

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

note:当我们用一个子类的非可失败构造器重写一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。一个非可失败的构造器永远也不能代理调用一个可失败的构造器。
我们可以用一个非可失败构造器重写一个可失败的构造器,但分过来却行不通。

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

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

可失败构造器init!

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

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

必要构造器

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

 class SomeCalss {
    required init(){
        //在这里添加该必要构造器的实现代码
    }
 
 }

在子类重写父类的必须要构造器时,必须在子类的构造器前夜添加required修饰符,这是为了保证继承链上子类构造器也是必要构造器。在重写父类的必要构造器时,不需要加override修饰符:
note:如果子类继承的构造器能满足必要的构造器的需求,则无需显示的在子类中提供必要构造器的实现。

class SomeSubClass : SomeClass {

    required init(){
        //statement 
    }
    
}

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

如果某个存储属性的默认值需要特别的定制或准备,我们就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。

class SomeClass {

    let someProperty: SomeType = {
        //.........
        return someValue 
    }()

}

析构过程

析构器只适用于类类型,当一个类的实例被释放前,析构器会被立即调用。析构器用关键字deinit来标示,类似于构造器要用init来标示。

析构过程原理

Swift会自动释放不再需要的实例以释放资源。Swift通过自动引用计数(ARC)处理实例的内存管理。通常当我们的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,我们可能需要进行一些额外的处理。比如,打开一个文件,哪么就需要手动去关闭一个文件。

在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数:

deinit {
    //执行析构过程
}

析构器时在实例释放发生前被自动调用。析构器时不允许被主动调用的。子类继承了负累的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。

因为直到实例的析构器被调用时,实例才会被释放,所以析构器可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)

析构器操作

class Animal {
    
    deinit{
        print("animal dead")
    }
    
}

class Dog: Animal{
    
    deinit{
        print("Dog dead")
    }
}

func doSomething() {
    
    _ = Dog()
    
}

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

推荐阅读更多精彩内容