好奇心驱使,最近在学习Swift这门语言。第一次接触了解概况时觉得它像Javascript,Python等脚本语言,随着深入学习让我领略到了强类型语言的严谨慎密。
此篇文章记录在阅读Swift官方教程中文版 中的重点摘要笔记。
基础语法 变量和常量 Swift中用let
声明常量,用var
声明变量。
1 2 let maxNumber = 10 ;var currentNumber = 0 ;
常量设置后就不可以修改。
数据类型 Swift中常用数据类型有:Int
、Float
、Double
、String
、Bool
、Tuples
、Arrays
、Sets
、Dictionary
数值 Swift中Int
提供了8(Int8、UInt8)、16(Int16、UInt16)、32(Int32、UInt32)、64(Int64、UInt64)位的有符号和无符号(U)整数类型。
整数字面量可以被写作:
一个十进制数,没有前缀
一个二进制数,前缀是0b
一个八进制数,前缀是0o
一个十六进制数,前缀是0x
1 2 3 4 let decimalInteger = 17 let binaryInteger = 0b10001 let octalInteger = 0o21 let hexadecimalInteger = 0x11
在声明变量时你可以写上数据类型,也可以不写。Swift会根据你赋值推断该变量的类型。
1 2 3 var oneMillion: Int = 1_000_000 var name: String = "Fynn" var age = 28
浮点数 Double
表示64位浮点数,它的精确度更高至少有15位数字。Float
表示32位浮点数,只有6位数字。推荐有限选择Double
整数和浮点数转换必须显式指定类型:
1 2 3 let three = 3 let pointOneFourOneFiveNine = 0.14159 let pi = Double (three)+ pointOneFourOneFiveNine
字符串 字符串是一组有序的Character
类型的值的集合,通过String
类型来表示。
多行字符串 1 2 3 4 5 6 7 let quotation = """ The White Rabbit put on his spectacles. "Where shall I begin, please your Majesty?" he asked. "Begin at the beginning," the King said gravely, "and go on till you come to the end; then stop." """
多行字符串有一对三个双引号包裹着的。引号内没有换行符,如果你想在多行字符串字面量中出现换行符的话,可以在行尾写一个反斜杠()作为续行符。
字符串字面量可以包含一下特殊字符:
转义字符\0(空字符)、\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、"(双引号)、'(单引号)。
Unicode 标量,写成\u{n}(u为小写),其中n为任意一到八位十六进制数且可用的 Unicode 位码。
遍历字符串 可以通过for-in
循环来遍历字符串,获取字符串中的每一个字符的值:
1 2 3 4 5 6 7 8 for character in "Mouse" { print (character) }
可以通过+=
将两个字符串相加,也可以通过append()
方法将一个字符串附加到另一个字符串变量尾部。
1 2 3 4 var str1 = "hello" var str2 = "world" str1 += str1 str1.append("!" )
注意 : 不能将一个字符串或一个字符添加到一个已经存在的字符 变量上。因为字符变量只能包含一个字符。
可以向字符串中传入变量,语法是:\(variable)
1 2 3 let multiplier = 3 let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5 ) "
Unicode Unicode 是一个国际标准,用于文本的编码和表示。它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。 Swift 的String
和Character
类型是完全兼容 Unicode 标准的。
Swift 的String类型是基于 Unicode 标量 建立的。 Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如U+0061表示小写的拉丁字母(LATIN SMALL LETTER A)(“a”),U+1F425表示小鸡表情(FRONT-FACING BABY CHICK) (“🐥”)。
Swift中一个Unicode码位表示一个字符。
每一个String
值都有一个关联的索引(index)类型,如果需要获取一个字符串中指定位置的字符需要先获取到该字符的index索引值。
获取index索引值方法有:
1 2 3 4 5 6 7 8 let str = "Hello World!" let first = str.startIndex str[first] # "H" str.endIndex # 字符串最后一个字符 str.index(before: str.endIndex) str.index(after: str.startIndex) str.index(str.startIndex,offserBy:2 )
注意: str.endIndex
获取是最后一个字符位置加一的值。越界获取字符会引发运行时错误。
string
包含一个indices
属性可以获取该字符串全部索引的范围。
1 2 3 4 5 let animal = "Mouse" for index in animal.indices { print ("\(animal[index]) " , terminator: "" ) }
字符串中一个字符的插入和删除通过:insert(_,at:)
和remove(at:)
完成。
1 2 3 4 5 6 var welcome = "hello" welcome.insert("!" , at: welcome.endIndex) welcome.insert(contentsOf: " there" , at: welcome.index(before: welcome.endIndex))
插入和删除一个字符串通过:insert(contentsOf:at:)
和removeSubrange(_:)
完成。
1 2 3 4 5 6 welcome.remove(at: welcome.index(before: welcome.endIndex)) let range = welcome.index(welcome.endIndex, offsetBy: - 6 )..< welcome.endIndexwelcome.removeSubrange(range)
子字符串 从一个字符串获取一个子字符串通过:使用string.index(of:str)
获取起始和结束字符index,然后利用区间获取子字符串。
1 2 3 4 5 6 7 let greeting = "Hello, world!" let index = greeting.index(of: "," ) ?? greeting.endIndex let beginning = greeting[..< index] let newString = String (beginning)
在Swift中子字符串指向原字符串某段字符串的内存地址。这是Swift性能优化的地方,这样避免了在获取子字符串时需要进行内存的拷贝和节省了内存空间。如果你需要长时间使用子字符串,你应该将子字符串转化成独立的字符串,因为如果子字符串一直在使用原来的字符串会一直存在,这样会有不必要的内存空间浪费。
元组 元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let tp1 = (28 ,180 ) print (tp1.0 ) print (tp1.1 ) let tp2 = (28 ,"Fynn" ) let (age,name) = tp2print (age) print (name) let tp3 = (age:28 ,name:"Fynn" ) print (tp3.age)print (tp3.name)
使用场景:
当方法返回多个值时,可以使用元组类型。
两个值交换(a,b) = (b,a)
控制流中解析赋值
Swift提供了Arrays
、Sets
、Dictionaries
三种基本的集合类型。
Arrays保存有序数据的集合
Sets保存无序无重复数据的集合
Dictionaries保存无序的键值对的值
Swift中的集合类型存储的数据值类型必须明确,不能将不同类型值存放一起。
1 2 3 4 5 6 7 8 var arr = [Int ]() arr.append(1 ) var name = Set <String >() name.insert("Fynn" ) var nameAndAge = [String : Int ]() nameAndAge["Fynn" ] = 28
数组Arrays 通过Array(repeating:,count:)
可以创建指定大小的默认值相同的数组。
1 var tenAge = Array (repeating:0 , count: 10 )
通过+
符号可以将两个相同类型的数组相加。
1 2 3 4 5 6 7 8 9 10 11 var anotherAge = Array (repeating: 10 ,count:5 )var newAge = tenAge+ anothernewAge += [2 ,3 ] newAge.append(5 ) newAge.insert(5 ,at:4 ) newAge.remove(at:5 ) newAge[2 ... 6 ] = [2 ,3 ,4 ,5 ]
可以通过Array.count
读取数组的长度,Array.isEmpty
检测数组是否为空。
for-in
循环中通过调用enumerated()
方法返回一个元组,这个元组有获得数据项和其对应的索引值组成。
1 2 3 4 var name = ["Fynn" ,"Echo" ,"Mouse" ]for (index, value) in name.enumerated() { print ("index: \(index) ,name: \(value) " ) }
集合(Sets) 集合存储着类型相同没有重复着的无序值。这些值类型一定提供了一种可以计算出其哈希值的方法。Swift中的基本类型(String,Int,Double,Bool)默认都有哈希值。字典的键类型也必须是可以计算出哈希值的。
你可以自己定义类型作为集合的值或字典的键。但种类型必须符合 Swift 标准库中的Hashable协议。符合Hashable协议的类型需要提供一个类型为Int的可读属性hashValue。
Hashable协议符合Equatable协议,Equateble协议要求实现“==”运算符方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct Persion { var name: String var age: Int } extension Persion : Equatable { static func == (lhs : Persion , rhs : Persion ) -> Bool { return (lhs.name == rhs.name && lhs.age == rhs.age) } } extension Persion : Hashable { var hashValue: Int { return name.hashValue^ age.hashValue } } var me = Persion (name:"Fynn" ,age: 28 )var family:Set = [me]
集合常用方法:
1 2 3 4 5 6 7 8 var name = Set <String >() var email:Set = ("fynn.90@outlook.com" ) name.insert["Echo" ] name.count name.isEmpty name.remove("Echo" ) name.contains("Echo" )
集合是无序的,但调用sorted()
可以获取一个使用顺序操作符<
排序后的可迭代对象。
1 2 3 4 5 6 7 let name: Set = ["Fynn" ,"Echo" ,"Mouse" ]for n in name.sorted() { print ("name: \(n) " ) }
集合之间操作:
使用intersection(_:)
方法根据两个集合中都包含的值创建的一个新的集合。
使用symmetricDifference(_:)
方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
使用union(_:)
方法根据两个集合的值创建一个新的集合。
使用subtracting(_:)
方法根据不在该集合中的值创建一个新的集合。
集合之间关系:
使用“是否相等”运算符(==
)来判断两个集合是否包含全部相同的值。
使用isSubset(of:)
方法来判断一个集合中的值是否也被包含在另外一个集合中。
使用isSuperset(of:)
方法来判断一个集合中包含另一个集合中所有的值。
使用isStrictSubset(of:)
或者isStrictSuperset(of:)
方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
使用isDisjoint(with:)
方法来判断两个集合是否不含有相同的值(是否没有交集)。
字典 字典用于存储相同类型的值的容器。每个值都关联唯一的键,键类型必须是遵循Hashable
协议。
1 2 3 4 5 6 7 8 9 10 var namesAndAge = [String :Int ]()nameAndAge = ["Fynn" :28 ,"Echo" :28 ] for (key,value) in namesAndAge { print ("\(key) :\(value) " ) } var names = namesAndAge.values var ages = nameAndAge.keys
枚举(enum)为一组相关的值定义一个共同的类型,在代码中可以用类型安全的方式使用这些值。
1 2 3 4 5 6 enum Season { case spring case summer case autumn case winter }
枚举在Swift中是一等类型(Class,struct都是,首字母推荐大写)。它支持一些类特性,例如:计算属性、枚举附加值、实例方法。
关联值 可以为枚举成员定义关联值。在声明枚举时,定义成员可以初始时可以传递的值类型。每个枚举成员定义的关联值类型不可以不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enum Barcode { case upc(Int , Int , Int , Int ) case qrCode(String ) } var productBarcode = Barcode .upc(8 ,85909 ,51226 ,3 )productBarcode = .qrCode("ABCDEFGHIJKLMNOP" ) switch productBarcode { case .upc(let numberSystem, let manufacturer, let product, let check): print ("UPC: \(numberSystem) , \(manufacturer) , \(product) , \(check) ." ) case .qrCode(let productCode): print ("QR code: \(productCode) ." ) }
原始值 在声明枚举时我们可以为成员设定默认值(原始值),这些原始值类型必须相同。
1 2 3 4 5 enum ASCIIControlCharacter : Character { case tab = "\t " case lineFeed = "\n " case carriageReturn = "\r " }
在声明枚举时需指定原始值类型,并且成员的原始值必须是唯一的。原始值设定后就不能改变。
如果设定原始值类型是整型或字符串类型,如果不设定原始值,则成员会获得默认原始值。
1 2 3 4 5 6 7 8 9 10 11 enum Planet : Int { case mercury = 1 , venus, earth, mars, jupiter, saturn, uranus, neptune } enum CompassPoint : String { case north, south, east, west } print (Planet .venus.rawValue) print (CompassPoint .north.rawValue) let possiblePlanet = Planet (rawValue: 7 )
递归枚举 递归枚举 是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。需要在此成员变量前添加关键字indirect
。
1 2 3 4 5 enum ArithmeticExpression { case number(Int ) indirect case addition(ArithmeticExpression , ArithmeticExpression ) indirect case multiplication(ArithmeticExpression , ArithmeticExpression ) }
运算符是一个编程语言的基础,Swift提供和其他语言差不多的运算符。有所区别的是Swift没有其他语言常见的自加减 (++a,a++,—a,a—)!提供了在Swift常见的区间 运算符、空合 运算符。
区间运算符 闭区间运算符a…b
定义 一个a
至b
所有值的区间(包含b)。a的值不能大于b。
1 2 3 for index in 1 ... 5 { print ("\(index) * 5 = \(index * 5 ) " ) }
半开区间运算符a..<b
定义一个a至b不包含b的所有值。半开区间包含起始值但不包含结束值。在迭代数组时数组长度为结束值则就应该使用半开区间运算符。
1 2 3 4 5 let name = ["Fynn" ,"Echo" ,"Mouse" ]let count = name.countfor i in 0 ..< count { print ("index:\(i+ 1 ) --name:\(name[i]) " ) }
单侧区间运算符[a...]
或[...a]
一边到无穷大。半开区间也可以是单侧区间[...<a]
1 2 3 for i in [0 ... ] { print ("index:\(i+ 1 ) -- name:\(name[i]) " ) }
空合运算符 Swift中有个表示空(无)的类型nil
。当一个变量的值可能是nil
时它就被称为可选型。空合运算符就是为可选型变量赋值给别人时设计的。
1 2 3 4 let defaultColorName = "red" var userDefinedColorName: String ? var colorNameToUse = userDefinedColorName ?? defaultColorName
控制流分为三类:
循环遍历:for-in
、while
、repeat-while
条件语句:if
、switch
、where
、guard
控制转移:continue
、break
、return
、fallthrough
、throw
switch Swift中的switch语句每个判断条件后面可以不写break
默认会自动跳出。并且编译器会要求将各种情况都列出来否则就会报错,所以default
会作为最后的条件判断。
1 2 3 4 5 6 7 8 9 let name = "mouse" switch name { case "fynn" : print ("you are fynn" ) case "echo" : print ("you are echo" ) case default : print ("you are mouse" ) }
case
语句后面的判断条件写法可以很丰富,可以是区间、逗号分隔多个值、元组(元组值可以是区间)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let num = 6 switch num { case 0 ,1 : print (0 ) case 2 ... 5 : print ("2...5" ) case 6 ..< 10 : print ("6...10" ) default : print ("10..." ) } let person = (28 ,"Fynn" )switch person { case (let age,"Fynn" ): print ("age:\(age) ,name:Fynn" ) case (let age, "Echo" ) where age != 28 : print ("age:\(age) ,name:Echo" ) case let (age,name): print ("age: \(age) ,name:\(name) " ) }
Switch 默认匹配到满足条件后会自动跳出,如果需要继续向下匹配可以在case
分支尾部使用fallthrough
关键字。
guard guard Condition else {}
guard默认会跟着一个else,当Condition条件为真时继续执行,否则进入else分支。常用于方法内条件判断不符合就提前提出。
1 2 3 4 5 6 7 8 9 var person = ["name" :"Fynn" ,"age" :"28" ]func greet (_ person :[String :Int ]) { guard person["name" ] != nil else { print ("what's you name?" ) return } print ("hello \(person.name) " ) } greet(person)
函数和闭包 Swift中的函数最大的特点就是参数形态很多,除了常见的有参,无参,默认参数。还可以设置外部参数名,内部参数名,输入参数,可变参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func greet (name : String ) { print ("hello,\(name) " ) } func greetWorld () { print ("hello,world!" ) } func greetPerson (name : String = "world" ) { print ("hello,\(name) " ) } func person (name parameter1 :String ,age parameter2 : Int ) { print ("name: \(parameter1) ,age: \(parameter2) " ) } person(name:"Mouse" ,age: 1 ) func prefixName (_ name : inout String ) { name = "sir,\(name) " } var name = "Fynn" prefixName(& name) print (name)func maxNum (_ numbers : Int ...) -> Int { var num = 0 for n in numbers { if num < n { num = n } } return num } maxNum(3 ,6 ,10 ,8 ,6 )
上面的例子都是函数没有返回值,如果函数有返回值需要声明返回值数量和值类型。如果是多个参数以元组形式返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func person (_ name : String ,age : Int ) -> String { return "name:\(name) ,age:\(age) " } func maxAndMin (_ numbers : Int ...) ->(max:Int ,min:Int ) { var currentMin = numbers[0 ] var currentMax = numbers[0 ] for value in numbers[1 ..< numbers.count] { if value < currentMin { currentMin = value } else if value > currentMax { currentMax = value } } return (max:currentMax, min:currentMin) } let (maxNumber,minNumber) = maxAndMin(4 ,- 1 ,5 ,8 ,9 ,100 ,- 3 )
闭包是可以没有函数名、参数名自包含代码块。它可以捕获和存储其所在上下文中任意变量和常量的引用。
1 2 3 4 {(parameters) -> returnType in statements }
闭包的参数可以是 in-out
参数、可变参数,但不能设置默认值。元组可以作为参数或返回值。
闭包多种简写形式,以Array.sorted(by:)
为例,它用于对数组元素进行排序。Array.sorted(by:)
接受一个闭包作为参数,闭包默认接受两个值作为参数,如果第一值应该出现在第二个值前面闭包函数应该返回true
,反之返回false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let names = ["Fynn" ,"Echo" ,"Mouse" ]func backward (_ s1 :String , _ s2 : String ) { return s1 > s2 } names.sorted(by: backward) names.sorted(by:{(s1: String , s2:String ) -> Bool in return s1> s2 }) names.sorted(by:{(s1:String ,s2:String )->Bool in return s1> s2}) names.sorted({by:{s1,s2 in return s1 > s2}}) names.sorted(by:{s1, s2 in s1 > s2}}) names.sorted(by:{$0 > $1 })
尾随闭包 当闭包作为最后一个参数传递给函数时,可以使用尾随闭包写法简化代码的编写。
1 2 3 4 5 6 7 8 9 10 func someFn (_ name :String ,fn : (String )->Void ) { fn(name) } someFn("Fynn" ,fn:{ print ("hello \($0 ) " ) }) someFn("Echo" ){ print ("hello \($0 ) " ) }
逃逸闭包 当一个闭包作为参数传递给函数,但要在函数返回后才被执行,则这个闭包被称为逃逸闭包。如果是逃逸闭包需要用@escaping
关键字定义它。并且逃逸闭包如果要使用其上下文需要显式的引用self
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var completionHandlers: [() -> Void ] = []func someFunctionWithEscapingClosure (completionHandler : @escaping () -> Void ) { completionHandlers.append(completionHandler) } func someFunctionWithNonescapingClosure (closure : () -> Void ) { closure() } class SomeClass { var x = 10 func doSomething () { someFunctionWithEscapingClosure { self .x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } let instance = SomeClass ()instance.doSomething() print (instance.x)completionHandlers.first? () print (instance.x)
类和结构体 Swift中的类(class)和结构体(struct)及其相似。它们共同点有:
定义属性
定义方法
自定义下标
定义构造函数
定义扩展
实现协议
类和结构体看起来一摸一样,但两个关键特性使得它们应用场景区别开来
类是引用类型,结构体是值类型
类可以继承,结构体没有继承
结构体默认拥有逐一构造器,而类没有。
1 2 3 4 5 struct point { var x = 0 var y = 0 } let p = point(x:120 ,y:120 )
Swift所有基础类型(Int、Float、Bool、String、Array、Dictionary)在底层都是通过结构体实现。
值类型,在赋值时是通过拷贝变量存储的真实值。而引用类型,在赋值时是传递真实数据内存的引用。
判断两个变量或常量是否引用同一个类实例通过恒等运算符:===
和!==
类和结构体使用区别:
结构体表示具体的值,类表示抽象性的物体
想拥有继承特性就该使用类
更高的效率该应用结构体
属性&方法&下标 计算属性可以在类(Class)、结构体(Struct)、枚举(Enum)中关联一个值。而存储属性只能用于类(Class)、结构体(Struct)。
存储属性和计算属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 () var center: Point { get { let centerX = origin.x + (size.width / 2 ) let centerY = origin.y + (size.height / 2 ) return Point (x: centerX, y: centerY) } set (newCenter) { origin.x = newCenter.x - (size.width / 2 ) origin.y = newCenter.y - (size.height / 2 ) } } } var square = Rect (origin: Point (x: 0.0 , y: 0.0 ), size: Size (width: 10.0 , height: 10.0 )) let initialSquareCenter = square.centersquare.center = Point (x: 15.0 , y: 15.0 ) print ("square.origin is now at (\(square.origin.x) , \(square.origin.y) )" )
注意:结构体的实例赋予一个常量则它的属性将不能修改,因为它是值类型。
延迟存储属性 当初始化属性值需要大量复杂工作,而这些工作并不是构造时需要的时候,为了提高效率我们可以将这个属性定义为延迟存储属性lazy
。当第一次调用这个属性再去初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class DataImporter { var fileName = "data.txt" } class DataManager { lazy var importer = DataImporter () var data = [String ]() } let manager = DataManager ()manager.data.append("Some data" ) manager.data.append("Some more data" )
属性观察器 属性观察器willSet
在属性值在被设新值前会被调用,willSet
观察器会将新值作为参数(默认参数名:newVale
)传入。你可以 自定义此参数名。
属性观察器didSet
在属性被设置后被调用,旧值会作为参数(默认参数:oldValue
)传入。如果你在此观察器内对其赋值,新值会被旧值覆盖。
在willSet
无法修改属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Num { var n = 0 { willSet { print (newValue) } didSet { print (oldValue) n = 1 } } } var n = Num ()n.n = 3 n.n
静态属性 通过关键字static
可以定义一个静态属性(类型属性),它属于包含的数据类型而不属于任何实例,可以通过类型名直接获取赋值,实例间可以共享它。
实例方法 实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。
值类型(结构体,枚举)的属性不能在实例中被修改。如果你想在方法中修改值类型的属性值,你需要在该方法前添加关键字mutating
。
1 2 3 4 5 6 7 8 9 struct point { var x = 0.0 , y = 0.0 mutation func move (deltaX : Double , deltaY : Double ) { x += deltaX y += deltaY } } var somePoint = point(0.0 ,0.0 )somePoint.move(deltaX: 40.0 , deltaY: 20.9 )
静态方法 静态方法输入包含它的类型而不是某个实例,可以通过类型名称直接调用它。在func
之前添加关键字static
Javascript中数组、object可以通过下标访问内部值。而在Swift中你可以给类(Class)、结构体(struct)、枚举(enum)定义下标,用于获取内部值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 struct Matrix { let rows: Int , columns: Int var grid: [Double ] init (rows : Int , columns : Int ) { self .rows = rows self .columns = columns grid = Array (repeating: 0.0 , count: rows * columns) } func indexIsValid (row : Int , column : Int ) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript (row : Int , column : Int ) -> Double { get { assert (indexIsValid(row: row, column: column), "Index out of range" ) return grid[(row * columns) + column] } set { assert (indexIsValid(row: row, column: column), "Index out of range" ) grid[(row * columns) + column] = newValue } } } var matrix = Matrix (rows: 2 , columns: 2 )matrix[0 , 1 ] = 1.5 matrix[1 , 0 ] = 3.2
subscript
可以任意类型和数量的值作为参数,并返回任意类型的值。
继承 Swift一个类只能继承一个父类,而子类重写父类的属性和方法需要使用关键字override
声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Car { var currentSpeed = 0.0 ; var description: String { return "traveling at \(currentSpeed) miles per hour" ; } func makeNoise () { } } class AutomaticCar : Car { var gear = 1 ; override var currentSpeed: Double { didSet { gear = Int (currentSpeed / 10.0 ) + 1 } } override func makeNoise () { print ("Choo Choo" ) } override var description: String { return super .description + " in gear \(gear) " } }
注意: 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器,因为它们不会被设置值。
如果父类某些特性不想被子类重写,可以通过关键字final
来防治它们被重写。例如:final var
、final func
、final class func
、final subscript
。
构造 在使用类(class)、结构体(struct)、枚举(enum)实例之前的准备过程被称为构造。
Swift规定类、结构体中的存储属性在实例创建完成前必须要有初始值。有两种方式设定该初始值,一个是在定义该存储属性时给它设定默认值,另外一种就是在构造时设定初始值。
构造语法规则是,在类、结构体、枚举内部定义名字为init
的方法。
如果类或结构体的存储属性都设置了默认值,同时没有自定义构造器,那么Swift会为它们提供一个默认构造器,(无参数实例话该类)。
1 2 3 4 5 6 class ShoppingListItem { var name: String ? var quantity = 1 var purchased = false } var item = ShoppingListItem ()
Swift还为没有自定义构造器提供了成员逐一构造器。逐一构造器中的参数名就是该结构体中声明的属性名。即使这些属性没有设置默认值,逐一成员构造器依然有效。
1 2 3 4 struct Size { var width = 0.0 , height = 0.0 } let twoByTwo = Size (width: 2.0 , height: 2.0 )
假如你自定义了构造器,则默认构造器和逐一成员构造器将不存在,你想要在自定义的构造器确保每个存储属性都设置的初始值。
自定义构造器 Swift可以为类、结构体、枚举定义多个自定义构造器。通过构造参数名(外部参数名,内部参数名)、参数类型来区分这些构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct Celsius { var temperatureInCelsius: Double init (fromFahrenheit fahrenheit : Double ) { temperatureInCelsius = (fahrenheit - 32.0 ) / 1.8 } init (fromKelvin kelvin : Double ) { temperatureInCelsius = kelvin - 273.15 } init (_ celsius : Double ) { temperatureInCelsius = celsius } } let boilingPointOfWater = Celsius (fromFahrenheit: 212.0 ) let freezingPointOfWater = Celsius (fromKelvin: 273.15 )let bodyTemperature = Celsius (37.0 )
上面例子中:init(fromFahrenheit)
和init(fromKelvin)
构造器参数类型相同,通过外部参数名做区分。你也可以不设定外部参数,在调用构造器时直接传入形参,例如init(_ celsius)
。
注意: 常量存储属性可以在构造器中指定其值。类类型中,常量属性只能在定义它的类构造器中修改值,子类不能修改。
值类型的构造代理 自定义构造器可以定义多个,你可以在一个自定构造器中通过self.init
调用另一个自定义构造器完成所有存储属性的初始化。这些实现代码复用,简化代码。这样的构造方式称为构造代理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ) let originY = center.y - (size.height / 2 ) self .init (origin: Point (x: originX, y: originY), size: size) } }
class的构造器类型 Swift中的类有两种构造器,一种是在构造器中初始化了所有存储属性值,并通过调用父类构造器完成了父类实例话(父类的属性必须在父类中的构造器完成初始化),这样的构造器称为指定构造器 。每个类必须至少要有一个指定构造器。另外一种构造器是便利构造器 ,它必须能够间接或直接的调用同类中的一个指定构造器,在便利构造器中整理存储属性值,并通过调用其他构造器传递出去,最后在指定构造器中实现所有存储属性的初始化。必须通过convenience
关键字定义便利构造器。
类的构造器代理规则:
指定构造器必须调用其直接父类的的指定构造器。
便利构造器必须调用同 类中定义的其它构造器。
便利构造器必须最终导致一个指定构造器被调用。
便利构造器:
1 2 3 4 5 6 7 8 9 10 class Food { var name: String init (name : String ) { self .name = name } convenience init () { self .init (name: "[Unnamed]" ) } } let empty = new Food ();
上面例子构造链:
子类构造器:
1 2 3 4 5 6 7 8 9 10 11 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 ) } }
上面例子构造链:
类的两段式构造 类构造分为两个部分:
初始化各个属性值。在这个部分中self
是不存在的,你不能通过self
获取属性值或调用方法。
优化已经初始化的属性值。这个部分self
存在了,你可以调用它获取属性值或方法修改完成初始化的属性值。
判断第一阶段结束的一个标准是子类调用了父类的构造器。而子类的自有的属性必须在调用父类构造器前完成初始化。
图解两段式构造过程第一阶段:
构造过程从子类的便利构造器开始,这个便利构造器此时没法修改任何属性,它把构造任务代理给同一类中的指定构造器。指定构造器在保证自有属性都完成初始化后调用父类指定构造器。父类指定构造器在完成所有属性初始化后就完成了构造第一阶段。
两段式构造过程第二阶段:
父类指定构造器在完成所有属性初始化后,可以进一步定制实例。父类指定构造器调用完成,子类指定构造器就可以定制实例,最后当指定构造器调用完成后,便利构造器可以进行定制实例。
父类的构造器重写和继承 当你在子类编写一个和父类构造器模式相匹配的构造器时,你其实在重写父类构造器,这时你需要在重写的构造器前添加关键字override
。即使你重写的是系统自带的构造器(默认构造器)都需要带关键字override
。
当你将父类指定构造器重写为便利构造器需要添加关键字overried
,但你重写父类的便利构造器时则无需添加关键字override
。
一般情况子类是不会自动继承父类构造器,但在满足特定条件下子类构造器会继承父类构造器。
如果子类构造器自己的属性都提供了初始值,一下两种方式它将可以继承父类构造器。
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。
如果子类添加了多个便利构造器,这两条依然受用。
在构造前添加关键字require
则表示这个构造器是必要构造器,所有子类必须实现该构造器。子类在重写父类必要构造器时必须在子类构造器面前添加关键字require
,而不需要添加关键字override
。
可失败构造器 在类、结构体、枚举构造过程中可能构造失败(无效参数,缺少所需外部资源,不满足某种必要条件),这时你需要定义一个可失败构造器应对这种情况。
可失败构造器语法是在init
关键字后面加一个问号(init?
)。可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。可失败构造器可以调用return nil
来表明可失败构造器在何种情况下应该“失败”。
析构器 析构器可以在一个类的实例被释放前自动被Swift调用,如果你在该类中有定义它。
析构器的定义方式是通过关键字deinit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Bank { static var coinsInBank = 10_000 static func distribute (coins numberOfCoinsRequested : Int ) -> Int { let numberOfCoinsToVend = min (numberOfCoinsRequested, coinsInBank) coinsInBank -= numberOfCoinsToVend return numberOfCoinsToVend } static func receive (coins : Int ) { coinsInBank += coins } } class Player { var coinsInPurse: Int init (coins : Int ) { coinsInPurse = Bank .distribute(coins: coins) } func win (coins : Int ) { coinsInPurse += Bank .distribute(coins: coins) } deinit { Bank .receive(coins: coinsInPurse) } } var playerOne: Player ? = Player (coins: 100 )print ("A new player has joined the game with \(playerOne! .coinsInPurse) coins" )print ("There are now \(Bank.coinsInBank) coins left in the bank" )playerOne! .win(coins: 2_000 ) print ("PlayerOne won 2000 coins & now has \(playerOne! .coinsInPurse) coins" )print ("The bank now only has \(Bank.coinsInBank) coins left" )playerOne = nil print ("PlayerOne has left the game" )print ("The bank now has \(Bank.coinsInBank) coins" )
Swift通过引用计数(ARC)处理实例的内存管理,当一个实例引用计数为0时代表它所占用的内存将可以被释放。在释放前析构器将被调用。
扩展 扩展是给已经定义(无论是自己还是Swift的)好的类(class)、结构体(struct)、枚举、协议(protocol)添加额外的功能。常见的用法是给Swift定义好的类型添加自己需要的方法或计算属性,以达到方便使用。
扩展支持的额外功能有:
添加计算型属性和计算型类型属性,存储型属性和观察器属性不能添加。
定义实例方法和类型方法。
提供型的构造器。如果你需要自定义构造器而又不想覆盖系统自带的默认构造器和逐一构造器,你可以将自定义构造器通过扩展添加。
定义下标
定义和使用新的嵌套类型
使一个已有类型符合某个协议
扩展协议,提供协议要求的实现,或添加额外的功能,从而让符合这个协议的类型拥有这些功能
扩展的语法是:
1 2 3 4 5 6 extension someType :protocolTyle { }
计算属性 扩展Double
给它添加计算属性使它快速将不同的距离单位转换成“米”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extension Double { var km: Double { return self * 1_000.0 } var m : Double { return self } var cm: Double { return self / 100.0 } var mm: Double { return self / 1_000.0 } var ft: Double { return self / 3.28084 } } let oneInch = 25.4 .mmprint ("One inch is \(oneInch) meters" )let threeFeet = 3 .ftprint ("Three feet is \(threeFeet) meters" )let aMarathon = 42 .km + 195 .mprint ("A marathon is \(aMarathon) meters long" )
构造器 扩展可以为以后类型添加构造器,但只能为类添加便利构造器不能添加指定构造器和解析器。
如果扩展给值类型添加构造器,这些值类型没有自定义构造器并且存储属性有默认值,则在扩展添加的构造器内可以调用默认构造器或逐一构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct Size { var width = 0.0 , height = 0.0 } struct Point { var x = 0.0 , y = 0.0 } struct Rect { var origin = Point () var size = Size () } extension Rect { init (center : Point , size : Size ) { let originX = center.x - (size.width / 2 ) let originY = center.y - (size.height / 2 ) self .init (origin: Point (x: originX, y: originY), size: size) } }
方法 扩展Int
让它拥有so cool的能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 extension Int { func repetitions (task : () -> Void ) { for _ in 0 ..< self { task() } } mutating func square () { self = self * self } } 3 .repetitions({ print ("Hello!" ) }) 5 .repetitions { print ("Goodbye!" ) } var someInt = 3 someInt.square()
下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 extension Int { subscript (digitIndex : Int ) -> Int { var decimalBase = 1 for _ in 0 ..< digitIndex { decimalBase *= 10 } return (self / decimalBase) % 10 } } 746381295 [0 ]746381295 [1 ]746381295 [2 ]746381295 [8 ]
协议 Swift中的协议类似其他高级编程语言的接口。它用于定义一套标准,符合它要求的类型必须实现这些标准。类、结构体、枚举可以遵循协议。
属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protocol SomeProtocol { var mustBeSettable:Int { get set } var doesNotNeedToBeSettable: String { get } static var someTypeProperty: Int { get set } }
协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protocol FullyNamed { var fullName: String { get } } struct Person : FullyNamed { var fullName: String } let john = Person (fullName: "John Appleseed" )class Starship : FullyNamed { var prefix : String ? var name: String init (name : String , prefix : String ? = nil ) { self .name = name self .prefix = prefix } var fullName: String { return (prefix != nil ? prefix ! + " " : "" ) + name } } var ncc1701 = Starship (name: "Enterprise" , prefix: "USS" )
方法 1 2 3 4 5 protocol SomeProtocol { static func someTypeMothod () -> Double }
协议的方法不需要大括号和方法体。可以在协议中定义具有可变参数的方法,但不能指定参数默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 protocol RandomNumberGenerator { func random () -> Double } class LinearCongruentialGenerator : RandomNumberGenerator { var lastRandom = 42.0 let m = 139968.0 let a = 3877.0 let c = 29573.0 func random () -> Double { lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m)) return lastRandom / m } } let generator = LinearCongruentialGenerator ()print ("Here's a random number: \(generator.random()) " )print ("And another one: \(generator.random()) " )protocol Togglable { mutating func toggle () } enum OnOffSwitch : Togglable { case off, on mutating func toggle () { switch self { case .off: self = .on case .on: self = .off } } } var lightSwitch = OnOffSwitch .offlightSwitch.toggle()
构造器 1 2 3 4 5 6 7 8 9 10 11 12 protocol SomeProtocol { init (someParameter : Int ) } class SomeClass : SomeProtocol { required init (someParameter : Int ) { } }
如果一个子类重写了父类的构造器并且又实现了协议中该构造器,那么在实现该构造器时需要添加 required
和override
关键字。
作为类型 协议可以作为一种类型,用于定义变量、常量、或属性的类型。也可以用于声明方法,函数,构造器参数类型或返回值类型。
当作为方法参数类型时,如果某个参数遵循多个协议,这些协议用”&”符号相连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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)
协议也可以用于定义数组和字典集合类型。let things: [TextRepresentable] = [game, d12, simonTheHamster]
检查协议一致性 协议既然是一种普通类型,那么就可以通过is
和as
检测某个实例的类型是否遵循某个协议。
is
用来检查实例是否符合某个协议,若符合则返回 true
,否则返回 false
。
as?
返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil
。
as!
将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 protocol HasArea { var area: Double { get } } class Circle : HasArea { let pi = 3.1415927 var radius: Double var area: Double { return pi * radius * radius } init (radius : Double ) { self .radius = radius } } class Country : HasArea { var area: Double init (area : Double ) { self .area = area } } class Animal { var legs: Int init (legs : Int ) { self .legs = legs } } let objects: [AnyObject ] = [ Circle (radius: 2.0 ), Country (area: 243_610 ), Animal (legs: 4 ) ] for object in objects { if let objectWithArea = object as? HasArea { print ("Area is \(objectWithArea.area) " ) } else { print ("Something that doesn't have an area" ) } }
扩展协议 通过扩展使已有类型遵循某个协议。
1 2 3 4 5 6 7 8 9 10 11 12 13 protocol TextRepresentable { var textualDescription: String { get } } extension Dice : TextRepresentable { var textualDescription: String { return "A \(sides) -sided dice" } } let d12 = Dice (sides: 12 , generator: LinearCongruentialGenerator ())print (d12.textualDescription)
通过扩展为一遵循某个协议的类型提供属性、方法和下标的实现。这样的方式可以为以遵循协议添加共有的方法。协议本来是不可以拥有具体的实现,但通过扩展可以让协议拥有具体的实现并让遵循它的类型自动拥有这些实现。
如果遵循协议的类型自己定义了这些实现,则自定义的实现会代替扩展中默认的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 extension RandomNumberGenerator { func randomBool () -> Bool { return random() > 0.5 } } let generator = LinearCongruentialGenerator ()print ("Here's a random number: \(generator.random()) " )print ("And here's a random Boolean: \(generator.randomBool()) " )
你还可以为协议扩展添加条件,只有遵循协议并满足限制条件的类型才能获得协议提供的默认实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 protocol TextRepresentable { var textualDescription: String { get } } struct Hamster { var name: String var textualDescription: String { return "A hamster named \(name) " } } extension Hamster : TextRepresentable {}extension Collection where Iterator .Element : TextRepresentable { var textualDescription: String { let itemsAsText = self .map { $0 .textualDescription } return "[" + itemsAsText.joined(separator: ", " ) + "]" } } let murrayTheHamster = Hamster (name: "Murray" )let morganTheHamster = Hamster (name: "Morgan" )let mauriceTheHamster = Hamster (name: "Maurice" )let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]print (hamsters.textualDescription)
如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
协议的继承 协议可以像类一样继承一个或多个协议。
1 2 3 4 5 6 7 protocol PrettyTextRepresentable : TextRepresentable { var prettyTextualDescription: String { get } }
如果你希望协议只被类遵循,可以是协议继承class
。这样该协议只能被类遵循。
1 2 3 4 protocol SomeClassOnlyProtocol : class , SomeInheritedProtocol { }
泛型 泛型是强类型语言为了保持代码通用性的一种语法。它可以让变量类型可以在使用它之前指定。
1 2 3 4 5 6 func swapTwoInts (_ a : inout Int , _ b : inout Int ) { let temporaryA = a a = b b = temporaryA }
上面例子是交换参数a
、b
值,如果参数类型是不确定的,只有调用时才知道,这时我们就可以使用泛型了用来满足这个场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func swapTwoValues <T >(_ a : inout T , _ b : inout T ) { let temporaryA = a a = b b = temporaryA } var someInt = 3 var anotherInt = 107 swapTwoValues(& someInt, & anotherInt) var someString = "hello" var anotherString = "world" swapTwoValues(& someString, & anotherString)
上面例子中T
称为占位符类型,在调用swapTwoValues
才会确定它的类型。 如果需要多个占位符,它们都可以写在尖括号里面,用逗号分隔。
泛型类型 泛型除了用于函数,也可以应用在类(Class)、结构体(Struct)、枚举(Enun)。
定义一个泛型结构体,用于处理数组的添加项和移除项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct Stack <Element > { var items = [Element ]() mutating func push (_ item : Element ) { items.append(item) } mutating func pop () -> Element { return items.removeLast() } } var stackOfStrings = Stack <String >()stackOfStrings.push("uno" ) stackOfStrings.push("dos" ) stackOfStrings.push("tres" ) stackOfStrings.push("cuatro" ) let fromTheTop = stackOfStrings.pop()
可以使用扩展给一个泛型类型添加新功能
1 2 3 4 5 6 extension Stack { var topItem: Element ? { return items.isEmpty ? nil : items[items.count - 1 ] } }
泛型约束 在定义泛型类型时,可以指定调用时的类型必须满足的条件,这被称为泛型约束。
泛型约束可以使得代码具有灵活性的情况下保证了代码的可靠性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func someFunction <T : SomeClass , U : SomeProtocol >(someT : T , someU : U ) { } func findIndex <T : Equatable >(of valueToFind : T , in array :[T ]) -> Int ? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil } let strings = ["cat" , "dog" , "llama" , "parakeet" , "terrapin" ]if let foundIndex = findIndex(ofString: "llama" , in: strings) { print ("The index of llama is \(foundIndex) " ) } let doubleIndex = findIndex(of: 9.3 , in: [3.14159 , 0.1 , 0.25 ])let stringIndex = findIndex(of: "Andrea" , in: ["Mike" , "Malcolm" , "Andrea" ])
关联类型 在协议中为某个类型提供占位符被称为关联类型。需要在协议中使用associatedtype
声明占位符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 protocol Container { associatedtype ItemType mutating func append (_ item : ItemType ) var count: Int { get } subscript (i : Int ) -> ItemType { get } } struct Stack <Element >: Container { var items = [Element ]() mutating func push (_ item : Element ) { items.append(item) } mutating func pop () -> Element { return items.removeLast() } mutating func append (_ item : Element ) { self .push(item) } var count: Int { return items.count } subscript (i : Int ) -> Element { return items[i] } }
泛型where语句 同过where
关键字添加条件以约束类型应用场景。通过泛型约束有时并不能满足类型的全部约束条件,通过where
可以完善约束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func allItemsMatch <C1 : Container , C2 : Container > (_ someContainer : C1 , _ anotherContainer : C2 ) -> Bool where C1 .ItemType == C2 .ItemType , C1 .ItemType : Equatable { if someContainer.count != anotherContainer.count { return false } for i in 0 ..< someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } return true } var stackOfStrings = Stack <String >()stackOfStrings.push("uno" ) stackOfStrings.push("dos" ) stackOfStrings.push("tres" ) var arrayOfStrings = ["uno" , "dos" , "tres" ]if allItemsMatch(stackOfStrings, arrayOfStrings) { print ("All items match." ) } else { print ("Not all items match." ) }
where
还可以应用于扩展中
1 2 3 4 5 6 7 8 9 extension Stack where Element : Equatable { func isTop (_ item : Element ) -> Bool { guard let topItem = items.last else { return false } return topItem == item } }
自动引用计数 Swift和Java,JavaScript一样无需手动释放内存,它会自动释放不会被使用的内存。自动引用计数 (ARC)被用来跟着和管理类实例的内存状态,值类型的数据内存不会被ARC管理。
对于一个引用类型的类实例,ARC会统计这个实例被其它实例或变量引用的次数,当发现这个实例引用次数为0时,则表示它不会被使用了,最后会被系统释放掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Person { let name: String init (name : String ) { self .name = name print ("\(name) is being initialized" ) } deinit { print ("\(name) is being deinitialized" ) } } var reference1: Person ?var reference2: Person ?var reference3: Person ?reference1 = Person (name: "John Appleseed" ) reference2 = reference1 reference3 = reference1 reference1 = nil reference2 = nil reference3 = nil
强引用 在实际代码中,两个实例之间或实例和闭包之间产生循环引用情况,这种引用被称为强引用,必须通过指明它们之间的引用关系才能使得ARC准确的统计引用计数释放内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Person { let name: String init (name : String ) { self .name = name } var apartment: Apartment ? deinit { print ("\(name) is being deinitialized" ) } } class Apartment { let unit: String init (unit : String ) { self .unit = unit } var tenant: Person ? deinit { print ("Apartment \(unit) is being deinitialized" ) } } var john: Person ? var unit4A: Apartment ? john = Person (name: "John Appleseed" ) unit4A = Apartment (unit: "4A" )
上面的例子,两个实例还没有产生循环引用。ARC关系如下图。
1 2 john! .apartment = unit4A unit4A! .tenant = john
上面代码使得两个实例之间产生了循环强引用。ARC关系如下图。
1 2 3 john = nil unit4A = nil
上面的代码表示Apartment
、Person
实例不在需要,内存可以被释放。但实际上ARC并不会释放这两个实例的内存,因为Apartment
、Person
引用计数不为0。
如上图展示的,两个实例内部属性的循环引用导致引用计数不为0。
弱引用(weak)和 无主引用(unowned) 可以用 弱引用 和 无主引用 这两个方式来说明循环引用之间的关系。这样ARC能够知道实例内存在什么时候可以 被释放。
弱引用(weak):用weak
关键字声明可选型的var
变量,该变量引用的实例被设为nil时,该变量也会被设置为nil。以切断其与实例的引用关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { let name: String init (name : String ) { self .name = name } var apartment: Apartment ? deinit { print ("\(name) is being deinitialized" ) } } class Apartment { let unit: String init (unit : String ) { self .unit = unit } weak var tenant: Person ? deinit { print ("Apartment \(unit) is being deinitialized" ) } }
建立强引用:
1 2 3 4 5 6 7 8 var john: Person ?var unit4A: Apartment ?john = Person (name: "John Appleseed" ) unit4A = Apartment (unit: "4A" ) john! .apartment = unit4A unit4A! .tenant = john
此时变量john
和unit4A
的关下图:
tenant
变量是弱引用,它不会阻止john
实例内存被释放。
无主引用(unowned):和弱引用不同的是,无主引用变量必须有值并且具有无主引用的实例必须比无主引用的实例生命周期长或相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Customer { let name: String var card: CreditCard ? init (name : String ) { self .name = name } deinit { print ("\(name) is being deinitialized" ) } } class CreditCard { let number: UInt64 unowned let customer: Customer init (number : UInt64 , customer : Customer ) { self .number = number self .customer = customer } deinit { print ("Card #\(number) is being deinitialized" ) } } var john: Customer ?john = Customer (name: "John Appleseed" ) john! .card = CreditCard (number: 1234_5678_9012_3456 , customer: john! )
实例Customer
和CreditCard
引用关系:
Customer
持有对CreditCard
强引用,而CreditCard
持有Cunstomer
的无主引用。当John
变量不在引用 Customer
实例,ARC会将Customer
实例内存释放掉。
由于再也没有指向Customer
实例的强引用,该实例将销毁了。
闭包的强引用 闭包也是引用类型,如果一个类实例有属性被赋值给闭包,而闭包使用了该类实例其他属性。这样闭包和该类实例就产生了强引用。即使该类实例被赋值nil
,该实例内存也不会释放。
Swift提供了定义捕获列表用于解决闭包强引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class HTMLElement { let name: String let text: String ? lazy var asHTML: (Void ) -> String = { [unowned self ] in if let text = self .text { return "<\(self .name) >\(text) </\(self .name) >" } else { return "<\(self .name) />" } } init (name : String , text : String ? = nil ) { self .name = name self .text = text } deinit { print ("\(name) is being deinitialized" ) } } var paragraph: HTMLElement ? = HTMLElement (name: "p" , text: "hello, world" )print (paragraph! .asHTML())paragraph = nil
错误处理 在运行时过程中,代码可能因为不可预知问题导致怎么系统奔溃。为了避免这种情况发生,我们可以指明(throws)代码中可能发生错误,在代码中抛出(throw)错误,并捕获(do-catch)发生错误后处理方式以避免系统奔溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 enum VendingMachineError : Error { case invalidSelection case insufficientFunds(coinsNeeded: Int ) case outOfStock } struct Item { var price: Int var count: Int } class VendingMachine { var inventory = [ "Candy Bar" : Item (price: 12 , count: 7 ), "Chips" : Item (price: 10 , count: 4 ), "Pretzels" : Item (price: 7 , count: 11 ) ] var coinsDeposited = 0 func dispenseSnack (snack : String ) { print ("Dispensing \(snack) " ) } func vend (itemNamed name : String ) throws { guard let item = inventory[name] else { throw VendingMachineError .invalidSelection } guard item.count > 0 else { throw VendingMachineError .outOfStock } guard item.price <= coinsDeposited else { throw VendingMachineError .insufficientFunds(coinsNeeded: item.price - coinsDeposited) } coinsDeposited -= item.price var newItem = item newItem.count -= 1 inventory[name] = newItem print ("Dispensing \(name) " ) } } let favoriteSnacks = [ "Alice" : "Chips" , "Bob" : "Licorice" , "Eve" : "Pretzels" , ] func buyFavoriteSnack (person : String , vendingMachine : VendingMachine ) throws { let snackName = favoriteSnacks[person] ?? "Candy Bar" try vendingMachine.vend(itemNamed: snackName) } var vendingMachine = VendingMachine ()vendingMachine.coinsDeposited = 8 do { try buyFavoriteSnack(person: "Alice" , vendingMachine: vendingMachine) } catch VendingMachineError .invalidSelection { print ("Invalid Selection." ) } catch VendingMachineError .outOfStock { print ("Out of Stock." ) } catch VendingMachineError .insufficientFunds(let coinsNeeded) { print ("Insufficient funds. Please insert an additional \(coinsNeeded) coins." ) }
throws
可以用于方法、函数、构造器。如果有返回值,throws
应放在箭头符号(->)前面。
1 2 func canThrowErrors () throws -> String func cannotThrowErrors () -> String
使用try?
通过将错误转换成一个可选值来处理错误。
1 2 3 4 5 6 7 8 9 10 11 12 func someThrowingFunction () throws -> Int { } let x = try? someThrowingFunction()let y: Int ?do { y = try someThrowingFunction() } catch { y = nil }
如果 你确定某个可抛出错误的方法不会抛出错误,你可以使用try!
声明它。
指定清理操作 defer 我们会用break
、return
提前退出当前作用域,或抛出一个异常。如果一些操作需要在退出该作用域前被执行,可以将它们放在defer
语句中。
1 2 3 4 5 6 7 8 9 10 11 12 13 func processFile (filename : String ) throws { if exists(filename) { let file = open (filename) defer { close(file) } while let line = try file.readline() { } } }
如果一个作用域有多个defer
语句,则defer
按声明倒序一次执行。最后声明的defer
语句先执行。
类型判断(is)和转换(as) 通过is
操作符可以判断操作符左边的实例类型是否属于操作符右边的类型,如果是返回true
否则返回false
。
1 2 3 4 5 6 7 8 9 10 11 class Person { var name: String var age: Int init (_ name :String ,_ age :Int ) { self .name = name self .age = age } } let echo = Person ("Echo" ,28 );print (echo is Person )
如果一个实例属于一个子类型,通过as
操作符可以将它转换成该类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class MediaItem { var name: String init (name : String ) { self .name = name } } class Movie : MediaItem { var director: String init (name : String , director : String ) { self .director = director super .init (name: name) } } class Song : MediaItem { var artist: String init (name : String , artist : String ) { self .artist = artist super .init (name: name) } } let library = [ Movie (name: "Casablanca" , director: "Michael Curtiz" ), Song (name: "Blue Suede Shoes" , artist: "Elvis Presley" ), Movie (name: "Citizen Kane" , director: "Orson Welles" ), Song (name: "The One And Only" , artist: "Chesney Hawkes" ), Song (name: "Never Gonna Give You Up" , artist: "Rick Astley" ) ] var movieCount = 0 var songCount = 0 for item in library { if item is Movie { movieCount += 1 } else if item is Song { songCount += 1 } } print ("Media library contains \(movieCount) movies and \(songCount) songs" )for item in library { if let movie = item as? Movie { print ("Movie: '\(movie.name) ', dir. \(movie.director) " ) } else if let song = item as? Song { print ("Song: '\(song.name) ', by \(song.artist) " ) } }
Any 和 AnyObject 的类型转换
Any
可以表示任何类型,包括函数类型。
AnyObject
可以表示任何类类型的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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) " }) for thing in things { switch thing { case 0 as Int : print ("zero as an Int" ) case 0 as Double : print ("zero as a Double" ) case let someInt as Int : print ("an integer value of \(someInt) " ) case let someDouble as Double where someDouble > 0 : print ("a positive double value of \(someDouble) " ) case is Double : print ("some other double value that I don't want to print" ) case let someString as String : print ("a string value of \" \(someString) \" " ) case let (x, y) as (Double , Double ): print ("an (x, y) point at \(x) , \(y) " ) case let movie as Movie : print ("a movie called '\(movie.name) ', dir. \(movie.director) " ) case let stringConverter as (String ) -> String : print (stringConverter("Michael" )) default : print ("something else" ) } }
内存安全 一般情况下一块内存空间访问是在瞬时间完成的,这个时间点上只可能有一个操作行为。如果在这个时间点上发生了多个操作,则会导致内存冲突。这种情况是不安全的内存行为。
内存访问冲突有三类型:
两个操作同时访问,并且又一个访问是写操作。
当两个访问是存储型内存时。
两个访问在时间上有重叠时。
最容易导致访问冲突语法场景是:In-Out 参数的访问冲突,方法里 self 的访问冲突,属性的访问冲突。
In-Out 参数的访问冲突 函数内部可以通过参数和全局变量访问同一块内存。很容易造成访问冲突。
1 2 3 4 5 6 7 8 var stepSize = 1 func increment (_ number : inout Int ) { number += stepSize } increment(& stepSize)
上面例子中,number
和stepSize
访问了同一块内存。
方法里 self 的访问冲突 在方法内访问自身就可能造成内存冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct Player { var name: String var health: Int var energy: Int static let maxHealth = 10 mutating func restoreHealth () { health = Player .maxHealth } } extension Player { mutating func shareHealth (with teammate : inout Player ) { balance(& teammate.health, & health) } }
如果传递给 shareHealth
参数实例和调用shareHealth
方法的实例不是同一个是正常的。
1 2 3 4 5 6 var oscar = Player (name: "Oscar" , health: 10 , energy: 10 )var maria = Player (name: "Maria" , health: 5 , energy: 10 )oscar.shareHealth(with: & maria) oscar.shareHealth(with: & oscar)
假如传递给 shareHealth
参数实例和调用shareHealth
方法的实例是同一实例,则会导致内存冲突。
属性的访问冲突 结构体,元组,枚举类型都是独立的值类型,对它们一个 属性的访问其实就是对整体的访问。这样情况很容易导致内存冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct Player { var name: String var health: Int var energy: Int static let maxHealth = 10 mutating func restoreHealth () { health = Player .maxHealth } } var holly = Player (name: "Holly" , health: 10 , energy: 10 )balance(& holly.health, & holly.energy) var playerInformation = (health: 10 , energy: 20 )balance(& playerInformation.health, & playerInformation.energy) func someFunction () { var oscar = Player (name: "Oscar" , health: 10 , energy: 10 ) balance(& oscar.health, & oscar.energy) }