Swift:知识点总结

说明 时间
首次发布 2019年03月22日
最近更新 2019年07月14日

前言:此为再次系统学习Swift所做的笔记。

基本数据类型都是遵守 Hashable 协议的,它们包括字符串,整数,浮点数以及布尔值。不带有关联值的枚举类型也会⾃动遵守 Hashable。


将错误转换成可选值 ==> P237
可以使用 try? 通过将错误转换成一个可选值来处理错误。如果在评估 try? 表达式时一个错误被抛出,那么表达式的值就是 nil 。例如,在下面的代码中, xy 有着相同的数值和等价的含义:

func someThrowingFunction() throws -> Int {
    // ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
y = nil }

如果 someThrowingFunction() 抛出一个错误, xy 的值是 nil 。否则 xy 的值就是该函数的返回值。注意,无论 someThrowingFunction() 的返回值类型是什么类型, xy 都是这个类型的可选类型。例子中此函数返回一个整型,所以 xy 是可选整型。
如果你想对所有的错误都采用同样的方式来处理,用 try? 就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回 nil :

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

引用类型(类)的 extension 只能创建便捷构造器,值类型可以创建指定构造器

struct TestStruct {
    var age: Int
    var name: String
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

extension TestStruct {
    init(age: Int) {
        self.init(age: age, name: "Lily")
    }
}

let testS = TestStruct(age: 11)
print(testS.name)

实现pch功能
@_exported用于在自己模块中导出其他模块。

比如,自己定义了一个 A 模块。
在 A 模块中定义:

@_exported import UIKit

当我们引入 A 模块(import A)时,可以使用 UIKit 中的符号。

如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。

无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。

Enum的mutating
enum MyEnum {
    case high, low, off
    
    mutating func change() {
        switch self {
        case .high:
            self = .low
        case .low:
            self = .off
        case .off:
            self = .high
        }
    }
}

var status = MyEnum.high  // high
status.change()  // low
status.change()  // off
status.change()  // high

private 表示代码只能在当前作用域或者同一文件中同一类型的作用域中被使用,
fileprivate 表示代码可以在当前文件中被访问,而不做类型限定。
类、结构体和枚举要使用大驼峰命名方法,方法和属性是用小驼峰命名方法。

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。与结构体不同,类实例没有默认的成员逐一构造器。

结构体和枚举是值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮 点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是 值类型,并且在底层都是以结构体的形式所实现。

属性将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。

必须将延迟存储属性声明成变量(使用var关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

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

由于不同的字符可能会占用不同数量的内存空间,所以要知道 Character 的确定位置,就必须从 String 开 头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。

你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定 的值为nil,或者任意一个布尔条件为false,则整个if条件判断为false,这时你就需要使用嵌套 if 条 件语句来处理。

如下所示:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber, secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 输出 "4 < 42 < 100"
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
} }
}
// 输出 "4 < 42 < 100"

子类构造的三步:
  1. 设置子类声明的属性值
  2. 调用父类的构造器
  3. 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。

每个属性都 需要赋值——无论是通过声明(就像 numberOfSides )还是通过构造器(就像 name )。

如:

class Shape {
    var numberOfSides = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides"
    }
}

对于Self来说它只是表示特定类型,并且只能用在协议中或者作为某个类的方法的返回值类型,而self在实例方法中代指当前实例,在类方法中则代指当前类。

默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用_表示不使用参数标签。

如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型和返回值。单个语句闭包会把它语句的值当做结果返回。

我们可以把 self.buttonTapped(atIndex:) 简 写 为 self.buttonTapped 或 者 单 写buttonTapped;所有三种写法都说的是同⼀个函数。只要没有产⽣歧义,参数标签就可以被省略。

inout 参数将⼀个值传递给函数,函数可以改变这个值,然后将原来的值替换掉,并从函数中传出。

如果结构体没有指定构造器:
  • 如果结构体的属性有默认值, 可以直接使用()构造一个结构体
  • 如果结构体的属性没有默认值, 必须使用逐一构造器实例化结构体

Swift 中的弱引⽤是趋零的:当⼀个弱引⽤变量所引⽤的对象被释放时,这个变量将被⾃动设为 nil,这也是弱引用⽤必须被声明为可选值的原因。

在编程术语⾥,⼀个函数和它所捕获的变量环境组合起来被称为闭包。

闭包的几个点:
  1. 如果你将闭包作为参数传递,并且你不再⽤这个闭包做其他事情的话,就没有必要现将它存储到⼀个局部变量中。可以想象⼀下⽐如 5*i 这样的数值表达式,你可以把它直接传递给⼀个接受 Int 的函数,⽽不必先将它计算并存储到变量⾥。1. 如果编译器可以从上下⽂中推断出类型的话,你就不需要指明它了。在我们的例⼦中,从数组元素的类型可以推断出传递给 map 的函数接受 Int 作为参数,从闭包的乘法结果的类型可以推断出闭包返回的也是 Int。
  2. 如果闭包表达式的主体部分只包括⼀个单⼀的表达式的话,它将⾃动返回这个表达式的结果,你可以不写 return。
  3. Swift 会⾃动为函数的参数提供简写形式,$0 代表第⼀个参数,$1 代表第⼆个参数,以此类推。
  4. 如果函数的最后⼀个参数是闭包表达式的话,你可以将这个闭包表达式移到函数调⽤的圆括号的外部。这样的尾随闭包语法在多⾏的闭包表达式中表现⾮常好,因为它看起来更接近于装配了⼀个普通的函数定义,或者是像 if (expr) { } 这样的执⾏块的表达形式。
  5. 最后,如果⼀个函数除了闭包表达式外没有别的参号也可以⼀并省略。

例如:

[1, 2, 3].map( { (i: Int) -> Int in return i * 2 } )
[1, 2, 3].map( { i in return i * 2 } )
[1, 2, 3].map( { i in i * 2 } )
[1, 2, 3].map( { $0 * 2 } )
[1, 2, 3].map() { $0 * 2 }
[1, 2, 3].map { $0 * 2 }

Swift中的结构体和类跟其它面向对象语言一样都有构造函数,而OC是没有的;
Swift要求实例化一个结构体或类的时候,所有的成员变量都必须有初始值;
构造函数的意义就是用于初始化所有成员变量的,而不是分配内存,分配内存是系统帮我们做的。

不同的字符可能会占用不同数量的内存空间,所以要知道Character的确定位置,就必须从String开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。

如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等的,那就认为它们是相等的。在这个情况 下,即使可扩展的字形群集是有不同的 Unicode 标量构成的,只要它们有同样的语言意义和外观,就认为它们标 准相等。

var oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted() // 取并集
oddDigits.subtract(singleDigitPrimeNumbers) // 取补集
evenDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // 交集取反

必须将延迟存储属性声明成变量(使用var关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

当值类型(枚举、结构体)的实例被声明为常量的时候,它的所有属性也就成了常量。属于引用类型的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。

必须使用var关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。

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

func stepForward(_ input: Int) -> Int {
    return input + 1
}

func stepBackward(_ input: Int) -> Int {
    return input - 1
}


func cal(isAdd: Bool) -> (Int) -> Int {
    if isAdd {
        return stepForward
    }
    return stepBackward
}

// 等价于

func caculate(isAdd: Bool) -> (Int) -> Int {
    if isAdd {
        func stepFor(input: Int) -> Int {
            return input + 1
        }
        return stepFor
    } else {
        func stepBack(input: Int) -> Int {
            return input - 1
        }
        return stepBack
    }
}

// 调用1
cal(isAdd: true)(1)
// 调用2
caculate(isAdd: true)(1)

类型属性语法

在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(global)静态变量定义的。但 是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支 持的范围内。
使用关键字 static 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父 类的实现进行重写。

例如:

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}

enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}

class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
获取和设置类型属性的值

跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实
例。

例如:

print(SomeStructure.storedTypeProperty) // 打印 "Some value." 
SomeStructure.storedTypeProperty = "Another value." print(SomeStructure.storedTypeProperty) // 打印 "Another value.” 
print(SomeEnumeration.computedTypeProperty) // 打印 "6" 
print(SomeClass.computedTypeProperty) // 打印 "27"

在实例方法中修改值类型

结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择可变(mutating) 行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始 结构中。方法还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。

也就是说:如果我们想要改变 self,或是改变 self ⾃⾝或者嵌套的 (⽐如 self.origin.x) 任何属性,我们就需要把方法标记为 mutating。

例如:如果没有mutating,将报错Left side of mutating operator isn't mutable: 'self' is immutable

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

枚举的可变方法可以把 self 设置为同一枚举类型中不同的成员:

例如:

enum TriStateSwitch {
    case Off, Low, High
    
    mutating func next() {
        switch self {
        case .Off:
            self = .Low
        case .Low:
            self = .High
        case .High:
            self = .Off
        }
    }
}

var ovenLight = TriStateSwitch.Low
ovenLight.next() // ovenLight 现在等于 .High
ovenLight.next() // ovenLight 现在等于 .Off

类型方法

实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做类型方 法。在方法的 func 关键字之前加上关键字 static ,来指定类型方法。类还可以用关键字 class 来允许子类重写 父类的方法实现。

注意

在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。


重写属性

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

重写属性的 Getters 和 Setters
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的 属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在 重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类 型的属性相匹配的。
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即 可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。

注意

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

当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。属于引用类型的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。

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

构造过程中常量属性的修改

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

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

默认构造器

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

如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。

对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用self.init。如果你为某个值类型定义了一个自定义的构造器,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器注意假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(extension)中,而不是写在值类型的原始定义中。

指定构造器和便利构造器

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

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

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

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

类的构造器代理规则

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

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

一个更方便记忆的方法是:

  • 指定构造器必须总是向上代理

  • 便利构造器必须总是横向代理

构造器的继承和重写

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

当你重写一个父类的指定构造器时,你总是需要写 override 修饰符,即使你的子类将父类的指定构造器重写为了便利构造器。

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


构造器的自动继承

如上所述,子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承
的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:

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

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

特别地,子类便利构造器重写了父类的指定构造器 init(name: String) ,因此必须在前面使用 override 修饰符。
尽管子类将父类的指定构造器重写为了便利构造器,它依然提供了父类的所有指定构造器的实现。因此,子类会自动继承父类的所有便利构造器。

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

例如:
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)
    }
    
    convenience override init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

class FinalClass: RecipeIngredient {
    
    var hasBuy: Bool = false
    
    var desc: String {
        return "name: \(name), quantity: \(quantity)"
    }
}

可失败构造器
注意

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

例如:

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

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

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

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

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

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


解决实例之间的循环强引用

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。这样实例能够互相引用而不产生循环强引用。

注意

当 ARC 设置弱引用为 nil 时,属性观察不会被触发。

弱引用(weak)和无主引用(unowned)的选择

当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。

无主引用

和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 unowned 表示这是一个无主引用。
无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 nil ,因为非可选类型的变量不允许被赋值为nil 。

重要

使用无主引用,你必须确保引用始终指向一个未销毁的实例。
如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。

弱引用和无主引用需要打破循环强引用的场景:

Person和Apartment的例子展示了两个属性的值都允许为 nil,并会潜在的产生循环强引用。这种场景最适合用 弱引用来解决。
Customer 和 CreditCard 的例子展示了一个属性的值允许为 nil ,而另一个属性的值不允许为 nil ,这也可能会 产生循环强引用。这种场景最适合通过无主引用来解决。

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。注意如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。


没有返回值的方法具有隐式的返回类型Void,如无返回值函数 (页 0)中所述。这意味着没有返回值的方法也会返回(),或者说空的元组。如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是Void?,而不是Void,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用if语句来判断能否成功调用printNumberOfRooms()方法,即使方法本身没有定义返回值。通过判断返回值是否为nil可以判断调用是否成功:

在方法的圆括号后面加上问号是因为你要在buildingIdentifier()方法的可选返回值上进行可选链式调用,而不是方法本身。


为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上throws关键字。一个标有throws关键字的函数被称作throwing 函数。如果这个函数指明了返回值类型,throws关键词需要写在箭头(->)的前面。

注意只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。


指定清理操作

可以使用 defer 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如 return 或者 break 的语句。例如,你可以用 defer 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。


检查类型

用类型检查操作符( is )来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true ,否则返回 false 。

向下转型某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(as? 或 as!)。

补充:as从派生类转换为基类,向上转型(upcasts)

因为向下转型可能会失败,类型转型操作符带有两种不同形式。

as? 还是 as!

条件形式as? 返回一个你试图向下转成的类型的可选值。当你不确定向下转型可以成功时,用类型转换的条件形式( as? )。条件形式的类型转换总是返回一个可选值,并且若下转是不可能的,可选值将是 nil 。这使你能够检查向下转型是否成功。
强制形式 as! 把试图向下转型和强制解包转换结果结合为一个操作。 只有你可以确定向下转型一定会成功时,才使用强制形式( as! )。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

注意

转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。

Any 和 AnyObject 的类型转换

Swift 为不确定类型提供了两种特殊的类型别名:
• Any 可以表示任何类型,包括函数类型。
• AnyObject 可以表示任何类类型的实例。
只有当你确实需要它们的行为和功能时才使用 Any 和 AnyObject 。在你的代码里使用你期望的明确类型总是更好的。

例如:

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
注意

Any 类型可以表示所有类型的值,包括可选类型。Swift 会在你用 Any 类型来表示一个可选值的时候,给你一
个警告。如果你确实想使用 Any 类型来承载可选值,你可以使用 as 操作符显示转换为 Any。

例如:

let optionalNumber: Int? = 3 
things.append(optionalNumber) // 警告 
things.append(optionalNumber as Any) // 没有警告

属性要求

协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。协议总是用var关键字来声明变量属性,在类型声明后加上{ set get }来表示属性是可读可写的,可读属性则用{ get }来表示。


Mutating

方法要求有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。


协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
协议可以像其他普通类型一样使用,使用场景如下:
• 作为函数、方法或构造器中的参数类型或返回值类型
• 作为常量、变量或属性的类型
• 作为数组、字典或其他容器中的元素类型

注意

协议是一种类型,因此协议类型的名称应与其他类型(例如 Int , Double , String )的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamed 和 RandomNumberGenerator)。

通过扩展添加协议一致性

即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。

注意

通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。


通过扩展遵循协议

当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议。

例如:
TextRepresentable协议

protocol TextRepresentable {
    var textualDescription: String { get }
}
struct Hamster {
    var name: String
       var textualDescription: String {
        return "A hamster named \(name)"
  } 
}
extension Hamster: TextRepresentable {}
注意

即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。


协议合成

有时候需要同时遵循多个协议,你可以将多个协议采用 SomeProtocol & AnotherProtocol 这样的格式进行组合,称为 协议合成(protocol composition)。你可以罗列任意多个你想要遵循的协议,以与符号( & )分隔。 下面的例子中,将 Named 和 Aged 两个协议按照上述语法组合成一个协议,作为函数参数的类型:

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”

注意

协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。

访问级别

如果你定义了一个public访问级别的协议,那么该协议的所有实现也会是public访问级别。这一点不同 于其他类型,例如,当类型是public访问级别时,其成员的访问级别却只是internal。


关键字
关键字

如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型Int -> Int -> Int可以理解为Int -> (Int -> Int) ,也就是说,该函数类型的参数为 Int 类型,其返回类型是一个参数类型为Int,返回类型为 Int 的函数类型。


字面量 类型 含义
#file String 所在的文件
#line Int 所在的行数
#column Int 所在的列
#function String 所在的函数名

你可能感兴趣的:(Swift:知识点总结)