swift学习笔记3——类、结构体、枚举

以前学习swift时的我的笔记,根据github:the-swift-programming-language-in-chinese学习、总结,将重要的内容提取,加以理解后整理为学习笔记,方便之后查询用。详细能够参考the-swift-programming-language-in-chinese,或者苹果官方英文版文档html

当前版本是swift2.2ios

类和结构体

与 Objective-C 语言不一样的是,Swift 容许直接设置结构体属性的子属性。git

实际上,在 Swift 中,全部的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,而且在底层都是以结构体的形式所实现。在 Swift 中,全部的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。github

“等价于”(用三个等号表示,===)与“等于”(用两个等号表示,==)的不一样:swift

“等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
“等于”表示两个实例的值“相等”或“相同”,断定时要遵守设计者定义的评判标准,所以相对于“相等”来讲,这是一种更加合适的叫法。数组

Swift 中,许多基本类型,诸如String,Array和Dictionary类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。安全

Objective-C 中NSString,NSArray和NSDictionary类型均以类的形式实现,而并不是结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。app

以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来彷佛总会发生。然而,Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理全部的值拷贝以确保性能最优化,因此你不必去回避赋值来保证性能最优化。ide

常量结构体的存储属性函数

若是建立了一个结构体的实例并将其赋值给一个常量,则没法修改该实例的任何属性,即便有属性被声明为变量也不行:

注意
必须将延迟存储属性声明成变量(使用 var 关键字),由于属性的初始值可能在实例构造完成以后才会获得。而常量属性在构造过程完成以前必需要有初始值,所以没法声明成延迟属性

这种行为是因为结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的全部属性也就成了常量。
属于引用类型的类(class)则不同。把一个引用类型的实例赋给一个常量后,仍然能够修改该实例的变量属性。

若是一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则没法保证该属性只会被初始化一次。

存储属性与计算属性

存储属性没有get和setter方法,延迟存储属性即lazy懒加载

计算属性,有get方法和可选的set方法(必须有get方法),在set方法里面给计算属性赋值会形成循环调用

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即便新值和当前值相同的时候也不例外。

能够为除了延迟存储属性以外的其余存储属性添加属性观察器,也能够经过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器

父类的属性在子类的构造器中被赋值时,它在父类中的 willSet 和 didSet 观察器会被调用,随后才会调用子类的观察器

在第一个检查过程当中,willSet,didSet 属性观察器再次将其设置成了不一样的值,但这不会形成属性观察器被再次调用。有了属性观察器,就不该再有getter和setter方法

类型属性

跟实例的存储型属性不一样,必须给存储型类型属性指定默认值,由于类型自己没有构造器,也就没法在初始化过程当中使用构造器给类型属性赋值。
存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即便它们被多个线程同时访问,系统也保证只会对其进行一次初始化,而且不须要对其使用 lazy 修饰符。

继承

在 Swift 中,继承是区分「类」与其它类型的一个基本特征。

重写属性

你能够重写继承来的实例属性或类型属性,提供本身定制的 getter 和 setter,或添加属性观察器使重写的属性能够观察属性值何时发生改变。

你能够将一个继承来的只读属性重写为一个读写属性,只须要在重写版本的属性里提供 getter 和 setter 便可。可是,你不能够将一个继承来的读写属性重写为一个只读属性。

若是你在重写属性中提供了 setter,那么你也必定要提供 getter。若是你不想在重写版本中的 getter 里修改继承来的属性值,你能够直接经过super.someProperty来返回继承来的值,其中someProperty是你要重写的属性的名字。

你不能够为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不能够被设置的,因此,为它们提供willSet或didSet实现是不恰当。
此外还要注意,你不能够同时提供重写的 setter 和重写的属性观察器。若是你想观察属性值的变化,而且你已经为那个属性提供了定制的 setter,那么你在 setter 中就能够观察到任何值变化了。

存储属性的初始赋值

类和结构体在建立实例时,必须为全部存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。可选类型的属性能够不设,它将自动初始化为nil

你能够在构造器中为存储型属性赋初值,也能够在定义属性时为其设置默认值。如下小节将详细介绍这两种方法。

当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者

若是你在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每一个参数自动生成一个跟内部名字相同的外部名

若是你不但愿为构造器的某个参数提供外部名字,你可使用下划线(_)来显式描述它的外部名

默认构造器

若是结构体或类的全部属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器(default initializers)。这个默认构造器将简单地建立一个全部属性值都设置为默认值的实例。

除了上面提到的默认构造器,若是结构体没有提供自定义的构造器,它们将自动得到一个逐一成员构造器,即便结构体的存储型属性没有默认值。

指定构造器、便利构造器

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

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

init(parameters) {
    statements
}

便利构造器也采用相一样式的写法,但须要在init关键字以前放置convenience关键字,并使用空格将它们俩分开:

convenience init(parameters) {
    statements
}

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

  • 指定构造器必须调用其直接父类的的指定构造器。
  • 便利构造器必须调用同一类中定义的其它构造器。
  • 便利构造器必须最终致使一个指定构造器被调用。

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

两段式构造

Swift 的两段式构造过程跟 Objective-C 中的构造过程相似。最主要的区别在于阶段 1,Objective-C 给每个属性赋值0或空值(好比说0或nil)。Swift 的构造流程则更加灵活,它容许你设置定制的初始值,并自如应对某些属性不能以0或nil做为合法默认值的状况。

Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能不出错地完成:

安全检查 1

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

如上所述,一个对象的内存只有在其全部存储型属性肯定以后才能彻底初始化。为了知足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理以前先完成初始化。

安全检查 2

指定构造器必须先向上代理调用父类构造器,而后再为继承的属性设置新值。若是没这么作,指定构造器赋予的新值将被父类中的构造器所覆盖。

安全检查 3

便利构造器必须先代理调用同一类中的其它构造器,而后再为任意属性赋新值。若是没这么作,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。

安全检查 4

构造器在第一阶段构造完成以前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用self做为一个值。

类实例在第一阶段结束之前并非彻底有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。

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

阶段 1

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

阶段 2

  • 从顶部构造器链一直往下,每一个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时能够访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器能够有机会定制实例和使用self。
  1. 调用构造器后,先完成实例内存的分配,再保证本身引入的存储性属性都被初始化(有默认值也可),而后调用父类的构造器(没有设置默认值的存储性属性,必须在构造器初始化完成后再调用父类的指定构造器),当父类有多个指定构造器,只调用其中一个便可
  2. 存储性属性必须先初始化后才能使用,特别是在构造器中尤为要注意。父类的存储属性,只有调用完父类的构造器以后才能用self来右值访问,使用self来左值赋值
class Food {
    var name: String
    init(name: String) {
//        print(self.name)  // 这里访问会出错,由于self.name尚未初始化
        self.name = name    // 初始化 self.name
        print(self.name)    // 已经初始化,能够访问
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

可失败构造器

init?()

你能够用非可失败构造器重写可失败构造器,但反过来却不行。

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

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

类,结构体,枚举的可失败构造器能够横向代理到类型中的其余可失败构造器。相似的,子类的可失败构造器也能向上代理到父类的可失败构造器。

不管是向上代理仍是横向代理,若是你代理到的其余可失败构造器触发构造失败,整个构造过程将当即终止,接下来的任何构造代码不会再被执行。

可失败构造器,能够不向上代理

必要构造器

在类的构造器前添加required修饰符代表全部该类的子类都必须实现该构造器:

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

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

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

析构过程

析构器只适用于类类型,当一个类的实例被释放以前,析构器会被当即调用。在类的定义中,每一个类最多只能有一个析构器.

析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,而且在子类析构器实现的最后,父类的析构器会被自动调用。即便子类没有提供本身的析构器,父类的析构器也一样会被调用

由于直到实例的析构器被调用后,实例才会被释放,因此析构器能够访问实例的全部属性,而且能够根据那些属性能够修改它的行为

枚举

Swift 中的枚举更加灵活,没必要给每个枚举成员提供一个值。若是给枚举成员提供一个值(称为“原始”值),则该值的类型能够是字符串,字符,或是一个整型值或浮点数。

下面是用枚举表示指南针四个方向的例子:

enum CompassPoint {
    case North
    case South
    case East
    case West
}

与 C 和 Objective-C 不一样,Swift 的枚举成员在被建立时不会被赋予一个默认的整型值。在上面的CompassPoint例子中,North,South,East和West不会被隐式地赋值为0,1,2和3。相反,这些枚举成员自己就是完备的值,这些值的类型是已经明肯定义好的CompassPoint类型。

原始值

enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

枚举类型ASCIIControlCharacter的原始值类型被定义为Character,对于一个特定的枚举成员,它的原始值始终不变

相关文章
相关标签/搜索