swift学习笔记5——其它部分(自动引用计数、错误处理、泛型...)

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

当前版本是swift2.2ios

自动引用计数

引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是经过引用的方式存储和传递git

当你每次建立一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例全部相关的存储型属性的值。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,若是你试图访问这个实例,你的应用程序极可能会崩溃。github

为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。swift

为了使上述成为可能,不管你将实例赋值给属性、常量或变量,它们都会建立此实例的强引用。之因此称之为“强”引用,是由于它会将实例紧紧地保持住,只要强引用还在,实例是不容许被销毁的。app

弱引用必须被声明为变量,代表其值能在运行时被修改。弱引用不能被声明为常量。框架

在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,由于没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。可是在 ARC 中,一旦值的最后一个强引用被移除,就会被当即销毁,这致使弱引用并不适合上面的用途。即,ARC中weak对象不会被缓冲,会当即释放ide

无主引用

和弱引用相似,无主引用不会紧紧保持住引用的实例。和弱引用不一样的是,无主引用是永远有值的。所以,无主引用老是被定义为非可选类型(non-optional type)。你能够在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。函数

若是你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。
还须要注意的是若是你试图访问实例已经被销毁的无主引用,Swift 确保程序会直接崩溃,而不会发生没法预期的行为。因此你应当避免这样的事情发生。post

可选链式调用

调用结果返回一个可选值

可选链式调用(Optional Chaining)是一种能够在当前值可能为nil的可选值上请求和调用属性、方法及下标的方法。若是可选值有值,那么调用就会成功;若是可选值是nil,那么调用将返回nil。多个调用能够链接在一块儿造成一个调用链,若是其中任何一个节点为nil,整个调用链都会失败,即返回nil

Swift 的可选链式调用和 Objective-C 中向nil发送消息有些相像,可是 Swift 的可选链式调用能够应用于任意类型,而且能检查调用是否成功

给可选链式调用赋值时,若是左边为nil,则右边的不会执行,好比john.residence?.address = createAddress(),若是residence为nil则createAddress函数不会被执行

经过可选链式调用调用方法

一个没有返回值的方法具备隐式的返回类型Void。这意味着没有返回值的方法也会返回(),或者说空的元组。若是在可选值上经过可选链式调用来调用这个方法,该方法的返回类型会是Void?,而不是Void,由于经过可选链式调用获得的返回值都是可选的。

if john.residence?.printNumberOfRooms() != nil {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")
}

一样的,能够据此判断经过可选链式调用为属性赋值是否成功

if (john.residence?.address = someAddress) != nil {
    print("It was possible to set the address.")
} else {
    print("It was not possible to set the address.")
}
  • 若是你访问的值不是可选的,可选链式调用将会返回可选值。
  • 若是你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。

所以:

  • 经过可选链式调用访问一个Int值,将会返回Int?,不管使用了多少层可选链式调用。
  • 相似的,经过可选链式调用访问Int?值,依旧会返回Int?值,并不会返回Int??。

错误处理

用 throwing 函数传递错误

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

func canThrowErrors() throws -> String

指定清理操做

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

defer语句将代码的执行延迟到当前的做用域退出以前。该语句由defer关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break或是return语句,或是抛出一个错误。延迟执行的操做会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条defer语句中的代码会在第二条defer语句中的代码被执行以后才执行,以此类推。

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // 处理文件。
        }
        // close(file) 会在这里被调用,即做用域的最后。
    }
}

上面的代码使用一条defer语句来确保open(:)函数有一个相应的对close(:)函数的调用。

类型转换

检查类型(Checking Type)

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

向下转型(Downcasting)

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

当你不肯定向下转型能够成功时,用类型转换的条件形式(as?)。条件形式的类型转换老是返回一个可选值(optional value),而且若下转是不可能的,可选值将是 nil。这使你可以检查向下转型是否成功。

只有你能够肯定向下转型必定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

Any 和 AnyObject 的类型转换

Swift 为不肯定类型提供了两种特殊的类型别名:

AnyObject 能够表示任何类类型的实例。
Any 能够表示任何类型,包括函数类型。

泛型(Generics)

泛型函数

泛型函数能够适用于任何类型,下面的 swapTwoValues(_:_:) 函数是上面三个函数的泛型版本:

func swapTwoValues<T>(inout a: T, inout _ b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

另一个不一样之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来( )。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:)函数定义内的一个占位类型名,所以 Swift 不会去查找名为 T 的实际类型。

命名类型参数

在大多数状况下,类型参数具备一个描述性名字,例如 Dictionary<Key, Value> 中的 Key 和 Value,以及 Array 中的 Element,这能够告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,一般使用单个字母来命名,例如 T、U、V,正如上面演示的 swapTwoValues(_:_:) 函数中的 T 同样。

  • 泛型实现栈
struct Stack<Element> {
    var items = [Element]()
    mutating func push(item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

你能够经过在尖括号中写出栈中须要存储的数据类型来建立并初始化一个 Stack 实例。例如,要建立一个 String 类型的栈,能够写成 Stack ():

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中如今有 4 个字符串

扩展一个泛型类型

当你扩展一个泛型类型的时候,你并不须要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中能够直接使用,而且这些来自原始类型中的参数名称会被用做原始定义中类型参数的引用。

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

类型约束语法

你能够在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法以下所示(做用于泛型类型时的语法与之相同):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

上面这个函数有两个类型参数。第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。

访问控制(Access Control)

访问级别
Swift 为代码中的实体提供了三种不一样的访问级别。这些访问级别不只与源文件中定义的实体相关,同时也与源文件所属的模块相关。

  • public:能够访问同一模块源文件中的任何实体,在模块外也能够经过导入该模块来访问源文件里的全部实体。一般状况下,框架中的某个接口能够被任何人使用时,你能够将其设置为 public 级别。
  • internal:能够访问同一模块源文件中的任何实体,可是不能从模块外访问该模块源文件中的实体。一般状况下,某个接口只在应用程序或框架内部使用时,你能够将其设置为 internal 级别。
  • private:限制实体只能在所在的源文件内部使用。使用 private 级别能够隐藏某些功能的实现细节。

public 为最高(限制最少)访问级别,private 为最低(限制最多)访问级别。默认为internal

一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。若是你将类型指定为 private 级别,那么该类型的全部成员的默认访问级别也会变成 private。若是你将类型指定为 public 或者 internal 级别(或者不明确指定访问级别,而使用默认的 internal 访问级别),那么该类型的全部成员的默认访问级别将是 internal。

上面提到,一个 public 类型的全部成员的访问级别默认为 internal 级别,而不是 public 级别。若是你想将某个成员指定为 public 级别,那么你必须显式指定。这样作的好处是,在你定义公共接口的时候,能够明确地选择哪些接口是须要公开的,哪些是内部使用的,避免不当心将内部使用的接口公开。

访问级别基本原则

Swift 中的访问级别遵循一个基本原则:不能够在某个实体中定义访问级别更高的实体。

例如:

  • 一个 public 访问级别的变量,其类型的访问级别不能是 internal 或 private。由于没法保证变量的类型在使用变量的地方也具备访问权限。
  • 函数的访问级别不能高于它的参数类型和返回类型的访问级别。由于若是函数定义为 public 而参数类型或者返回类型定义为 internal 或 private,就会出现函数能够在任何地方被访问,可是它的参数类型和返回类型却不能够。

框架的访问级别

当你开发框架时,就须要把一些对外的接口定义为 public 级别,以便使用者导入该框架后能够正常使用其功能。这些被你定义为 public 的接口,就是这个框架的 API。

单元测试 target 的访问级别

当你的应用程序包含单元测试 target 时,为了测试,测试模块须要访问应用程序模块中的代码。默认状况下只有 public 级别的实体才能够被其余模块访问。然而,若是在导入应用程序模块的语句前使用 @testable 特性,而后在容许测试的编译设置(Build Options -> Enable Testability)下编译这个应用程序模块,单元测试 target 就能够访问应用程序模块中全部 internal 级别的实体。

元组类型

元组的访问级别将由元组中访问级别最严格的类型来决定。例如,若是你构建了一个包含两种不一样类型的元组,其中一个类型为 internal 级别,另外一个类型为 private 级别,那么这个元组的访问级别为 private。

元组不一样于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而没法明确指定。

函数类型

函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。若是这种推断出来的访问级别不符合函数定义所在环境的默认访问级别,那么就须要明确地指定该函数的访问级别。并且函数访问级别不能高于访问的参数

枚举类型

枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不一样的访问级别。

  • 原始值和关联值

枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 internal 访问级别的枚举中定义 private 级别的原始值类型。

嵌套类型

若是在 private 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 private 访问级别。若是在 public 或者 internal 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 internal 访问级别。若是想让嵌套类型拥有 public 访问级别,那么须要明确指定该嵌套类型的访问级别。

子类

子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 internal,子类的访问级别就不能是 public。

Getter 和 Setter

常量、变量、属性、下标的 Getters 和 Setters 的访问级别和它们所属类型的访问级别相同。

构造器

自定义构造器的访问级别能够低于或等于其所属类型的访问级别。惟一的例外是必要构造器,它的访问级别必须和所属类型的访问级别相同。

如同函数或方法的参数,构造器参数的访问级别也不能低于构造器自己的访问级别。(要么改函数,要么改参数的访问级别)

默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 public。若是一个类型被指定为 public 级别,那么默认构造器的访问级别将为 internal

协议

协议中的每个要求都具备和该协议相同的访问级别。你不能将协议中的要求设置为其余访问级别。这样才能确保该协议的全部要求对于任意采纳者都将可用。

若是你定义了一个 public 访问级别的协议,那么该协议的全部实现也会是 public 访问级别

协议一致性

一个类型能够采纳比自身访问级别低的协议。例如,你能够定义一个 public 级别的类型,它能够在其余模块中使用,同时它也能够采纳一个 internal 级别的协议,可是只能在该协议所在的模块中做为符合该协议的类型使用。

采纳了协议的类型的访问级别取它自己和所采纳协议二者间最低的访问级别。也就是说若是一个类型是 public 级别,采纳的协议是 internal 级别,那么采纳了这个协议后,该类型做为符合协议的类型时,其访问级别也是 internal。

若是你采纳了协议,那么实现了协议的全部要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 public 级别的类型,采纳了 internal 级别的协议,那么协议的实现至少也得是 internal 级别。

Swift 和 Objective-C 同样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不一样的方式实现同一个协议。

扩展

你能够在访问级别容许的状况下对类、结构体、枚举进行扩展。扩展成员具备和原始类型成员一致的访问级别。例如,你扩展了一个 public 或者 internal 类型,扩展中的成员具备默认的 internal 访问级别,和原始类型中的成员一致 。若是你扩展了一个 private 类型,扩展成员则拥有默认的 private 访问级别。

或者,你能够明确指定扩展的访问级别(例如,private extension),从而给该扩展中的全部成员指定一个新的默认访问级别。这个新的默认访问级别仍然能够被单独指定的访问级别所覆盖。

经过扩展添加协议一致性

若是你经过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中全部协议要求的实现提供默认的访问级别。

泛型

泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数自己的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来肯定。

类型别名

你定义的任何类型别名都会被看成不一样的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,private 级别的类型别名能够做为 public、internal、private 类型的别名,可是 public 级别的类型别名只能做为 public 类型的别名,不能做为 internal 或 private 类型的别名。

高级运算符(Advanced Operators)

与 C 语言中的算术运算符不一样,Swift 中的算术运算符默认是不会溢出的。全部溢出行为都会被捕获并报告为错误。若是想让系统容许溢出行为,能够选择使用 Swift 中另外一套默认支持溢出的运算符,好比溢出加法运算符(&+)。全部的这些溢出运算符都是以 & 开头的。

对无符号整数进行移位的规则以下:

  • 已经存在的位按指定的位数进行左移和右移。
  • 任何因移动而超出整型存储范围的位都会被丢弃。
  • 用 0 来填充移位后产生的空白位。

有符号

  • 当对整数进行按位右移运算时,遵循与无符号整数相同的规则,可是对于移位产生的空白位使用符号位进行填充,而不是用 0。

运算符函数(运算符重载)

单目运算符只运算一个值。当运算符出如今值以前时,它就是前缀的(例如 -a),而当它出如今值以后时,它就是后缀的(例如 b!) a+b,+为中缀运算符

不能对默认的赋值运算符(=)进行重载。只有组合赋值运算符能够被重载。一样地,也没法对三目条件运算符 (a ? b : c) 进行重载。

等价运算符

自定义的类和结构体没有对等价运算符进行默认实现,等价运算符一般被称为“相等”运算符(==)与“不等”运算符(!=)。对于自定义类型,Swift 没法判断其是否“相等”,由于“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。

自定义运算符
除了实现标准运算符,在 Swift 中还能够声明和实现自定义运算符。能够用来自定义运算符的字符列表请参考运算符。

新的运算符要使用 operator 关键字在全局做用域内进行定义,同时还要指定 prefix、infix 或者 postfix 修饰符:

prefix operator +++ {}
相关文章
相关标签/搜索