Swift学习笔记
根据《Swift编程权威指南》和 Swift官方文档 学习整理的Swift学习笔记,持续更新,中间有什么错误,欢迎大家留言讨论。
系列总目录
官方文档
一、类型、常量和变量
1、变量
使用 var
关键字声明个变量。
var num = 4
num += 2
2、常量
使用 let
关键字声明一个常量,常量只能有一次赋值。
let numL = 10
3、类型推断
可以在变量/常量名后使用“ : 类型 ”,定义类型。
let cityName: String = "潍坊"
4、数
4.1 整数
4.1.1 Int、UInt、Int32 的最大值与最小值
如果需要知道整数精确长度,可以使用Swift的显式长度整数类型,比如Int32
是Swift的32位有符号整型。对于8位、16位和64位有符号整型,相应的还有Int8
、Int16
、Int64
。
有符号整型和无符号整型的最小值和最大值之间有对应关系:UInt64的最大值是Int64的最大值与最小值绝对值之和。无论是有符号整型还是无符号整型,都有2的64次方种可能的值,但是有符号整型需要把一半可能的值留给负数用。
print("The maximum Int value is \(Int.max).")
print("The minimum Int value is \(Int.min).")
print("The maximum value for a 32-bit integer is \(Int32.max).")
print("The minimum value for a 32-bit integer is \(Int32.min).")
print("The maximum UInt value is \(UInt.max).")
print("The minimum UInt value is \(UInt.min).")
print("The maximum value for a 32-bit unsigned integer is \(UInt32.max).")
print("The minimum value for a 32-bit unsigned integer is \(UInt32.min).")
执行结果如下图所示:
4.1.2 创建整数实例
显式和隐式地声明Int
let numA:Int = 10
let numB = 3
显式声明其它整数类型
let numUInt: UInt = 40
let numInt32: Int32 = -1000
4.1.3 整数操作符
- 基本操作符:加(
+
)、减法(-
)、乘(*
)、除(/
)
print(50+30)
print(50-30)
print(50*30)
print(60/30)
- 操作符运算顺序,使用圆括号
print(10 + 2 * 5) // 20,因为 2 * 5 先计算
print(30 - 5 - 5) // 20,因为 30 - 5 先计算
print((10 + 2) * 5) // 60,因为(10 + 2)先计算
print(30 - (5 - 5)) // 30,因为(5 - 5)先计算
- 整数除法
print(11 / 3) // 打印3
Swift把小数部分截断后留下了3。整数除法总是向0舍入。
- 取余操作符
print(11 % 3) // 打印2
print(-11 % 3) // 打印-2
需要注意的是,取余运算符不ܹ太一样,用在负整数上的结果可能会出乎你的意料。
-
快捷操作符
+=
、-+
、*=
、/=
和%=
,每个都会把运算结果赋给操作符左边的值。
var x = 10
x += 10 // 等同于: x = x + 10
x -= 5 // 等同于: x = x - 5
注意:从swift3开始,去除了自增(++)和自减(–)操作符
- 溢出操作符
先看以下代码:
let y: Int8 = 120
let z = y + 10
Xcode报错,“执行中断”,是什么意思?下面来分解每一步发生了什么:
(1) y是Int8类型,所以编译器认为y + 10也必须是Int8,因而推断z是Int8;
(2) 运行后,Swift给y加10,得到130;
(3) 在把结果存入z 之前,Swift检查发现130对于Int8来说是非法值。
因此程序会触发陷阱,程序停止运行。Swift提供溢出操作符(overflow operator),它们在值ܹ太大(或ܹ太小)时的行为不同于普通操作作符,会绕过去而不是处罚陷阱。
下面是加法的溢出操作符&+
实例代码。
let y: Int8 = 120
let z = y + 10
let z = y &+ 10
print("120 &+ 10 is \(z)")
执行上述代码,将得到 -126。
因为y是Int8
类型的, 所以一旦达到到127就不能再往上加了。在加1之后不会得到128,而是折回到了-128。因此120 + 8 = -128,120 + 9 = -127,120 + 10 = -126。
减法和乘法也有ຼ溢出版本的操作符:&-
和&*
。减法很明显不会上溢,但是可能会下溢(underflow)。比如说,一个值为-120的Int8减去10会让结果ܹ太小,无法用Int8表示。用&-会让下溢折回到正数,得到126。
4.1.4 转换整数类型
swift并不支持类型的自动转换,需要手动转换类型使之匹配。
let a: Int16 = 200
let b: Int8 = 50
let c = a + b // 报错
let c = a + Int16(b) // 正确
4.2 浮点数
Swift有两种基本的浮点数类型:32位数Float
和64位数Double
。Float和Double 的长度差异并不像整数那样影响其最小值和最大值,而是影响其精度。Double的精度比Float高,这意味着它能存储更精确的近似值。在swift中,浮点数的默认类型推断是Double。
浮点数通常不准确,有很多数无法以浮点数的形式精确存储。计算机会存储一个近似值,非常接近你要的数。
4.2.1 声明浮点数
let d1 = 1.1 // 隐式Double声明
let d2: Double = 1.1 // 显式Double声明
let f1: Float = 100.3 // 显式Float声明
4.2.2 浮点数的操作与比较上的坑
print(10.0 + 11.4)
print(11.0 / 3.0)
if d1 == d2 {
print("d1 and d2 are the same!")
}
print("d1 + 0.1 is \(d1 + 0.1)")
if d1 + 0.1 == 1.2 {
print("d1 + 0.1 is equal to 1.2")
}
执行上述代码,会发现,d1 + 0.1的结果是1.2,但是第二个在if语句中的print()函数却没有运行。正如之前所说的那样,很多数(包括1.2)无法用浮点数精确表示。计算机会存储一个非常接近1.2的近似值,当你给1.1加0.1后,结果实际上是类似于1.200 000 000 000 000 1的值,而你输入字面量1.2后存储的值近似于1.199 999 999 999 999 9。尽管在打印的时候Swift会把两者都舍入为 1.2,但实际上说它们并不相等。
4.3 浮点数和整数的转换
整数和浮点数字类型之间的转换必须明确。
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double
浮点数到整数的转换也必须明确。整数类型可以使用Double
或Float
值初始化。
注意:以这种方式初始化新的整数值时,浮点值始终会被截断。
let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int
4.4 数字文字
- 定义其他进制整数
十进制数,无前缀
二进制数,用
0b
前缀八进制数,用
0o
前缀十六进制数,用
0x
前缀
let decimalInteger = 17 // 十进制
let binaryInteger = 0b10001 // 二进制
let octalInteger = 0o21 // 八进制
let hexadecimalInteger = 0x11 // 十六进制
- 浮点数
浮点文字可以是十进制(不带前缀)或十六进制(带0x
前缀)。它们的小数点两侧必须始终有一个数字(或十六进制数字)。小数浮点数也可以有一个可选的指数,用大写或小写表示e
; 十六进制浮点数必须具有指数,以大写或小写表示p
。
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
- 数字文字可以包含额外的格式
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
5、类型别名
类型别名为现有类型定义备用名称。使用typealias
关键字定义类型别名。
typealias AudioSample = UInt16
定义类型别名后,可以在任何可能使用原始名称的地方使用别名:
var a = AudioSample.min
var b :AudioSample = 2
6、布尔值
Swift具有一个基本的布尔类型,称为Bool
。布尔值被称为逻辑值,因为它们只能是true或false。Swift提供了两个布尔常量值,true
以及false
:
let b1 = true
let b2 = false
7、元组
详见 二、流程控制、2.7
8、字符串和字符
Swift的String
和Character
类型提供了一种快速,符合Unicode的方式来处理代码中的文本。
8.1 使用字符串
8.1.1 声明字符串
let letString = "Hello, playground" // 常量字符串
var varString = "Hello, mutable playground" // 可变字符串
varString += "!" // 给可变字符串添加内容
let quotation = """ // 多行字符串
这是一个多行
字符串
"""
转义的特殊字符
\0
(空字符),\\
(反斜杠),\t
(水平制表符),\n
(换行符),\r
(回车),\"
(双引号)和\'
(单引号)
8.1.2 使用Characters
组成Swift字符串的字符都是Character
类型。Swift的Character类型表示Unicode字符,组合起来形成String实例。
for c: Character in mutablePlayground.characters {
print(c)
}
8.1.3 连接字符串和字符
String
值可以与加法运算符(+)或者快捷运算符(+=)以创建新String
值:
let string1 = "hello"
let string2 = " swift"
var welcome = string1 + string2 // "hello swift"
var instruction = "apple"
instruction += string2 // "apple swift"
可以使用append()
方法连接字符串。
let exclamationMark: Character = "!"
welcome.append(exclamationMark) // "hello swift!"
不能将
String
或Character
加到现有Character
变量,因为一个Character
值只能包含一个字符。
8.1.4 拼接字符串
使用 \()
在字符串中拼接
let numL = 10
let cityName: String = "潍坊"
let cityDes = "\(cityName)是世界风筝之都,\(numL)"
8.2 Unicode
Unicode是字符编码的国际标准,目标是不用考虑其他平台即可无缝处理和表达字符。
8.2.1 Unicode标量
从内部实现来说,Swift字符串是由Unicode标量(Unicode scalar)组成的。Unicode标量是21位的数,表示Unicode标准中一个特定字符。
- 使用Unicode标量
let oneCoolDude = "\u{1F60E}"
\u{}
语法表示Unicode标量,十六进制数放在花括号中。在本例中,oneCoolDude被置为“戴墨镜”emoji的字符。
- 使用组合标量
let aAcute = "\u{0061}\u{0301}" // 使用组合标量 结果:á
Swift中的每个字符都是扩展字形簇(extended grapheme cluster)。扩展字形簇是人类可读的单个字符,由一个或多个Unicode标量组合而成。
- 显示字符串背后的Unicode标量
let playground = "Hello, playground"
for scalar in playground.unicodeScalars {
print(scalar)
}
执行上述代码,输出:72 101 108 108 111 44 32 112 108 97 121 103 114 111 117 110 100
控制台中的每个数对应一个字符串标量,表示字符串中的单个字符。但是这些数不是十六进制的 Unicode数,而是用无符号32位整数表示的。
8.2.2 标准等价
组合标量有其存在意义,不过Unicode也为某些常见字符提供了已经组合过的形式。
- 使用预组合标量
let aAcute = "\u{0061}\u{0301}" // 使用组合标量 结果:á
let aAcutePrecomposed = "\u{00E1}" //预组合标量 结果:á
let b = (aAcute == aAcutePrecomposed) // 真
aAcutePrecomposed看起来和aAcute的值一样。实际上,如果检查两个字符是否相等,会发现Swift确实认为它们相等。
标准等价是指两个Unicode标量序列在语言学层面是否相等。对于两个字符或者两个字符串,如果它们具备相同的语言学含义和外观,那么无论是否用相同的Unicode变量创建,都认为两者相等。
8.2.3 计算字符数量
使用characters的count
属性来判断两个字符串的字符数量。
print("aAcute: \(aAcute.characters.count);
aAcutePrecomposed: \(aAcutePrecomposed.characters.count)")
运行结果显示字符数量相等:都是1字符长。标准等价意味着无论是用组合标量还是预组合标量,结果都会被当成单个字符。
8.3 访问和修改字符串
8.3.1 索引和区间
可以通过字符串的方法和属性或使用下标语法来访问和修改字符串。
- 使用下标语法访问String特定索引处的Character。
let greeting = "Guten Tag!"
greeting[greeting.startIndex] // G
// 可以使用的index(before:)和index(after:)方法访问给定索引之前和之后的索引String
greeting[greeting.index(before: greeting.endIndex)] // !
greeting[greeting.index(after: greeting.startIndex)] // u
// 若要访问距离给定索引更远的索引,可以使用index(_:offsetBy:)方法
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a
如果访问超出字符串范围的Character将触发陷阱。
greeting[greeting.endIndex] // Error
- 使用
indices
属性可访问字符串中各个字符的所有索引。
for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n T a g ! "
-
获取区间
start…end的结果被称为String.CharacterView.Index类型的闭区间。
let start = greeting.startIndex
let end = greeting.index(start, offsetBy: 4)
let range = start...end
let firstFive = greeting[range] // "Guten"
8.3.2 插入和移除
- 要将单个字符插入指定索引处的字符串中,可以使用
insert(_:at:)
方法
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex) // "hello!"
注意和append方法的区别,append只能添加到字符串最后。
- 要从指定索引处的字符串中删除单个字符,使用
remove(at:)
方法 - 要在指定范围内删除子字符串,使用
removeSubrange(_:)
方法
var welcome = "hello swift!"
welcome.remove(at: welcome.index(before: welcome.endIndex)) // "hello swift"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range) // "hello"
8.4 子串
当从字符串中获取子字符串时(例如,使用下标或类似prefix(_:)
的方法),结果是实例Substring
,而不是另一个字符串。
Swift中的子字符串与字符串具有大多数相同的方法,这意味着可以像处理字符串一样使用子字符串。但是,在对字符串执行操作时,只能在短时间内使用子字符串。当您准备长时间存储结果时,可以将子字符串转换为的实例String
。
let greeting = "Hello, world!"
let start = greeting.startIndex
let end = greeting.index(start, offsetBy: 4)
let range = start...end
let beginning = greeting[range] // "hello"
let newString = String(beginning)
9、可空类型(可选项)与 nil
可空类型(optional)是Swift的独特特性,用来指定某个实例可能没有值。如果一个实例没有值,就称其为nil
。
可以在不存在值的情况下使用optional。可选的代表两种可能性:要么是有一个值,可以解开可选项并访问该值,或者就是没有。
9.1 可空类型示例
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
上述程序尝试将String
装换为Int
。但由于可能存在转换失败的情况(例如“aaa”就不能成功转换),因此它返回一个optional Int
而不是一个Int
。可选Int
内容写为Int?
而不是Int
。
这不是相同的类型(和kotlin空安全有本质区别)。问号表示它包含的值是可选的,这意味着它可能包含某个 Int
值,或者可能根本不包含任何值。
可选项是对其他类型的一层包装,可以理解为一个盒子
如果是
nil
,那么他是一个空盒子如果不为
nil
,那么盒子里装的是:被包装的类型的数据
9.2 可空实例和强制展开
- 声明可空类型
var errorCodeString: String? // 默认就是 nil
- 打印可空类型
print(errorCodeString)
errorCodeString = "404"
print(errorCodeString)
上述代码执行效果如下图所示:
可以看到为空时,将打印nil
,存在值时会打印 Optional(“404”)。
- if 语句和强制展开
var errorCodeString: String?
errorCodeString = "404"
if errorCodeString != nil {
let theError = errorCodeString!
print(theError)
}
这里增加了if
语句,如果errorCodeString不是nil
就会执行相应的代码。执行体内的“errorCodeString!”后面的 !
作用是强制展开。强制展开会访问可空实例封装的值。之所以称之为“强制”展开,是因为无论是否有值,都会访问封装的值。因此,强制展开具有一定的风险,可能会触发陷阱。
9.3 可选项绑定
可选项绑定(optional binding)是一种固定模式,对于判断可选项是否有值很有用。如果有值,就将其赋给一个临时常量或变量,并且使这个常量或变量在条件语句的第一个分支代码中可用。
格式如下:
if let
constantName
=someOptional
{
// 用constantName
做一些事情
}else {
//someOptional
没有值
}
- 可选项绑定(修改9.2 3代码)
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString {
print(theError)
}
上述代码和9.2 3代码执行效果相同,简化了写法,因此,不需要再强制展开可选项了。如果转换成功,那么这个操作已经自动完成了。
假设想把errorCodeString转换成相应的整数形式,可以用嵌套的if let
。
- 嵌套的可选项绑定
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString {
if let errorCodeInteger = Int(theError) {
print("\(theError): \(errorCodeInteger)")
}
}
在第二个绑定中,Int(theError)的结果被展开并赋给errorCodeInteger,使得整数值也可用了。
嵌套可选项绑定可能会显得错综复杂。不过,swift还支持单个if let绑定就可以展开多个可空实例。
- 展开多个可选项
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString, let errorCodeInteger = Int(theError) {
print("\(theError): \(errorCodeInteger)")
}
现在一行代码展开了两个可选项。
可选项绑定还能进行额外的检查。假设当错误码的值为404时,才处理,如下代码所示。
- 可选项绑定和额外的检查
var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString, let errorCodeInteger = Int(theError),
errorCodeInteger == 404 {
print("\(theError): \(errorCodeInteger)")
}
现在,如果errorCodeInteger等于404,条件语句就计算为真。只有当两个可空实例都成功展开时,才会执行最后一个子句(errorCodeInteger == 404)。
-
while循环中使用可空实例绑定
示例:遍历数组,将遇到的正数加起来,如果遇到负数或者非数字,停止遍历
var strs = ["10","20","abc","-10"]
var index = 0
while let num = Int(strs[index]), num > 0 {
sum += num
index += 1
}
print(sum)
9.4 隐式展开可空实例
隐式展开可空类型与普通可空类型类似,只是有一个重要的区别: 它们不需要展开。
确保可选项一直有值。
- 声明隐式可空类型
var errorCodeString: String!
errorCodeString = "404"
print(errorCodeString)
这里的可空类型是用 !
声明的,表示这是一个隐式展开可空实例。
不过要注意,这种强大和灵活性也伴随着一定的危险性:如果隐式展开可空实例没有值的话, 访问其值会导致运行时错误。因此,只要某个实例有可能是nil
,就别用隐式展开可空实例。
var errorCodeString: String! = nil
let anotherErrorCodeString: String = errorCodeString // 报错
let yetAnotherErrorCodeString = errorCodeString // 是可空类型还是隐式展开可空实例
上述代码中,第一个是会触发陷阱,因为anotherErrorCodeString显式声明了类型,不可能是可空实例。第二个,Swift会尽可能推断最安全的类型:普通的可空实例。
它˞主要的应用场景是类初始化。
9.5 可空链式调用
可选链式调用是一种可以在当前值可能为 nil 的可空类型上调用属性、方法及下标的方法。如果可选项有值,那么调用就会成功;如果可选项是 nil ,那么调用将返回 nil 。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 nil ,整个调用链都会失败,即返回 nil 。
通过在想调用的属性、方法、或下标的可选项后面放一个问号( ?
),可以定义一个可选链。
var errorCodeString: String?
errorCodeString = "404"
var errorDescription: String?
if let theError = errorCodeString, let errorCodeInteger = Int(theError),
errorCodeInteger == 404 {
errorDescription = "\(errorCodeInteger + 200): resource was not found."
}
var upCaseErrorDescription = errorDescription?.uppercased()
- 原地修改可空实例
upCaseErrorDescription?.append(" PLEASE TRY AGAIN.")
9.6 nil合并运算符 ??
a
??
ba 必须为可空实例;
b 可以为可选项 或者 不是可选项
a 和b 的存储类型必须相同
- 如果a不为
nil
,就返回a,否则返回b;如果b不是可选项,返回a是会自动解包
- 实例:分析 c 的类型和值
let a: Int? = 1
let b: Int? = 2
let c = a ?? b
// c 是 Int?,Optional(1)
let a: Int? = nil
let b: Int? = 2
let c = a ?? b
// c 是 Int?,Optional(2)
let a: Int? = nil
let b: Int? = nil
let c = a ?? b
// c 是 Int?, nil
let a: Int? = 1
let b: Int = 2
let c = a ?? b // 如果b不是可选项,返回a时会自动解包
// c 是 Int,1
let a: Int? = nil
let b: Int = 2
let c = a ?? b
// c 是 Int,2
- 多个
??
一起使用,从左往右运算
let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3
// c 是 Int,1
let a: Int? = nil
let b: Int? = 2
let c = a ?? b ?? 3
// c 是 Int,2
??
跟if let
配合使用
let a: Int? = nil
let b: Int? = 2
if let c = a ?? b {
print(c)
}
// 类似于if a != nil || b != nil
if let c = a, let d = b {
pritn(c)
print(d)
}
// 类似于if a != nil && b != nil