该部分以及后续章节的学习内容,是我依照官方文档(Swift5.1版本)学习后的心得体会,其中有不少与官方文档重叠的内容,也有我自己进行改动和精简的部分。该专栏的目的是为了向广大Swift爱好者提供一个接触入门的途径,也是给我自己提供一个小型知识库,方便开发过程中的技术查询。由于本人知识储备的局限性,文中难免出现错误或偏差,希望大家可以理解并帮助我改正问题。非常感谢!
有兴趣的童鞋可以多学习一下官方文档及相关学习资源:
The Swift Programming Language
The Swift Programming Language In Chinese
Swift Development Resources
Start Developing iOS Apps
Using Swift with Cocoa and Objective-C(中文版 by @CocoChnia)
@Swift 学习指引
Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
Swift 包含了 C 和 Objective-C 上所有基础数据类型,Int 表示整型值; Double 和 Float 表示浮点型值; Bool 是布尔型值;String 是文本型数据。 Swift 还提供了三个基本的集合类型,Array、Set 和 Dictionary ,详见 集合类型。
就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。
Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 x ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 nil ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 nil 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
Swift 是一门类型安全的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 String ,类型安全会阻止你不小心传入一个 Int 。同样的,如果你的代码需要一个 String,类型安全会阻止你意外传入一个可选的 String 。类型安全可以帮助你在开发阶段尽早发现并修正错误。
常量和变量把一个名字(比如 welcomeMessage)和一个指定类型的值(比如字符串 “Hello” )关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。
常量和变量必须在使用前声明,用 let 关键字 声明常量,用 var 关键字声明变量。
let maximumNumber = 10
var currentNumber = 0
可以在一行中声明多个常量或变量,用逗号隔开
var x = 0.0, y = 0.0, z = 0.0
注意:
如果代码中有不需要改变的值,请使用 let 关键字将它声明为常量。只将需要改变的值声明为变量。
声明常量或变量的时候可以加上类型标注(type annotation),说明常量或变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
var name: String = "Tom" // 变量 name 要存储的值的类型是 String 类型
var red, green, blue: Double // 在一行中定义多个同类型的变量
一般来说很少需要写类型标注,如果在声明常量或变量的时候赋了一个初值,Swift可以推断这个常量或变量的类型(类型安全和类型推断)。没有赋初值的话,通过类型标注可以指定常量或变量的类型。
不能以数字开头、不能包含数学符号、箭头、保留的或非法的Unicode码位、连线与制表符;
不能重复声明相同名字的常量或变量,或改变其存储的值的类型,不能将常量与变量进行互转;
如果需要使用Swift保留关键字的名称作为常量或变量名,可以使用反引号 ` 将关键字包围。
let ?? = "dogcow"
name = "Jim"
使用print(:separator:terminator:)函数来输出当前常量或变量的值,print(:separator:terminator:) 是一个用来输出一个或多个值到适当输出区的全局函数。
print(name) // 打印 "Jim"
Swift 用字符串插值的方式把常量名或变量名当做占位符加入到长字符串中,并用当前常量或变量的值替换这些占位符。
print("My name is \(name)") // 打印 "My name is Jim"
可以把代码中的非执行文本注释成提示或者笔记以方便后期阅读,编译器会在编译代码时自动忽略掉注释部分。
// 单行注释
/* 多行注释,
多行注释 */
/* 第一个多行注释开头
/* 被嵌套的多行注释 */
第一个多行注释结尾 */
Swift并不强制每条语句的结尾处使用分( ; ),但是同一行内写多条独立语句必须要用分号。
let cat = "?"; print(cat)
整数就是没有小数部分的数字,整数可以是有符号(正、负、零)或无符号(正、零),Swift提供了 8,16,32,64位的有符号和无符号整数类型。
整数范围:可以访问不同整数类型的min和max属性来获取对应类型的最小值和最大值。
let minValue = UInt8.min
let maxValue = UInt8.max
一般来说,不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 Int,长度与当前平台的原生字长相同:
在32位平台上,Int 和 Int32 长度相同。
在64位平台上,Int 和 Int64 长度相同。
Swift 也提供了一个特殊的无符号类型 UInt,长度与当前平台的原生字长相同:
在32位平台上,UInt 和 UInt32 长度相同。
在64位平台上,UInt 和 UInt64 长度相同。
注意:
尽量不要使用 UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 Int,即使你要存储的值已知是非负的。统一使用 Int 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断。
浮点数是有小数部分的数字,比如 3.14159、0.1 和 -273.15。浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:Double 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。Float 表示32位浮点数。精度要求不高的话可以使用此类型。
注意:
Double 精确度很高,至少有15位数字,而 Float 只有6位数字。
选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 Double。
Swift是一个类型安全的语言,可以清楚的知道代码要处理的值的类型。编译代码时会进行类型检查,不匹配的类型会被标记为错误。如果没有显示指定类型,Swift会使用类型推断来选择合适的类型。只要在声明常量或变量时赋给它们一个字面量,编译器就可以在编译代码时自动推断出表达式的类型。
let count = 20 // count 会被推测为 Int 类型
let pi = 3.1415926 // pi 会被推测为 Double 类型
let anotherPi = 3 + 0.1415926 // 表达式同时出现整数和浮点数,会被推断为 Double 类型
let decimalInteger = 17 // 十进制的17
let binaryInteger = 0b10001 // 八进制的17
let octalInteger = 0o21 // 八进制的17
let hexadecimalInteger = 0x11 // 十六进制的17
十进制浮点数可以有一个可选的指数,通过大写或者小写的 e 来指定;
十六进制浮点数 必须 有一个指数,通过大写或者小写的 p 来指定。
1.25e2 表示 1.25×10^2,等于125.0
1.25e-2表示 1.25×10^-2,等于0.0125
0xFp2 表示 15×2^2,等于60.0
0xFp-2 表示 15×2^-2,等于3.75
数值型字面量可以包括额外的格式来增强可读性,整数和浮点数都可以添加额外的零或下划线,并不会影响字面量。
let oneHundred = 000100
let oneMillion = 1_000_000
不同整数类型的变量和常量可以存储不同范围的数字,如果数字超出常量或变量可存储的范围,编译时会报错。
let toobig: Int8 = Int8.max + 1 // 报错溢出最大存储范围
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。
在下面的例子中,常量 twoThousand 是 UInt16 类型,然而常量 one 是 UInt8 类型。它们不能直接相加,因为它们类型不同。所以要调用 UInt16(one) 来创建一个新的 UInt16 数字并用 one 的值来初始化,然后使用这个新数字来计算:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
整数和浮点数转换必须显示指定类型
let three = 3
let pointOne = 0.1
let threeAndPointOne = Double(three) + pointOne;
let integerThree = Int(threeAndPointOne) // 浮点值会直接被截断,例如:4.75会变成4,-3.9会变成-3
类型别名就是给现有类型定义另一个名字。
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
布尔值指逻辑上的值,只能是真或假,Swift有两个布尔常量 true 和 false。
let orangesAreOrange = true
let turnipsAreDelicious = false
当你编写条件语句比如 if 语句的时候,布尔值非常有用:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
元组把多个值组合成一个复合值,元组内的值可以是任意类型。
作为函数返回值,在临时组织值时,元组非常有用,但并不适合创建复杂的数据结构。
let http404Error = (404, "Not Found") // 这个元组可以被描述为“一个类型为 (Int, String) 的元组”
可以将一个元组的内容分解成单独的常量或变量。
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
只需要一部分元组值时可以把要忽略的部分用下划线 _ 标记。
let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
可以通过下标访问元组中的单个元素。
print("The status code is \(http404Error.0)")
print("The status message is \(http404Error.1)")
可以在定义元组的时候给单个元素命名。
let http200Status = (statusCode: 200, description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is \(http200Status.statusCode)")
Swift 中使用可选类型来处理值可能缺失的情况,可选类型表示两种可能:有值,等于 x 或者 没有值。
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 nil,nil 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数,例如 “hello, world”。
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
convertedNumber被推测为类型 “Int?”,或者类型 “optional Int”。因为该构造器可能会失败,所以它返回一个可选类型Int,而不是一个Int。一个可选的Int被写作 Int? 而不是 Int,问号暗示包含的值是可选类型,也就是可能包含 Int 值也可能不包含。但不能包含其他任何值比如 Bool 或 String,只能是 Int 或者什么都没有。
可以给可选变量赋值为 nil 来表示它没有值。
var serverResponseCode: Int? = 404 // serverResponseCode 包含一个可选的 Int值 404
serverResponseCode = nil // serverResponseCode 现在不包含值
nil 不能用于非可选的常量和变量,如果有常量或变量需要处理值缺失的情况,须把它们声明成对应的可选类型。如果声明一个可选常量或变量但是没有赋值,它们会自动被设置为nil。
var surveyAnswer: String? // surveyAnswer 被自动设置为nil
注意:
Swift 的 nil 和 Objective-C 中的 nil 并不一样,在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针,它是一个确定的值,用来表示值缺失,任何类型的可选状态都可以被设置为 nil,不只是对象类型。
可以使用 if 语句和 nil 比较来判断一个可选值是否包含值,可以使用“相等(==)”或“不等(!=)”来执行比较。
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
当确定可选类型确实包含值之后,可以在可选的后边加一个感叹号( ! )来获取值,这个感叹号表示知道这个可选有值,请使用它,这被称为可选值的强制解析。
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
注意:使用 ! 来获取一个不存在的可选值会导致运行时错误,使用 ! 强制解析之前,一定要确定可选包含一个非 nil 的值。
使用可选绑定来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或变量。可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或变量。
if let actualNumber = Int(possibleNumber) {
print("possibleNumber has an integer value of \(actualNumber)")
} else {
print("possibleNumber could not be converted to an integer")
}
这段代码可以被理解为:
如果 Int(possibleNumber) 返回的可选 Int 包含一个值,创建一个叫做 actualNumber 的新常量并将可选包含的值赋给它。如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用,它已经被可选类型包含的值初始化过,所以不需要再使用 ! 后缀来获取它的值。在这个例子中,actualNumber 只被用来输出转换结果。可以在可选绑定中使用常量和变量,想在if语句的第一个分支中操作 actualNumber 的值,可以改成 if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。
可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要任意一个可选绑定的值为 nil ,或任意一个布尔条件为 false,则整个 if 条件判断为 false。
if let firstNumber = Int("4"), let secondNumber = Int("40"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) < \(secondNumber) < 100")
}
有时候在程序架构中,第一次被赋值后,可以确定一个可选类型总会有值。这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。这种类型的可选状态被定义为隐式解析可选类型,把想要用作可选的类型的后面的问号改成感叹号来声明一个隐式解析可选类型。
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在Swift中类的构造过程中。一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型使用,并不需要每次都使用解析来获取可选值。
let possibleString: String? = "An optional string"
let forcedString: String = possibleString! // 需要感叹号来取值
let assumedString: String! = "An implicitly unwrapped optional string"
let implicitlyString: String = assumedString // 不需要感叹号
可以把隐式解析可选类型当做一个可以自动解析的可选类型。如果在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误,和普通可选类型一样。
你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值。
if assumedString != nil {
print(assumedString as Any)
}
也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值。
if let definiteString = assumedString {
print(definiteString)
}
注意:
如果一个变量之后可能变成 nil 的话请不要使用隐式解析可选类型。
如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选类型。
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传到程序其他部分。当一个函数遇到错误条件,它能报错,调用该函数的地方能抛出错误消息并合并处理。
一个函数可以通过在声明中添加 throws 关键词来抛出错误消息:
func canThrowAnError() throws {}
当函数能抛出错误消息时,应该在表达式do中前置 try 关键词:
do {
try canThrowAnError()
// 没有错误消息抛出
} catch {
// 有错误消息抛出
}
一个 do 语句创建了一个新的包含作用域,使得错误能被传到一个或多个 catch 从句。
断言和先决条件是在运行时所做的检查,用来检查在执行后续代码之前,是否一个必要条件已经被满足。如果断言或先决条件中的布尔条件评估结果为true,则代码正常继续执行;如果布尔条件评估结果为false,程序的当前状态是无效的,则代码执行结束,应用程序终止。
断言帮你在开发阶段找到错误和不正确的假设,先决条件帮你在生产环境中探测到存在的问题。
断言和先决条件与错误处理不同,它们并不是用来处理可以恢复或可预期的错误。断言仅在调试环境中运行,而先决条件在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估,因此可以使用很多断言在开发阶段。
可以调用Swift标准库的 assert(::file:line:) 函数来写断言,向这个函数传入一个结果为 true 或 false 的表达式。
let age = -3
assert(age >= 0, "A person's age can't be less than zero") // 因为 age < 0,所以断言会触发
只有 age >= 0 为 true 时,代码才会继续执行,为 false 时,断言被触发,终止应用。
如果不需要断言信息,可以忽略掉:
assert(age >= 0)
如果代码已经检查了条件,可以使用 assertionFailure(_:file:line:) 函数来表明断言失败了:
if age > 10 {
print("You can ride the roller-coaster or the ferris wheel")
} else if age > 0 {
print("You can ride the ferris wheel")
} else {
assertionFailure("A person's age can't be less than zero")
}
当一个条件可能为 false,但是继续执行代码要求条件必须为 true 时,需要使用先决条件。可以使用先决条件来检查下标是否越界或传递给函数的参数传递是否正确,可以使用全局 precondition(::file:line:) 函数来写一个先决条件:
precondition(age >= 0, "Age must be greater than zero")
可以调用 precondition(::file:line:) 方法来表明出现了一个错误。例如:switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 defaultf 分支)处理。
注意:
如果使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查,编译器会假设所有先决条件总是为 true,它将优化你的代码。
但是,无论怎么进行优化设定,fatalError(:file:line:) 函数总是中断执行。
fatalError(:file:line:) 函数可以使用在设计原型和早期开发阶段,这个阶段只有方法的声明,没有具体实现。
可以在方法体中写上 fatalError(“Unimplemented”) 作为具体实现,因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。