1.前言:
Swift
包含了 C
和 Objective-C
上所有基础数据类型,Int
表示整型值; Double
和 Float
表示浮点型值; Bool
是布尔型值;String
是文本型数据。 Swift
还提供了三个基本的集合类型,Array
,Set
和 Dictionary
。
在 Swift
中,广泛的使用着值不可变的变量,它们就是常量。而且比 C
语言的常量更强大。在 Swift
中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
Swift
是一门类型安全
的语言,可选类型
就是一个很好的例子。
2.字符串插值
Swift
用字符串插值(string interpolation
)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift
会用当前常量或变量的值替换这些占位符。
var friendlyWelcome = "朋友你好!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!
-
类型推断
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
如果possibleNumber
是"asdas,那么convertedNumber
就是nil
4.nil
你可以给可选变量赋值为nil
来表示它没有值:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
Swift
的 nil
和 Objective-C
中的 nil
并不一样。在 Objective-C
中,nil
是一个指向不存在对象的指针。在 Swift
中,nil
不是指针——它是一个确定的值,用来表示值缺失
。任何类型的可选状态都可以被设置为 nil
,不只是对象类型。
5.隐式解析可选类型
把想要用作可选的类型的后面的问号(String?
)改成感叹号(String!
)来声明一个隐式解析可选类型。
可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
6.错误处理
你可以使用 错误处理(error handling
) 来应对程序执行中可能会遇到的错误条件。
1)函数throws
7.集合类型
Swift
语言提供Arrays
、Sets
和Dictionaries
三种基本的集合类型用来存储集合数据。数组(Arrays
)是有序数据的集。集合(Sets
)是无序无重复数据的集。字典(Dictionaries
)是无序的键值对的集。
Swift
的Arrays
、Sets
和Dictionaries
类型被实现为泛型
集合。
在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift
编译器可以优化我们创建的集合。 Swift
的Array
类型被桥接到Foundation
中的NSArray
类
1)数组
通过两个数组相加创建一个数组
var threeDoubles = [Double](repeating: 0.0, count: 3)
var anotherThreeDoubles = [Double](repeatElement(0.125, count: 3))
var sixDoubles = threeDoubles + anotherThreeDoubles
可以使用数组的只读属性count
来获取数组中的数据项数量:
2)集合(Sets)
集合(Set
)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要
时或者希望确保每个元素只出现一次
时可以使用集合而不是数组。
Swift
的Set
类型被桥接到Foundation
中的NSSet
类。
集合类型的哈希值
一个类型为了存储在集合
中,该类型必须是可哈希化
的
一个类型为了存储在集合中,该类型必须是可哈希化
的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是Int
类型的,相等的对象哈希值必须相同,比如a==b
,因此必须a.hashValue == b.hashValue
。
Swift
的所有基本类型(比如String,Int,Double和Bool
)默认都是可哈希化
的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在枚举有讲述)默认也是可哈希化的。
Swift
中的Set
类型被写为Set<Element>
,这里的Element
表示Set
中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
*注意:集合要联想起我们高中数学的集合。
使用intersect(_:)
方法根据两个集合中都包含的值创建的一个新的集合。
使用exclusiveOr(_:)
方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
使用union(_:)
方法根据两个集合的值创建一个新的集合。
使用subtract(_:)
方法根据不在该集合中的值创建一个新的集合。
3)字典
Swift
的Dictionary
类型被桥接到Foundation
的NSDictionary
类。
字典是一种存储多个相同类型的值的容器。每个值(value
)都关联唯一的键(key
),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。
Swift
的字典使用Dictionary<Key, Value>
定义,其中Key
是字典中键的数据类型,Value
是字典中对应于这些键所存储值的数据类型。
创建一个空字典
var namesOfIntegers = [Int: String]()
// namesOfIntegers 是一个空的 [Int: String] 字典
8.控制流
提前退出
像if
语句一样,guard
的执行取决于一个表达式的布尔值。我们可以使用guard
语句来要求条件必须为真时,以执行guard
语句后的代码。不同于if
语句,一个guard
语句总是有一个else
从句,如果条件不为真则执行else
从句中的代码。
9.检测 API 可用性
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
10.函数
参数标签 (Specifying Argument Labels
)
可以在函数名称前指定它的参数标签,中间以空格分隔:
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
*注意:这里的from 就是参数hometown的标签
参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。
忽略参数标签(Omitting Argument Labels
)
如果不希望为某个参数添加一个标签,可以使用一个下划线(_
)来代替一个明确的参数标签。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
可变参数 (Variadic Parameters
)
一个可变参数(variadic parameter
)可以接受零个或多个值。通过在变量类型名后面加入(...
)的方式来定义可变参数
一个函数最多只能拥有一个可变参数。
输入输出参数(In-Out Parameters
)
函数参数默认是常量。
只能传递变量给输入输出参数。你不能传入常量
或者字面量
(literal value
),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 &
符,表示这个值可以被函数修改。
eg:
函数类型 (Function Types
)
每个函数都有种特定的函数类型,函数的类型由函数的参数类型
和返回类型
组成。
func eschop(_ a: Strinfg, _b: Int) -> String {
// func body
}
函数类型为:(String, Int) -> String
嵌套函数
可以把函数定义在别的函数体中,称作 嵌套函数(nested functions
)
默认情况下,嵌套函数
是对外界不可见的,但是可以被它们的外围函数
(enclosing function
)调用。
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!
11.闭包(Closures
)
闭包
是自包含
的函数代码块
,可以在代码中被传递
和使用
。Swift
中的闭包
与 C
和 Objective-C
中的代码块(blocks
)以及其他一些编程语言中的匿名函数
比较相似。
闭包
可以捕获和存储其所在上下文
中任意常量
和变量
的引用。闭合
、包裹
常量和变量,所谓闭包
也。Swift
会为你管理在捕获过程中涉及到的所有内存操作。
捕获(capturing):闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
全局和嵌套函数实际上也是特殊的闭包:
闭包采取如下三种形式之一:
·全局函数是一个有名字但不会捕获
任何值的闭包
·嵌套函数是一个有名字并可以捕获
其封闭函数域内值的闭包
·闭包表达式是一个利用轻量级语法所写的可以捕获
其上下文中变量或常量值的匿名闭包
如果你将闭包
赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获
了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表
来打破这种循环强引用。
Swift
的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
1)利用上下文推断参数和返回值类型
2)隐式返回单表达式闭包,即单表达式闭包可以省略 return
关键字
3)参数名称缩写
4)尾随(Trailing
)闭包语法
闭包表达式(Closure Expressions
)
嵌套函数是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。
当然,有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时。
闭包表达式
是一种利用简洁语法构建内联闭包的方式。
闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。
sorted(by:)
方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true
,反之返回false
。
利用闭包表达式语法可以更好地构造一个内联排序闭包
。
闭包表达式语法(Closure Expression Syntax
)
闭包表达式语法有如下的一般形式:
闭包表达式的参数可以是inout
参数,但不能设定默认值。
也可以使用具名的可变参数.元组也可以作为参数和返回值。
内联闭包参数和返回值类型声明与 backward(::) 函数类型声明相同。在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
根据上下文推断类型(Inferring Type From Context
)
通过内联闭包表达式
构造的闭包
作为参数传递给函数或方法时,总是能够推断出闭包
的参数和返回值类型。
单表达式闭包隐式返回(Implicit Returns From Single-Expression Closures
)
单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
参数名称缩写(Shorthand Argument Names
)
reversedNames = names.sorted(by: { $0 > $1 } )
在这个例子中,$0和$1表示闭包中第一个和第二个 String 类型的参数。
运算符方法(Operator Methods
)
Swift
的 String
类型定义了关于大于号(>
)的字符串实现,其作为一个函数接受两个 String
类型的参数并返回 Bool
类型的值。而这正好与 sorted(by:)
方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift
可以自动推断出你想使用大于号的字符串函数实现:
reversedNames = names.sorted(by: >)
尾随闭包(Trailing Closures
)
如果你需要将一个很长的闭包表达式
作为最后一个参数传递给函数,可以使用尾随闭包
来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
reversedNames = names.sorted() { $0 > $1 } // 方法参数的字符串排序闭包可以改写为:
reversedNames = names.sorted { $0 > $1 } // 如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。
举例来说,Swift
的 Array
类型有一个 map(_:)
方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
闭包是引用类型(Closures Are Reference Types)
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。
逃逸闭包(Escaping Closures)
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸
。
当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping
,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”
出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为completion handler
。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:)
函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 @escaping
,就会得到一个编译错误。
自动闭包(Autoclosures)
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
12.枚举
可以定义 Swift
枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions
),标签联合(tagged unions
),或者变体(variants
)相似。
原始值(Raw Values)
在关联值小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值
(称为原始值
)预填充,这些原始值的类型必须相同。
原始值的隐式赋值(Implicitly Assigned Raw Values)
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
enum CompassPoint: String {
case north, south, east, west
}
上面例子中,CompassPoint.south
拥有隐式原始值south
,依次类推。
使用原始值初始化枚举实例(Initializing from a Raw Value)
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 类型为 Planet? 值为 Planet.uranus
13.类和结构体
类和结构体是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。
Swift
中类和结构体有很多共同点。共同处在于:
定义属性用于存储值
定义方法用于提供功能
定义下标操作使得可以通过下标语法来访问实例所包含的值
定义构造器用于生成初始化值
通过扩展以增加默认实现的功能
实现协议以提供某种标准功能
每次定义一个新类或者结构体的时候,实际上你是定义了一个新的Swift
类型。
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
在以上示例中,声明了一个名为hd
的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution
实例。
然后示例中又声明了一个名为cinema
的变量,并将hd赋值给它。因为Resolution
是一个结构体,所以cinema
的值其实是hd
的一个拷贝副本,而不是hd
本身。尽管hd
和cinema
有着相同的宽(width
)和高(height
),但是在幕后它们是两个完全不同的实例。
类是引用类型
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因为类是引用类型,所以tenEight
和alsoTenEight
实际上引用的是相同的VideoMode
实例。换句话说,它们是同一个实例的两种叫法。
恒等运算符
因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。
如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift
内建了两个恒等运算符:
等价于(===
)
不等价于(!==
)
指针,使用指针来引用内存中的地址
一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(*)来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。
在所有其它案例中,定义一个类,生成一个它的实例,并通过引用来管理和传递。实际中,这意味着绝大部分的自定义数据构造都应该是类,而非结构体。
字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为
Swift
中,许多基本类型,诸如String
,Array
和Dictionary
类型均以结构体
的形式实现。这意味着被赋值给新的常量
或变量
,或者被传入函数
或方法中时,它们的值会被拷贝
。
Objective-C
中NSString
,NSArray
和NSDictionary
类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来似乎总会发生。然而,Swift
在幕后只在绝对必要时才执行实际的拷贝。Swift
管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。
14.属性
属性将值跟特定的类
、结构
或枚举关联
。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类
和结构体
。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
存储属性
常量结构体的存储属性
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy
来标示一个延迟存储属性。
存储属性和实例变量
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储。
便捷 setter
声明
全局变量和局部变量
计算属性和属性观察器所描述的功能也可以用于全局变量和局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
全局的常量或变量都是延迟计算的,跟延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记lazy
修饰符。
局部范围的常量或变量从不延迟计算。
类型属性
类型属性语法
15.方法(Methods)
方法是与某些特定类型相关联的函数。
类、结构体、枚举都可以定义实例方法。
实例方法为给定类型的实例封装了具体的任务与功能。
类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C
中的类方法(class methods
)相似。
修改方法的外部参数名称(Modifying External Parameter Name Behavior for Methods)
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(_
)作为该参数的显式外部名称,这样做将覆盖默认行为。
self 属性
类型的每一个实例都有一个隐含属性叫做self,self完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例。
在实例方法中修改值类型
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
类型方法 (Type Methods)
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做类型方法(Type Methods)。在方法的func
关键字之前加上关键字static
,来指定类型方法。类还可以用关键字class
来允许子类重写父类的方法实现。
在 Objective-C
中,你只能为 Objective-C
的类类型(classes
)定义类型方法(type-level methods
)。在 Swift
中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含
。
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
在类型方法的方法体(body)中,self
指向这个类型本身,而不是类型的某个实例。这意味着你可以用self
来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。