-
常量与变量
- 使用let来声明常量,使用var来声明变量。
- 声明的同时赋值的话,编译器会自动推断类型。
- 值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
- 如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
-
Swift 包含了 C 和 Objective-C 上所有基础数据类型,
-
Int表示整型值;Double和Float表示浮点型值;
- 数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
let justOverOneMillion = 1_000_000.000_000_1
- 数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
Bool是布尔型值; 有两个布尔常量,true和false
Character和String是单字符和字符串文本型数据。
三个基本的集合类型,Array,Set和Dictionary
增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple): 把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
-
增加了可选(Optional)类型,用于处理值缺失的情况。
- 衍生的还有一个隐式解析可选类型
增加了函数类型
-
Swift 的多行注释可以嵌套在其它的多行注释之中。
可以使用typealias关键字来定义类型别名:typealias AudioSample = UInt16
可选值的强制解析(forced unwrapping):当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”
-
可选绑定:
- 可以用在if和while语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。if let/var firstNumber = Int(“4”){…}
- 可以包含多个可选绑定在if语句中,并使用where子句做布尔值判断。if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber { }
-
隐式解析可选类型:
- 类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?)改成感叹号(String!)来声明一个隐式解析可选类型。当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中
- 一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。
- 如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
- 仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值,也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值
- 如果一个变量之后可能变成nil的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是nil的话,请使用普通可选类型。
一个函数可以通过在声明中添加throws关键词来声明可能抛出错误消息,throw关键字用来抛出错误。当你使用的函数可能抛出错误消息时, 你应该在表达式中前置try关键词。
-
assert断言:(发布版本中自动失效)
- let age = -3; assert(age >= 0, "A person's age cannot be less than zero”);
- // 因为 age < 0,所以断言会触发
- //条件为true时才会向下执行,否则中断程序,输出后边的字串信息,但后边的信息是可选的
-
基本运算符
赋值符(=)不返回值,以防止把想要判断相等运算符(==)的地方写成赋值符导致的错误。
区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(%),
提供了 C 语言没有的表达两数之间的值的区间运算符(a..<b和a...b),这方便我们表达一个区间内的数值。
与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(&+ , &-, &*在2.0语法中&/和&%被移除)。
Swift 也提供恒等===和不恒等!==这两个比较符来判断两个对象是否引用同一个对象实例。
-
空合运算符(a ?? b)将对可选类型a进行空判断,如果a包含一个值就进行解封,否则就返回一个默认值b.这个运算符有两个条件
- 表达式a必须是Optional类型 ·
- 默认值b的类型必须要和a存储值的类型保持一致
- 如果a为非空值(non-nil),那么值b将不会被估值。这也就是所谓的短路求值。
-
字符串和字符
- String是例如"hello, world","albatross"这样的有序的Character(字符)类型的值的集合。
- 与OC的NSString不同的时String类型是值类型。
- 在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
- 使用startIndex属性可以获取一个String的第一个Character的索引。使用endIndex属性可以获取最后一个Character的后一个位置的索引。因此,endIndex属性不能作为一个字符串的有效下标。如果String是空串,startIndex和endIndex是相等的。
- 通过调用String.Index的predecessor()方法,可以立即得到前面一个索引,调用successor()方法可以立即得到后面一个索引。任何一个String的索引都可以通过锁链作用的这些方法来获取另一个索引,也可以调用advancedBy(_:x)方法来获取代表其后第x的index。但如果尝试获取出界的字符串索引,就会抛出一个运行时错误。
- 使用characters属性的indices属性会创建一个包含全部索引的范围(Range),用来在一个字符串中访问单个字符。
-
集合类型
- Swift 语言中的Arrays、Sets和Dictionaries中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。
- 如果我们同时需要每个数据项的值和索引值,可以使用enumerate()方法来进行数组遍历。enumerate()返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:for (index, value) in shoppingList.enumerate( ) { }
-
控制流
-
switch
case分支必须包含所有可能性
不存在隐式的贯穿(No Implicit Fallthrough),可以使用Fallthrough实现显示贯穿
一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写)
case 分支的模式也可以是一个值的区间.
不像 C 语言,Swift 允许多个 case 匹配同一个值。可以使用元组在同一个switch语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(_)来匹配所有可能的值。
可以进行值绑定(value binding):case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了.case 分支的模式可以使用where语句来判断额外的条件。
-
标签语句:
- 产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。然后可以使用break或continue加上标签来显式指明作用循环或switch.
-
guard语句:
- 像if语句一样,guard的执行取决于一个表达式的布尔值。我们可以使用guard语句来要求条件必须为真时,以执行guard语句后的代码。不同于if语句,一个guard语句总是有一个else分句,如果条件不为真则在else分支上的代码就会被执行。这个分支必须转移控制以退出guard语句出现的代码段。它可以用控制转移语句如return,break或continue做这件事,或者它调用了一个不返回的方法或函数,例如fatalError()
-
检测API是否可用
- Swift 有内置支持去检查接口的可用性的,这可以确保我们不会不小心地使用对于当前部署目标不可用的API。
-
available(iOS 9, OSX 10.10, *) 返回bool值
-
-
函数(Functions)
-
在 Swift 中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。
- 可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。
函数的定义可以写在在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
如果函数返回的元组类型中有可能在整个元组中含有“没有值”,你可以使用可选的(Optional) 元组(元组类型的右括号后放置一个问号来定义一个可选元组)返回类型反映整个元组可以是nil的事实。
-
参数
-
函数参数都有一个外部参数名(external parameter name)和一个本地参数名(local parameter name)。外部参数名用来标记传递给函数调用的参数,本地参数名在实现函数的时候使用。
- 你可以在本地参数名前指定外部参数名,中间以空格分隔。注意: 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。
- 如果你不想为第二个及后续的参数设置参数名,用一个下划线(_)代替一个明确地参数名。注意: 因为第一个参数默认忽略其外部参数名称,明确写下划线是多余的。
可以使用默认参数.注意: 将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。
-
可变参数
- 可变参数的传入值在函数体为此类型的一个数组。例如,一个叫做 numbers 的 Double... 型可变参数,在函数体内可以当做一个叫 numbers 的 [Double] 型的数组常量。
- 注意: 一个函数最多只能有一个可变参数,而且可变参数放在参数表的最后.
- 函数参数默认是常量,可以通过在参数名前加关键字 var 来定义变量参数.
- 注意: 对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。
-
输入输出inout参数
- 定义一个输入输出参数时,在参数定义前加 inout 关键字。一个输入输出参数在函数内部被修改,然后被传出函数,替换原来的值。
- 只能将变量作为输入输出参数.传入的参数作为输入输出参数时,需要在参数前加&符,表示这个值可以被函数修改。
- 注意: 输入输出参数不能有默认值,而且可变参数不能用 inout 标记。如果你用 inout 标记一个参数,这个参数不能被 var 或者 let 标记。
- 注意: 输入输出参数和返回值是不一样的。输入输出参数是函数对函数体外产生影响的另一种方式。
-
-
-
函闭包( Closures)
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。
闭包和函数都是引用类型.
-
闭包采取如下三种形式之一
- 全局函数是一个有名字但不会捕获任何值的闭包 ·
- 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包 ·
- 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
-
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
- 利用上下文推断参数和返回值类型 ·
- 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字 ·
- 参数名称缩写 ·
- 尾随(Trailing)闭包语法
-
提供闭包函数
- 一种方式是撰写一个符合其类型要求的普通函数,并将其作为方法的参数传入
- 另一种是闭包表达式: 可以使用常量、变量和inout类型作为参数,不提供默认值。 也可以在参数列表的最后使用可变参数。 元组也可以作为参数和返回值。{ (parameters) -> returnType in statements }
所有的类型都可以被正确推断,则返回箭头 (->) 和围绕在参数周围的括号也可以被省略
单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果
Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。
如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in关键字也同样可以被省略.
-
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
- 注意: 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。
-
捕获值
- 闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
- Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
- 原函数每次返回嵌套函数作为返回值给变量,则两个不同的变量拥有不同的闭包空间,两者互不影响.
-
枚举(Enumerations)
在 C 语言中枚举将枚举名和一个整型值相对应。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。
-
在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如
- 计算型属性(computed properties),用于提供关于枚举当前值的附加信息,
- 实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。
- 枚举也可以定义构造函数(initializers)来提供一个初始值;
- 可以在原始的实现基础上扩展它们的功能;
- 可以遵守协议(protocols)来提供标准的功能。
和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。
变量枚举类型已知时,再次为其赋值可以省略枚举名。
在判断一个枚举类型的值时,switch语句必须穷举所有情况,否则将无法通过编译.强制性全部穷举的要求确保了枚举成员不会被意外遗漏。
-
相关值
可以使用相关值:让你存储成员值之外的自定义信息,并且当你每次在代码中使用该成员时允许这个信息产生变化。
-
可以在switch的 case 分支代码中提取每个相关值作为一个常量(用let前缀)或者作为一个变量(用var前缀)来使用
- 如果一个枚举成员的所有相关值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个var或者let标注在紧跟case后,成员名称前
-
原始值
作为相关值的另一种选择,枚举成员可以被默认值(称为原始值)赋值,其中这些原始值具有相同的类型。
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
-
原始值的隐式赋值:在使用原始值为整数或者字符串类型的枚举时,不需要显式的为每一个成员赋值,这时,Swift将会自动为你赋值。
- 当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
- 当使用字符串作为枚举类型的初值时,每个枚举成员的隐式初值则为该成员的名称。
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法将原始值类型作为参数,返回枚举成员或者nil。你可以使用这种初始化方法来创建一个新的枚举变量。 let possiblePlanet = Planet(rawValue: 7)
使用枚举成员的rawValue属性可以访问该枚举成员的原始值
-
原始值与枚举值
- 原始值和相关值是不相同的。
- 当你开始在你的代码中定义枚举的时候原始值是被预先填充的值。对于一个特定的枚举成员,它的原始值始终是相同的。
- 相关值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做得时候,它的值可以是不同的。
-
递归枚举
- 递归枚举(recursive enumeration)是一种枚举类型,表示它的枚举中,有一个或多个枚举成员拥有该枚举的其他成员作为相关值。使用递归枚举时,编译器会插入一个中间层。
- 你可以在枚举成员前加上indirect来表示这成员可递归。也可以在枚举类型开头加上indirect关键字来表示它的所有成员都是可递归的.
-
类和结构体(Classes and Structures)
在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口。
-
命名
- 使用 UpperCamelCase 这种方式来命名(如 SomeClass 和SomeStructure等),以便符合标准Swift 类型的大写命名风格(如String,Int和Bool)。
- 相反的,请使用lowerCamelCase这种方式为属性和方法命名(如framerate和incrementCount),以便和类区分。
通过使用点语法(dot syntax),你可以访问实例中所含有的属性。
所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。与结构体不同,类实例没有默认的成员逐一构造器。
Swift 中字符串(String),数组(Array)和字典(Dictionary)类型均以结构体的形式实现.是值类型.
-
Objective-C中字符串(NSString),数组(NSArray)和字典(NSDictionary)类型均以类的形式实现,是引用类型.
- 实际上,在 Swift 中,所有的基本类型都是值类型,并且都是以结构体的形式在后台所实现。
-
Swift 中类和结构体有很多共同点与不同点:
-
共同点
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义附属脚本用于访问值
- 定义构造器用于生成初始化值
- 通过扩展以增加默认实现的功能
- 实现协议以提供某种标准功能
-
不同点
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 解构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
- 类是引用类型,结构体是值类型
-
-
属性 (Properties)
-
属性将值跟特定的类、结构或枚举关联。
存储属性存储常量或变量作为实例的一部分,,存储属性只能用于类和结构体。可以在定义存储属性的时候指定默认值
-
计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举.
- 注意:必须使用var关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
当值类型的实例被声明为常量的时候,它的所有属性也就成了常量,即使在定义的时候是变量.
-
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。
- 在属性声明前使用lazy来标示一个延迟存储属性。
- 注意:必须将延迟存储属性声明成变量(使用var关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
- 注意:如果一个被标记为lazy的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
-
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
- 可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器
- 注意:不需要为非重载的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。
- 注意:如果在一个属性的didSet观察器里为它赋值,这个值会替换该观察器之前设置的值。
-
全局与局部变量
全局或局部变量都属于存储型变量,跟存储属性类似,它提供特定类型的存储空间,并允许读取和写入。
-
在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算的值而不是存储值,声明格式也完全一样。
- 注意:全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记lazy特性。
- 注意:局部范围的常量或变量不会延迟计算。
-
类型属性用于定义特定类型所有实例共享的数据,
使用关键字static来定义类型属性。在为类(class)定义计算型类型属性时,可以使用关键字class来支持子类对父类的实现进行重写。
-
值类型的存储型类型属性可以是变量或常量
- 注意:跟实例的存储属性不同,必须给存储型类型属性指定默认值,因为类型本身无法在初始化过程中使用构造器给类型属性赋值。
- 注意:存储型类型属性是延迟初始化的(lazily initialized),它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行初始化一次,并且不需要对其使用 lazy 修饰符。
- 计算型类型属性跟实例的计算属性一样只能定义成变量属性。
-
-
方法(Methods)
-
方法是与某些特定类型相关联的函数。
- 实例方法:类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能
- 类型方法:类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。
Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。
-
变异(mutating)方法:
-
结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;要使用变异方法, 将关键字mutating 放到方法的func关键字之前就可以了.
- 注意:不能在结构体类型常量上调用变异方法,因为常量的属性不能被改变,即使想改变的是常量的变量属性也不行.
变异方法能够赋给隐含属性self一个全新的实例
-
-
-
下标脚本(Subscripts)
-
下标脚本
- 可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问集合(collection),列表(list)或序列(sequence的快捷方式,使用下标脚本的索引设置和获取值,不需要再调用实例的特定的赋值和访问方法。
- 与定义实例方法类似,定义下标脚本使用subscript关键字,显式声明入参(一个或多个)和返回类型。与实例方法不同的是下标脚本可以设定为读写或只读。
-
下标脚本选项
- 下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。
- 下标脚本的返回值也可以是任何类型。
- 下标脚本可以使用变量参数和可变参数,但不允许使用写入读出(in-out)参数或给参数设置默认值。
-
下标脚本的重载
- 一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过入参的类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行。
-
-
继承(Inheritance)
-
重写(overriding)
- 子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标脚本(subscript)提供自己定制的实现(implementation)。
- 重写某个特性,你需要在重写定义的前面加上override关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override关键字的重写都会在编译时被诊断为错误。
- override关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
重写方法:可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
-
重写属性
-
重写属性的Getters和Setters
- 可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。
- 子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。
- 你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
- 可以将一个继承来的只读属性重写为一个读写属性,只需要你在重写版本的属性里提供 getter 和 setter 即可。
- 但是,你不可以将一个继承来的读写属性重写为一个只读属性。
-
重写属性观察器(Property Observer)
- 可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。
- 注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供willSet或didSet实现是不恰当。
-
-
防止重写
- 可以通过把类, 方法,属性或下标脚本标记为final来防止它们被重写,只需要在声明关键字前加上final特性即可。
-
-
构造过程(Initialization)
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
-
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
- 注意:当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers)。
- 可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
- 构造函数中可以修改常量值:只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
-
默认构造器与逐一成员构造器:
- Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
- 结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
-
值类型的构造器代理:
-
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
-
值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。
- 值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init。
- 如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。
类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
-
-
-
类的继承和构造过程
-
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
-
指定构造器:
- 指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
-
便利构造器:
- 便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
- 需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开
-
-
-
类的构造器代理规则
-
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
规则 1-指定构造器必须调用其直接父类的的指定构造器。
规则 2-便利构造器必须调用同一类中定义的其它构造器。
规则 3-便利构造器必须最终以调用一个指定构造器结束。
-
一个更方便记忆的方法是:
- 指定构造器必须总是向上代理
- 便利构造器必须总是横向代理
-
-
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成:
- 安全检查 1-指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
- 安全检查 2-指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
- 安全检查 3-便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
- 安全检查 4-构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,self的值不能被引用。
-
跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器,但可以被重写。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
-
子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。
- 规则 1-如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
- 规则 2-如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。
-
-
可失败构造器:
- 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)。
- 你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
-
必要构造器
- 在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器
- 注意:如果子类继承的构造器能满足必要构造器的需求,则你无需显示的在子类中提供必要构造器的实现。
-
通过闭包和函数来设置属性的默认值
- 如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
-
析构过程(Deinitialization)
- 析构器是在实例释放发生前被自动调用。析构器是不允许被主动调用的。
- 子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。
- 即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
-
自动引用计数(Automatic Reference Counting)
-
类实例之间的循环强引用
-
你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
-
对于生命周期中会变为nil的实例使用弱引用。
- 声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。
-
对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
- 和弱引用类似,无主引用不会牢牢保持住引用的实例。
- 和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。
- 你可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。
-
-
-
相互强引用的场景及处理办法
- 两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
- 一个属性的值允许为nil,而另一个属性的值不允许为nil,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
- 两个属性都必须有值,并且初始化完成后永远不会为nil。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。
-
闭包引起的循环强引用
问题的产生:循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例。
-
解决办法:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
在闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
-
在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。
- 注意: Swift 有如下要求:只要在闭包内使用self的成员,就要用self.someProperty或者self.someMethod()(而不只是someProperty或someMethod())。这提醒你可能会一不小心就捕获了self。
-
-
可空链式调用(Optional Chaining)
- 通过可空链式调用得到的返回值都是可空的。
-
错误处理(Error Handling)
在Swift中,错误用符合ErrorType协议的值表示。 Swift枚举特别适合把一系列相关的错误组合在一起,同时可以把一些相关的值和错误关联在一起。因此编译器会为实现ErrorType协议的Swift枚举类型自动实现相应合成。
-
throws,throw,do,try,catch
通过在函数或方法声明的参数后面加上throws关键字,表明这个函数或方法可以抛出错误。如果指定一个返回值,可以把throws关键字放在返回箭头(->)的前面。除非明确地指出,一个函数,方法或者闭包就不能抛出错误。
在抛出函数体的任意一个地方,可以通过throw语句抛出错误。
-
当调用一个抛出函数的时候,在调用前面加上try。
- 可以通过try!来调用抛出函数或方法禁止错误传送
使用do-catch语句来就捕获和处理错误
类似switch语句,编译器会检查catch分句是否能够处理全部错误。
defer语句把执行推迟到退出当前域的时候,。被推迟的操作的执行的顺序和他们定义的顺序相反.
-
类型转换(Type Casting)
-
类型转换在 Swift 中使用 is 和 as 操作符实现。
用类型检查操作符(is)来检查一个实例是否属于特定子类型。
-
因为向下转型可能会失败,类型转型操作符带有两种不同形式。
条件形式(conditional form) as? 返回一个你试图向下转成的类型的可选值(optional value)。
-
强制形式 as! 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。
- 注意:转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。
- 注意:在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 switch case 语句的内容中这种检查总是安全的。
-
Swift为不确定类型提供了两种特殊类型别名:
AnyObject可以代表任何class类型的实例。
-
Any可以表示任何类型,包括方法类型(function types)。
- 注意:只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。
-
-
嵌套类型(Nested Types)
- 要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{ }内,而且可以根据需要定义多级嵌套。
- 在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名
-
扩展(Extensions)
-
扩展就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能(functionality)。
- 注意:扩展可以对一个类型添加新的功能,但是不能重写已有的功能。
- 注意:如果你定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是可用的,即使它们是在你的这个扩展的前面定义的。
-
Swift 中的扩展可以: ·
- 添加计算型属性和计算型静态属性 ·
- 定义实例方法和类型方法 ·
- 提供新的构造器 ·
- 定义下标 ·
- 定义和使用新的嵌套类型 ·
- 使一个已有类型符合某个协议
-
-
协议(Protocols)
-
协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。
-
属性:
- 协议可以规定其遵循者提供特定名称和类型的实例属性(instance property)或类属性(type property),而不指定是存储型属性(stored property)还是计算型属性(calculate property)。
- 此外还必须在类型声明后加上{ set get }来表示属性是只读的还是可读可写的。
- 定义类属性(type property)时,总是使用static关键字作为前缀。当协议的遵循者是类时,可以使用class或static关键字来声明类属性,但是在协议的定义中,仍然要使用static关键字。
-
方法:
这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。
可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。
但是在协议的方法定义中,不支持参数默认值。
协议中定义类方法的时候,总是使用static关键字作为前缀。当协议的遵循者是类的时候,虽然你可以在类的实现中使用class或者static来实现类方法,但是在协议中声明类方法,仍然要使用static关键字。
-
对Mutating方法的规定:
- 值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。
- 注意:用类实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。
-
对构造器的规定:
- 可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。在这两种情况下,你都必须给构造器实现标上"required"修饰符.
- 使用required修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
- 如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类。
- 如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符
-
尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
类专属协议:你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
协议合成:有时候需要同时遵循多个协议。你可以将多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)。你可以在<>中罗列任意多个你想要遵循的协议,以逗号分隔。
你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用where关键字来描述限制情况。
注意:如果有多个协议扩展,而一个协议的遵循者又同时满足它们的限制,那么将会使用所满足限制最多的那个扩展。
-
Swift2.0笔记
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 本章将会介绍 存储属性的初始赋值自定义构造过程默认构造器值类型的构造器代理类的继承和构造过程可失败构造器必要构造器...