Swift学习笔记(一)

简述

Swift是一种支持多编程范式、安全、快速和互动的跨平台编译式编程语言。且编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。它支持代码预览(playgrounds),这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。

历史

  • 2010年7月,苹果开发者工具部门总监克里斯·拉特纳开始着手 Swift 编程语言的设计工作,以一年时间,完成基本架构后,他领导了一个设计团队大力参与其中。
  • 2014年6月发表, Swift大约历经4年的开发期。苹果宣称Swift的特点是:快速、现代、安全、互动,而且明显优于Objective-C语言。Swift以LLVM编译,可以使用现有的Cocoa和Cocoa Touch框架。Xcode Playgrounds功能是Swift为苹果开发工具带来的最大创新,该功能提供强大的互动效果,能让Swift源代码在撰写过程中能即时显示出其运行结果。拉特纳本人强调,Playgrounds很大程度是受到布雷特·维克多理念的启发。
  • 2015年6月8日,苹果于WWDC2015上宣布,Swift将开放源代码,包括编译器和标准库。
  • 2015年12月3日,苹果宣布开源swift,并支持Linux,苹果在新网站swift.org和托管网站Github上开源了swift,但苹果的app store并不支持开源的swift,只支持苹果官方的swift版本,官方版本会在新网站swift.org上定期与开源版本同步。

Swift历史版本

  • 2019-01-24 Swift 5.0 更新
  • 2018-09-17 Swift 4.2 更新
  • 2018-03-29 Swift 4.1 更新
  • 2017-12-04 Swift 4.0.3 更新
  • 2017-09-19 Swift 4.0 更新
  • 2017-03-27 Swift 3.1 更新
  • 2016-10-27 Swift 3.0.1 更新
  • 2016-09-13 Swift 3.0 更新
  • 2016-03-21 Swift 2.2 更新
  • 2015-10-20 Swift 2.1 更新
  • 2015-09-16 Swift 2.0 更新
  • 2015-4-8 Swift 1.2 更新
  • 2014-10-16 Swift 1.1 更新
  • 2014-08-18 Swift 1.0 更新

Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。

下面通过Swift与Objective-C/C对比来介绍这门语言。

基础部分

基本数值类型(numeric types)

Swift 包含了 C 和 Objective-C 上所有基础数据类型

Int 表示整型值; Double 和 Float 表示浮点型值; Bool 是布尔型值;String 是文本型数据。

Swift 还提供了三个基本的集合类型,Array、Set 和 Dictionary。类似OC中NSArray、NSMutableArray、NSSet、NSMutableSet、NSDictionary、NSMutableDictionary。

值类型(Value Type)&引用类型(Reference Type)

内存(RAM)中有两个区域,栈区(stack)和堆区(heap)。在 Swift 中,值类型,存放在栈区;引用类型,存放在堆区。

在 Swift 中,典型的有 struct,enum,以及 tuples(元组) 都是值类型。而平时使用的 Int, Double,Float,String,Array,Dictionary,Set 其实都是用结构体实现的,也是值类型。详见下面代码

在 Swift 中,class 和闭包是引用类型。

而在Objective-C中,除了基本数据类型、结构体等,其他都是引用类型。

*

注意

标准库定义的集合,例如数组,字典和字符串,都对复制进行了优化以降低性能成本。新集合不会立即复制,而是跟原集合共享同一份内存,共享同样的元素。在集合的某个副本要被修改前,才会复制它的元素。而你在代码中看起来就像是立即发生了复制。即Copy On Write。

1
2
3
public struct Int : FixedWidthInteger, SignedInteger {
...
}

元组(tuples)

元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。这在Objective-C中是没有的。

1
2
3
4
5
6
7
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 输出“The status code is 404”
print("The status message is \(statusMessage)")
// 输出“The status message is Not Found”

(404, “Not Found”) 元组把一个 Int 值和一个 String 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 (Int, String) 的元组”。 如果函数需要返回多个值,是一个不错的应用场景。

类型安全

Swift 是一门类型安全的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 String ,类型安全会阻止你不小心传入一个 Int 。而在OC中则不然。

常量&变量

var - 声明变量。
let - 声明常量。

类型标注

1
2
3
4
5
//swift
var welcomeMessage: String = "hello world!";
var welcomeMessage = "hello world!";
//Objective-C
NSString *welcomeMessage = @"hello world";

声明一个类型为 String ,名字为 welcomeMessage 的变量。

如果你在声明常量或者变量的时候赋了一个初始值,Swift 可以推断出这个常量或者变量的类型。

常量和变量的命名

1
2
3
4
var x = 0.0, y = 0.0;
let z = 0.0;
let 你好 = "hello";
let 🐶🐮 = "emoj-dog&cat";

上面例子可以看出,常量和变量名可以包含任何字符,包括 Unicode 字符。

输出常量和变量

1
2
3
4
5
//swift
// 输出“The current value of friendlyWelcome is hello world!”
print("The current value of welcomeMessage is \(welcomeMessage)")
//iOS
NSLog("The current value of welcomeMessage is %@", welcomeMessage);

可选类型 | nil | 强制解析

使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示两种可能: 或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。

注意

C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。

1
2
3
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"

因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。

nil:你可以给可选变量赋值为 nil 来表示它没有值:

1
2
3
4
5
6
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil

注意

nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。

Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。

可选绑定

1
2
3
4
5
6
7
//如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。
if let actualNumber = Int(possibleNumber) {
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 输出“'123' has an integer value of 123”

强制解析

有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。

1
2
3
4
5
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号

注意

如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。


基本运算符

这里只介绍OC中没有的运算符

区间运算符

1
2
3
4
5
6
7
8
//[a,b] a< b
(a...b)
//[a,b) a<b
(a..<b)

for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}

运算符重载(略)

字符串和字符

Swift 的 String 类型与 Foundation NSString 类进行了无缝桥接。Foundation 还对 String 进行扩展使其可以访问 NSString 类型中定义的方法。这意味着调用那些 NSString 的方法,你无需进行任何类型转换。

操作

增删改、下标访问等

1
2
3
4
5
6
7
8
9
10
11
//连接字符串
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome 现在等于 "hello there"

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"

编码方式

Objective-C(NSString)

NSString对象是用 UTF-16 编码的码元组成的数组。相应地,length 方法的返回值也是字符串包含的码元个数(而不是字符个数)。

Swift(String)

每一个字符串都是由编码无关的 Unicode 字符组成。Swift 中通过多种 “View” 来区分处理字符串的方式,UTF8View 让你以 UTF-8 编码的方式来处理字符串,而 CharacterView 则让你以字符为单位处理字符串而不必考虑编码的问题。

1
2
3
4
5
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 226 128 188 240 159 144 182

1
2
3
4
5
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// 68 111 103 8252 55357 56374

集合

Swift 语言中的 Arrays、Sets 和 Dictionaries 中存储的数据值类型必须明确,即被现实为泛型集合。而Objective-C没有这样的要求(只要求存储的为对象)。

数组(Arrays)

创建数组

1
2
3
4
5
6
//swift 
var shoppingList = [String]()
var shoppingList: [String] = ["Eggs", "Milk"]
//...其他方法
//OC 类似泛型的功能
NSMutableArray <NSString *> *shoppingList = [NSMutableArray arrayWithArray:@[@"Eggs", @"Milk"]];

修改数组

1
2
3
4
5
6
7
//添加元素
//swift
shoppingList.insert("Maple Syrup", at: 0)
shoppingList.append("Flour")
//OC
[shoppingList addObject:@"Flour"];
[shoppingList addObject:@"Flour" atIndex:0];
1
2
3
4
5
//添加数组
//swift
shoppingList += ["Chocolate Spread", "Cheese", "Butter"];
//OC
[shoppingList addObjectFromArray:["Chocolate Spread", "Cheese", "Butter"]];
1
2
3
4
5
6
//修改数组
//swift
shoppingList[0] = "Six eggs"
shoppingList[4...6] = ["Bananas", "Apples"]
//OC
shoppingList[0] = @"Six eggs"
1
2
3
4
5
//移除元素
//swift
shoppingList.remove(at:0);
//OC
[shoppingList removeObjectAtIndex:0];

数组遍历

如果我们同时需要每个数据项的值和索引值,可以使用 enumerated() 方法来进行数组遍历。enumerated() 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:

1
2
3
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}

集合(Sets)

Q:集合和数组的区别?

集合(Set)用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。

存储在集合中的类型,必须是可哈希化的。遵循协议Hashable。

集合操作

使用 intersection(:) 方法根据两个集合中都包含的值创建的一个新的集合。
使用 symmetricDifference(
:) 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
使用 union(:) 方法根据两个集合的值创建一个新的集合。
使用 subtracting(
:) 方法根据不在该集合中的值创建一个新的集合。

字典

Swift 的字典使用 Dictionary<Key, Value> 定义,其中 Key 是字典中键的数据类型,Value 是字典中对应于这些键所存储值的数据类型。

一个字典的 Key 类型必须遵循 Hashable 协议,就像 Set 的值类型。

Objective-C中NSDictionary的key必须是字符串。
Objective-C中NSDictionary对存储的Value类型一致性没有要求。但只能存储对象。

字典创建

1
2
3
4
5
//swift
var namesOfIntegers = [Int: String]()
// namesOfIntegers 是一个空的 [Int: String] 字典
//OC
NSDictionary *namesOfIntegers = [NSDictionary dictionary];

访问&修改字典

基本一致

字典遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
//swift
for (key, value) in namesOfIntegers {
print("\(key): \(value)")
}
//OC
for (key in namesOfIntegers.allKeys) {
//key is NSString object
...
}

for (value in namesOfIntegers.allValues) {
...
}

控制流

for-in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!

for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}

//忽略变量值
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 输出“3 to the power of 10 is 59049”

While

1
2
3
while condition {
statements
}

Repeat-While

1
2
3
repeat {
statements
} while condition

if条件语句

1
2
3
4
5
if condition <= 0 {
print("It's very cold. Consider wearing a scarf.")
} else {
print("It's not that cold. Wear a t-shirt.")
}

Switch

1
2
3
4
5
6
7
8
9
switch some value to consider {
case value 1:
respond to value 1
case value 2,
value 3:
respond to value 2 or 3
default:
otherwise, do something else
}

不存在隐式的贯穿

与 C 和 Objective-C 中的 switch 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 switch 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 break 语句。这使得 switch 语句更安全、更易用,也避免了漏写 break 语句导致多个语言被执行的错误。

注意 : 如果想要显式贯穿 case 分支,请使用 fallthrough 语句。

区间匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")

函数

Swift拥有比Objective-C更强大的函数功能。

  • 多重返回值
  • 默认参数值
  • 可变参数
  • 使用函数作为参数
  • 使用函数作为返回类型

函数定义

1
2
3
4
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}

函数调用

1
let greeting = greet(person: "Anna");

多重返回值 && 可选返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14

func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}

OC 可以通过字典返、结构体、Model回多重值。

*

函数返回一个包含两个 Int 值的元组,这些值被标记为 min 和 max ,以便查询函数的返回值时可以通过名字访问它们。

指定/忽略参数标签

可以在参数名称前指定它的参数标签,中间以空格分隔:

1
2
3
func someFunction(argumentLabel parameterName: Int) {
// 在函数体内,parameterName 代表参数值
}
1
2
3
4
5
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印“Hello Bill! Glad you could visit from Cupertino.”

OC 没有可比性

如果你不希望为某个参数添加一个标签,可以使用一个下划线(_)来代替一个明确的参数标签。

1
2
3
4
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(1, secondParameterName: 2)

默认参数

你可以在函数体中通过给参数赋值来为任意一个参数定义默认值(Deafult Value)。当默认值被定义后,调用这个函数时可以忽略这个参数。

1
2
3
4
5
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12

OC 不支持

可变参数

一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(…)的方式来定义可变参数。

1
2
3
4
5
6
7
8
9
10
11
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是这 3 个数的平均数。

OC 通过数组实现

函数类型作为参数类型

你可以用 (Int, Int) -> Int 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。

1
2
3
4
5
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印“Result: 8”

函数类型作为返回类型

1
2
3
4
5
6
7
8
9
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
1
2
3
var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero 现在指向 stepBackward() 函数。

函数功能和其他函数式编程差不多,比如JS。

嵌套函数

1
2
3
4
5
6
7
8
9
10
11
12
13
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!")

闭包

枚举

类和结构体

与其他编程语言所不同的是,Swift 并不要求你为自定义的结构体和类的接口与实现代码分别创建文件。

在iOS开发中,类和结构体差距还是蛮大的,我们经常使用结构体来封装一些属性来组成新的类型,简化运算。

而在Swift中,结构体(值类型)和类(引用类型)有很多共同点。

  • 定义属性用于存储值
  • 定义方法用于提供功能
  • 定义下标操作用于通过下标语法访问它们的值
  • 定义构造器用于设置初始值
  • 通过扩展以增加默认实现之外的功能
  • 遵循协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

定义语法

1
2
3
4
5
6
struct SomeStructure {
// 在这里定义结构体
}
class SomeClass {
// 在这里定义类
}

Swift命名注意

每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 UpperCamelCase 这种方式来命名类型(如这里的 SomeClass 和 SomeStructure),以便符合标准 Swift 类型的大写命名风格(如 String,Int 和 Bool)。请使用 lowerCamelCase 这种方式来命名属性和方法(如 framerate 和 incrementCount),以便和类型名区分。

实例化

1
2
let someClass = SomeClass()
let someStructure = SomeStructure()

属性访问

你可以通过使用点语法访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者以点号(.)分隔,不带空格,与iOS一致。

恒等运算符

判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的,Swift 提供了两个恒等运算符:

  • 相同(===)
  • 不相同(!==)

“==”表示两个实例的值“相等”或“等价”,判定时要遵照设计者定义的评判标准。

1
2
3
4
5
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}

属性

存储属性

一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。

1
2
3
4
5
6
7
8
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数 0,1,2
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数 6,7,8

计算属性

除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。

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
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.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”

只读计算属性

只有 getter 没有 setter 的计算属性叫只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。

iOS中通过readOnly修饰@property,来实现只读属性。

1
@property (nonatomic, readonly) NSString *varString;

注意

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

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("将 totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步

iOS通过KVO来实现属性观察。

类型属性

实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。

你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。

使用关键字 static 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父类的实现进行重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}
}

iOS中通过 class来修饰property。

1
@property (nonatomic, class) NSString *classString;

方法

实例方法(Instance Methods)

实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。

实例方法中修改值类型

结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。

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

1
2
3
4
5
6
7
8
9
10
11
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”

类型方法

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

注意

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

1
2
3
4
5
6
class SomeClass {
class func someTypeMethod() {
// 在这里实现类型方法
}
}
SomeClass.someTypeMethod()

继承

为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:

1
2
3
class SomeClass: SomeSuperclass {
// 这里是子类的定义
}

重写

子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫重写。

如果要重写某个特性,你需要在重写定义的前面加上 override 关键字。

重写方法

1
2
3
4
5
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}

重写属性

1
2
3
4
5
6
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}

重写属性观察器

1
2
3
4
5
6
7
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}

防止重写/继承

你可以通过把方法,属性或下标标记为 final 来防止它们被重写,只需要在声明关键字前加上 final 修饰符即可(例如:final var、final func、final class func 以及 final subscript)。

任何试图对带有 final 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final。

可以通过在关键字 class 前添加 final 修饰符(final class)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。

Extension 扩展

扩展可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即逆向建模)。扩展和 Objective-C 的分类很相似。

Swift 中的扩展可以:

  • 添加计算型实例属性和计算型类属性
  • 定义实例方法和类方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使已经存在的类型遵循(conform)一个协议

注意

扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能(与OC不同)。

扩展语法

1
2
3
extension SomeType {
// 在这里给 SomeType 添加新的功能
}

计算型属性

1
2
3
4
5
6
7
8
9
10
11
12
13
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.mm
print("One inch is \(oneInch) meters")
// 打印“One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印“Three feet is 0.914399970739201 meters”

注意

扩展可以添加新的计算属性,但是它们不能添加存储属性,或向现有的属性添加属性观察者。

构造器

扩展可以给现有的类型添加新的构造器。它使你可以把自定义类型作为参数来供其他类型的构造器使用,或者在类型的原始实现上添加额外的构造选项。

方法

扩展可以给现有类型添加新的实例方法和类方法。

在下面的例子中,给 Int 类型添加了一个新的实例方法叫做 repetitions:

1
2
3
4
5
6
7
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
1
2
3
4
5
6
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!

可变实例方法

通过扩展添加的实例方法同样也可以修改(或 mutating(改变))实例本身。结构体和枚举的方法,若是可以修改 self 或者它自己的属性,则必须将这个实例方法标记为 mutating,就像是改变了方法的原始实现。

下标

扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 Int 类型添加了一个整数类型的下标。下标 [n] 从数字右侧开始,返回小数点后的第 n 位:

123456789[0] 返回 9

123456789[1] 返回 8

……以此类推:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7

协议

协议 定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议。

除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。

Objective-C中协议只能定义公用的一套接口,但不能提供具体的实现方法。也就是说,它只告诉你要做什么,但具体怎么做,它不关心。

协议语法

协议的定义方式与类、结构体和枚举的定义非常相似:

1
2
3
protocol SomeProtocol {
// 这里是协议的定义部分
}

要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。遵循多个协议时,各协议之间用逗号(,)分隔:

1
2
3
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 这里是结构体的定义部分
}

若一个拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:

1
2
3
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是类的定义部分
}

属性要求

协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。

如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。

协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属性则用 { get } 来表示:

1
2
3
4
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}

在协议中定义类型属性时,总是使用 static 关键字作为前缀。当类类型遵循协议时,除了 static 关键字,还可以使用 class 关键字来声明类型属性:

1
2
3
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}

方法要求

协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法提供默认参数。

泛型

泛型是 Swift 最强大的特性之一,Swift 的 Array 和 Dictionary 都是泛型集合。你可以创建一个 Int 类型数组,也可创建一个 String 类型数组。

Objective-C中,泛型支持较弱。

1
2
3
4
//swift
var stringArray:[String] = ["Jack", "Rose"]
//oc
NSMutableArray <NSString *>*stringArray = [NSMutableArray new];

可解决的问题

Q:编写一个函数,实现交换输入的2个参数的值

我们编写一个函数用来交换两个 Int 值 swapTwoInts(_:_:):

1
2
3
4
5
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}

调用:

1
2
3
4
5
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”

swapTwoInts(_::) 函数很实用,但它只能作用于 Int 类型。如果你想交换两个 String 类型值,或者 Double 类型值,你必须编写对应的函数,类似下面 swapTwoStrings(::) 和 swapTwoDoubles(:_:) 函数:

1
2
3
4
5
6
7
8
9
10
11
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}

泛型函数

而泛型函数可适用于任意类型。我们命名新的函数为 swapTwoValues(_:_:):

1
2
3
4
5
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}

泛型版本的函数使用占位符类型名(这里叫做 T ),而不是 实际类型名(例如 Int、String 或 Double),占位符类型名并不关心 T 具体的类型,但它要求 a 和b 必须是相同的类型,T 的实际类型由每次调用 swapTwoValues(_:_:) 来决定。

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

swapTwoValues(_::) 函数现在可以像 swapTwoInts(::) 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(:_:) 函数被调用时,T 所代表的类型都会由传入的值的类型推断出来。

1
2
3
4
5
6
7
8
9
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107,anotherInt 现在是 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是“world”,anotherString 现在是“hello”

你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。

泛型类型

除了泛型函数,Swift 还允许自定义泛型类型。这些自定义类、结构体和枚举可以适用于任意类型,类似于 Array 和 Dictionary。

编写一个名为 Stack(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。

Int型栈:

1
2
3
4
5
6
7
8
9
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}

泛型支持:

1
2
3
4
5
6
7
8
9
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

Stack 基本上和 IntStack 相同,只是用占位类型参数 Element 代替了实际的 Int 类型。这个类型参数包裹在紧随结构体名的一对尖括号里()。

调用:

1
2
3
4
5
6
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串

泛型扩展

当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

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

类型约束

例如,Swift 的 Dictionary 类型对字典的键的类型做了些限制,字典键的类型必须是可哈希(hashable)的。

类型约束语法

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

findIndex 用于查找任何符合 Equatable 的类型是否存在数组中,若存在,返回其索引。

1
2
3
4
5
6
7
8
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}

关联类型

定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 associatedtype 关键字来指定。

下面例子定义了一个 Container 协议,该协议定义了一个关联类型 Item:

1
2
3
4
5
6
7
protocol Container {
//给关联类型添加约束
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

Container 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:

  • 必须可以通过 append(_:) 方法添加一个新元素到容器里。
  • 必须可以通过 count 属性获取容器中元素的数量,并返回一个 Int 值。
  • 必须可以通过索引值类型为 Int 的下标检索到容器中的每一个元素。

泛型 Where 语句

类型约束让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。

对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 where 子句来实现。通过泛型 where 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 where 关键字紧跟在类型参数列表后面来定义 where 子句,where 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {

// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}

// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}

// 所有元素都匹配,返回 true
return true
}

上面的例子定义了一个名为 allItemsMatch 的泛型函数,用来检查两个 Container 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 true,否则返回 false。

ARC

Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。

注意

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed")
//initialized

reference2 = reference1
reference3 = reference1

reference1 = nil
reference2 = nil

reference3 = nil
//dealloc

解决2个类间循环引用问题

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。

闭包循环引用

在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。

1
2
3
4
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}

访问控制

访问控制可以限定其它源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。

访问级别

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

  • Open 和 Public 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。Open 和 Public 的区别在后面会提到。
  • Internal 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。
  • File-private 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。
  • Private 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。

Open 为最高访问级别(限制最少),Private 为最低访问级别(限制最多)。

Open 只能作用于类和类的成员,它和 Public 的区别如下:

  • Public 或者其它更严访问级别的类,只能在其定义的模块内部被继承。
  • Public 或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。
  • Open 的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。
  • Open 的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。

把一个类标记为 open,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。

默认访问级别

如果你没有为代码中的实体显式指定访问级别,那么它们默认为 internal 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。