原文链接:http://www.ioswift.org/
4.0.Swift指南
以上章节主要从整体上介绍了 Swift 的相关知识,从本章开始,我们一步一步学习 Swift ,正式开启 Swift 学习之旅...
5.基础
Swift 是一种新的编程语言,主要用于IOS 和 Mac OS X 应用程序的开发。尽管如此,Swift 的许多部分和 C 和 Objective - C 语言类似。
Swift 提供自己的语言类型版本,所有的都是基于 C 和 Objective - C 的基本类型,包括 Int、Double 、 Float 、Bool、String 。Swift还提供了强大的两个集合类型版本:Array 和 Dictionary 。
像 C 语言一样,Swift 通过变量来存储和通过明确的变量名来关联值。Swift 还大量的变量的值不能被改变,这些被称为常量,而且比 C 语言更强大。在 Swift 中,常量的使用会使代码更安全、所要表达的意图更清晰。
除了熟悉的类型,Swift 引入了在 Objective - C中没有的更先进的类型。比如元组,通过元组,您能够创建和传递一组值。在作为函数的返回值时,通过元组可以返回多个值。
Swift 也引入了可选(Optional)类型,用于处理缺失值的情况。可选(Optional)类型表示:“有一个值,它等于x” 或 “没有值”。可选(Optional)类型类似于在 Objective - C 中使用 nil ,但它可用于任何类型,不仅仅是类。相比于 Objective - C中的 nil ,可选(Optional)类型更加安全,更具表现力。是 Swift 许多最强大功能的重要组成部分。
事实上,Swift是一种更安全的语言,可选(Optional)就是一个例子。Swift 可以帮助你认清代码中值的类型。如果你的代码的预期一个字符串,类型安全会防止你误入一个Int。这可以使您在开发过程能够尽早捕获并修复错误。
5.1.常量和变量
常量和变量把一个名称(如 maximumNumberOfLoginAttempts 或 welcomeMessage )与某一特定类型的值(比如 10 或 “Hello” )关联起来。 常量的值一旦设置不能改变,而一个变量可以设置为不同的值。
5.2.声明常量和变量
声明常量和变量
常量和变量必须在使用之前声明。用 let 关键字声明常量,用 var 关键字声明变量。下面是一个示例,如何使用常量和变量来统计用户尝试登录的次数
var currentLoginAttempt = 0
这段代码可以理解为:
“声明一个名称为 maximumNumberOfLoginAttempts 的常量,并初始化值 10 。 声明一个名称为 currentLoginAttempt 的变量,并初始化值为 0。”
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为最大值不会改变。当前的登录尝试计数器被声明为一个变量,因为这个值在登录失败后必须增加。
您可以在一行中声明多个常量或多个变量,由逗号分隔:
注意:如果在代码中存储的值不会改变,就用 let 关键字声明这是一个常量。变量用于存储需要改变的值。
类型标注
您可以为常量或变量添加类型标注(type annotation),这样就清楚常量或变量可以存储的值的类型。添加类型标注的方法是:在常量或变量名后加冒号,再加一个空格,最后是类型的名称。
这个示例给变量 welcomeMessage 加了一个类型标注,表明它只可以存储 String 类型的值。
“冒号” 意味着“是...类型”,所以上面的代码可以解读为:
“声明一个名称为welcomeMessage的变量,是String类型的。”
“是String类型的”这个短语的意思是“可以存储任何字符串值。”
welcomeMessage 现在可以存储任何字符串值
注意:一般来说很少需要写类型标注。在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型。
5.3.命名常量和变量
你可以使用任何字符命名常量和变量名,包括Unicode
let 你好 = "你好世界"
let □ □ = "dogcow"
常量与变量名不能包含数学符号、箭头、保留的(或者非法的)Unicode 码、连线与制表符。不能以数字开头,但是可以在变量名的其他地方包含数字。
一旦命名了某种类型为常量或变量,就不能再用相同名称重复命名,不能改变它存储值的类型,也不能改变一个常量为变量或改变一个变量为常量。
注意:如果你需要命名一个常量或变量名称为保留关键字,你可以使用反引号(‘)包括关键字作为变量名称。当然,最好避免使用关键字作为名称,除非别无选择。
可以改变变量的值为另一个相同类型的值。在下面的例子中,friendlyWelcome 的值从“Hello!”改为“Bonjour!”
friendlyWelcome = "Bonjour! "
// friendlyWelcome is now "Bonjour! "
不同于变量,常量的值是不能改变的。如果试图改变,那么编译器将报告一个错误:
languageName = "Swift++"
// this is a compile-time error - languageName cannot be changed
5.4.打印常量和变量
使用 println 函数可以打印一个常量或变量的值:
// prints "Bonjour! "
println 是一个全局函数,输出值并且换行,如果你用 Xcode, println 将输出在 Xcode 的“console”(控制台)中。(另一个函数 print,功能相同,区别是不会换行)
println函数打印传递给它的任何字符串:
// prints "This is a string"
println 函数可以打印更复杂的信息,与 Cocoa 里的 NSLog 函数类似。这些信息可以包含常量和变量的值。
Swift 使用字符串插值(string interpolation)的方式,把常量或变量的名称作为一个占位符加入到一个长字符串中,Swift会用常量或变量的值取代他们。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
// prints "The current value of friendlyWelcome is Bonjour! "
5.5.注释
在代码中使用注释,可以方便代码阅读。注释会被Swift编译器忽略。
Swift的注释非常类似于C 语言。单行注释使用斜杠(//):
也可以使用多行注释,以斜杠星号(/*)开始,并以星号斜杠结束(*/) :
but written over multiple lines */
与 C 语言多行注释不同,在Swift中多行注释可以嵌套。先写一个多行注释块,然后在这个注释块中嵌套另一个多行注释:
/* this is the second, nested multiline comment */
this is the end of the first multiline comment */
嵌套多行注释能够使你更快速、轻松地注释掉大量的代码,即使已经包含多行注释块。
5.6.语句结束(;)
不同于许多其他语言,Swift不要求为每个语句的结尾处加一个分号( ; ),尽管你可以这样做(如果你愿意)。
但是,如果你想在一行中写多个独立的语句时,分号是必需的。
//prints "miao"
5.7.整数
整数就是没有小数部分的数字,如 42 和 -23 。整数是有符号的(正、负、零)或无符号的(正、零)。
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数命名约定类似于C语言,如8位无符号整数类型是 UInt8,32位有符号整数类型是 Int32。像所有Swift的其他类型一样,整数类型采用大写命名法。
5.8.整数范围
let maxValue = UInt8.max // maxValue is equal to 255, and is of type UInt8
5.9.Int类型
在大多数情况下,你不需要指定整数的长度。Swift 提供了一个特殊的整数类型:Int,长度与当前平台的字长相同。
在32位平台上,Int和Int32长度相同。
在64位平台上,Int和Int64长度相同。
除非你需要使用特定大小的整数,平时在代码中使用 Int 整数就够了。这可以提高代码的一致性和复用性。在32位平台上,Int可以存储-2147483648至2147483647之间的值,这个范围已经足够大了。
5.10.UInt类型
Swift 还提供了一个特殊的无符号类型UInt,长度与当前平台的字长相同:
在32位平台上,UInt和UInt32长度相同。
在64位平台上,UInt和UInt64长度相同。
注意:尽量不要使用UInt,除非你真的需要存储一个和当前平台字长相同的无符号整数。
最好使用Int,即使你要存储的值是非负的。统一使用Int可以提高代码的复用性,避免不同类型数字之间的转换。
5.11.浮点数
浮点数是由整数和小数部分组成,如 3.14159,0.1,-273.15。
浮点数类型可以表示比整数类型更大范围的值,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种浮点数类型:
Double:64位浮点数。当你需要存储很大或者很高精度的浮点数时使用此类型。
Float:32位浮点数。精度要求不高时使用此类型。
注意:Double 精确度很高,至少15位数字,而Float最少只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
5.12.类型安全和类型引用
Swift 是一种类型安全(type safe )的语言。类型安全的语言会让你更清楚代码中可以使用的值的类型。如果你的代码预期一个String,你绝不能误入一个Int。
因为 Swift 是类型安全的,编译器编译你的代码时会执行类型检查(type checks),并标记出任何不匹配的类型错误。这使你在开发过程中能够尽早捕获并修复错误。
当你使用不同类型的值时,类型安全检查可以帮助你避免错误。当然,这并不意味着你必须为每一个常量和变量显示地指定类型。如果你没有显示地指定类型,Swift会进行类型推测(type inference),选择出适当的类型。类型推测就是编译器在编译代码时能够推断出特定的表达式的类型,主要是根据所提供的值进行推测。
因为类型推测,所以 Swift 比 C 或 Objective - C 需要更少的类型声明。虽然常量和变量需要明确类型,但大部分的工作不需要你来做。
类型推测是特别有用的,当你声明一个常量或变量并初始值的时候,即触发类型推测。
例如,你给一个常量赋值42,但没有标明类型,Swift 可以推测出常量类型是Int,因为你给它赋的值看起来像一个整数:
// meaningOfLife is inferred to be of type Int
同样的,如果你不指定一个常量为浮点类型,Swift 推断出你想要的是Double类型
// pi is inferred to be of type Double
当推测浮点数的类型时,Swift 总是会选择 Double 而不是 Float。
如果你把整数和浮点数同时用在表达式中,Swift 会推测为Double 类型:
// anotherPi is also inferred to be of type Double
数字 3 没有显式的类型,而表达式中出现了一个浮点数,因此,表达式被推测为 Double 类型
数字类型字面值
整数字面值可以写为:
一个十进制数,没有前缀
一个二进制数,前缀是 0b
一个八进制数,前缀是 0o
一个十六进制数,前缀是 0x
下面的所有整数面值的十进制值都是17:
let binaryInteger = 0b10001 // 17 in binary notation
let octalInteger = 0o21 // 17 in octal notation
let hexadecimalInteger = 0x11 // 17 in hexadecimal notation
浮点数面值可以是十进制(没有前缀),或者是十六进制(0x前缀)。必须有至少一个十进制数字(或十六进制数)在小数点两侧。
浮点数面值还有一个可选的指数(exponent),在十进制浮点数中通过大写或者小写的e来表示,在十六进制浮点数中通过大写或者小写的p来表示。
如果一个十进制数的指数为exp,那这个数相当于基数和10^exp(10的exp次方)的乘积:
1.25e2 表示 1.25×10^2,等于 125.0
1.25e-2 表示 1.25×10^-2,等于 0.0125。
如果一个十六进制数的指数为exp,那这个数相当于基数和2^exp(2的exp次方)的乘积:
0xFp2 表示 15×2^2,等于 60.0。
0xFp-2 表示 15×2^-2,等于 3.75。
下面的这些浮点字面量都等于十进制的12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
数值类面值可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,这不会影响字面量:
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
5.13.数字类型转换
在代码中,我们对所有通用的整数常量和变量使用 Int 类型,即使他们知道非负。使用 Int 类型意味着整数常量和变量将易于复用,易于匹配整数面值的类型推测。
只有当必要的时候,才使用其他整数类型,比如处理外部长度明确的数据,或者为了优化性能、内存占用等,或其他必要的优化。使用显示长度的类型有助于发现值溢出和暗示正在使用的数据的性质。
5.14.整数转换
不同整数类型的常量或变量存储的范围是不同的。Int8 的范围是-128至127,而 UInt8 的范围是 0 至 255 。如果数字不在整数类型的范围内,编译器将报告一个错误:
// UInt8 cannot store negative numbers, and so this will report an error
let tooBig: Int8 = Int8.max + 1
// Int8 cannot store a number larger than its maximum value,
// and so this will also report an error
因为每个整数类型可以存储不同范围的值,所以,你必须根据不同的情况选择数值类型的转换。这样可以防止隐式转换的错误,也可以表明类型转换的意图。
将一个特定的数字类型转换成另一个,要用这个数字来初始化成一个需要类型的新数字。在下面的示例中,常量 twoThousand 是 UInt16 类型,然而常量 one 是 UInt8 类型。 它们不能直接加在一起,因为他们不是同一类型。所以,这个例子中调用 UInt16(one) 来创建新的 one 的值,并使用这个新值来计算:
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
因为这2个数现在都是 UInt16 类型,所以可以相加。输出常数(twoThousandAndOne) 也被推断为UInt16类型,因为它是两个UInt16类型值的总和。
SomeType(ofInitialValue) 是 Swift 调用构造器并传入初始值的默认方法。在语言中,UInt16 有一个构造器,可以接受一个 UInt8 类型的值,可以用 UInt8 来创建一个新的UInt16。
注意,你并不能传入任意类型的值,只能传入UInt16构造器需要的类型的值。
5.15.整数和浮点数转换
整数和浮点数之间的转换必须显示的指定类型:
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double
通过常量 three 的值创建一个新类型 Double 的值,所以,加号两边是相同的类型。如果不转换,是不允许相加的。
反过来也是一样的,可以将 Float 、Double 类型的值转换为整数
// integerPi equals 3, and is inferred to be of type Int
Float 和 Double 类型的值会被截断。比如:4.75会变成4,-3.9会变成-3。
注意:结合数字常量和变量的规则不同于字面量的规则。字面量3可以直接和字面量0.14159相加,因为数字字面量本身没有明确的类型。在编译器求值的时候会推测类型。
5.16.类型别名
类型别名就是给一个类型定义的一个小名。通过 typealias 关键字进行定义。
类型别名是非常有用的,特别是一个非常有意义的名称时。比如,处理特定大小的外部来源的数据:
一旦你定义一个类型别名,你可以在任何地方使用别名来替代原来的名称:
// maxAmplitudeFound is now 0
本例中,AudioSample 被定义为 UInt16 的一个别名。所以,AudioSample.min 实际上是 UInt16.min ,所以 maxAmplitudeFound 的初值是0。
5.17.布尔型
Swift 有一个基本的类型: 布尔型(Bool)。布尔值是逻辑值,他们只能是真或假。Swift 提供了两个布尔常量值:true ,false:
let turnipsAreDelicious = false
orangesAreOrange 和 turnipsAreDelicious 的类型会被推测为 Bool 型,因为它们的初值是布尔字面量。就像 Int 和 Double 一样,在创建变量并赋值 true 和 false的时候,就不需要显示的声明为 Boolean 型了。编译器会自动推测出变量的类型,所以 Swift 代码更加高效,可读性更强。
当你编写条件语句的时候,比如 if ,Boolean 是非常重要的:
println("Mmm, tasty turnips! ")
} else {
println("Eww, turnips are horrible.")
}
// prints "Eww, turnips are horrible."
Swfit 类型安全检查会阻止非 Boolean 类型的值用在 Boolean 的地方。下面的例子中编译器会报告错误:
if i {
// this example will not compile, and will report an error
}
然而,下面选择例子是可以的:
if i == 1 {
// this example will compile successfully
}
和 Swift 的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图是清晰的。
5.18.元组
元组(tuples)是把多个值组合成一个复合值。元组内的值可以使任意类型,并不要求是相同类型。
比如(404, "Not Found") ,这是一个描述 HTTP 状态码(HTTP status code)的元组,Http 状态码是请求web 服务器时返回的一个值。如果你请求的页面不存在,就会返回 404 。
// http404Error is of type (Int, String), and equals (404, "Not Found")
(404, "Not Found") 是将一个 Int 类型值和一个 String 类型值组合在一起,表示 HTTP 状态码的两个部分:数字和描述。它可以被描述为 “一个(Int,String)类型的元组”。
元组可以包含任何类型,类型的顺序也是随意的。比如,你可以创建 (Int, Int, Int) 或者 (String, Bool) 元组,或者其他任何你想要的元组。
您可以将一个元组的内容分解成单独的常量或变量,然后就可以正常访问了:
println("The status code is \(statusCode)")
// prints "The status code is 404"
println("The status message is \(statusMessage)")
// prints "The status message is Not Found"
如果你只需要一部分元组的值,忽略的部分用下划线(_)标记:
println("The status code is \(justTheStatusCode)")
// prints "The status code is 404"
另外,可以使用索引访问元组中的各个元素,索引数字从0开始:
// prints "The status code is 404"
println("The status message is \(http404Error.1)")
// prints "The status message is Not Found"
你可以给元组的各个元素进行命名:
这时,可以使用元素名来访问这些元素的值:
// prints "The status code is 200"
println("The status message is \(http200Status.description)")
// prints "The status message is OK"
元组作为函数的返回值时特别有用。一个试图访问网页的函数可能会返回一个(Int, String) 元组来描述是否成功。和只能返回一个值比起来,返回一个包含两个不同类型值的元组让函数更有用。
注意:元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体。
5.19.可选(Optionals)
使用可选(optionals)来处理可能缺失值的情况。一个可选(optionals)表示:
有值,等于 x
或者
没有值
注意:C 和 Objective-C 中没有可选这个概念。Objective-C 中的一个特性与可选比较类似:一个方法要么返回一个对象,要么返回 nil,nil 表示“缺少一个合法的对象”。
例如:Swift 的 String 类型有一个方法 toInt ,用于将一个 String 值转换成一个 Int 值。然而,不是所有的字符串都可以转换成一个整数。字符串 "123" 可以被转换成数字123,但是"hello, world"是不行的。
下面的例子调用 toInt 方法试图将一个 String 转换成 Int:
let convertedNumber = possibleNumber.toInt()
// convertedNumber is inferred to be of type "Int?", or "optional Int"
因为 toInt 方法可能失败,所以它返回一个可选的(optional)Int,而不是一个Int。一个可选的 Int 表示为: Int? 。问号表示是可选的,也就是说可能包含 Int 值也可能不包含。(只能是 Int 或者什么都没有。不能包含其他值,比如 Bool值或者 String 值。)
5.20.If语句和强制解析
可以使用 if 语句来判断一个可选的是否包含一个值。如果包含一个值,结果为 true ;否则 false。
如果你确定可选包含一个值,你可以在名称后面加惊叹号 ( ! ) 访问可选的值。惊叹号 ( ! )表示:“ 这个可选有值,请使用它。” 这就是所谓的 强制解析(forced unwrapping)可选的值。
println("\(possibleNumber) has an integer value of \(convertedNumber! )")
} else {
println("\(possibleNumber) could not be converted to an integer")
}
// prints "123 has an integer value of 123"
注意:如果用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值(if判断一下)。
5.21.可选绑定
可选绑定(optional binding)用来判断可选是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定一般用在 if 和 while 语句中,对可选的值进行判断并把值赋给一个常量或者变量。
statements
}
你可以使用可选绑定重写 possibleNumber 例子:
println("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
println("\(possibleNumber) could not be converted to an integer")
}
// prints "123 has an integer value of 123"
这段代码表示:“ 如果 possibleNumber.toInt 返回的可选 Int 包含一个值,创建常量 actualNumber,并将可选包含的值赋给它。”
如果转换成功,actualNumber 常量可以在 if 语句的第一个分支中使用。actualNumber已经被初始化,所以不需要再使用 ! 获取它的值。在这个例子中,actualNumber用来输出转换结果。
你可以在可选绑定中使用常量和变量。如果你只想在if语句的第一个分支中使用actualNumber的值,你可以改成 if var actualNumber,这样可选包含的值就会被赋给一个变量而非常量。
5.22.nil
通过给可选的变量赋值为 nil 来表示它没有值:
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value
注意:nil 不能用于非可选的常量和变量。如果代码中有常量或者变量需要处理值缺失的情况,把它们声明成对应的可选类型。
如果定义一个可选常量或变量没有提供默认值,它们会被自动设置为 nil :
// surveyAnswer is automatically set to nil
注意:Swift 的 nil 和 Objective-C 的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针,它是一个确定的值,用来表示值的缺失。任何类型的可选都可以被设置为 nil,不只是对象类型。
5.23.隐式解析可选
可选暗示了常量或者变量可以“没有值”。可选可以通过 if 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
有时候在程序中,第一次被赋值之后,可以确定一个可选总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种情况下的可选类型可以被定义为:隐式解析可选(implicitly unwrapped optionals)。声明一个隐式解析可选的方式是:把可选的类型的后面的问号(String? )改成感叹号(String! )。
一个隐式解析可选本质上是一个普通的可选,但是可以被当做非可选使用,并不需要每次都使用解析来获取可选值。下面的例子说明了可选 String 和隐式解析可选 String 的区别:
println(possibleString! ) // requires an exclamation mark to access its value
// prints "An optional string."
let assumedString: String! = "An implicitly unwrapped optional string."
println(assumedString) // no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."
可以把隐式解析可选当做一个可以自动解析的可选。只要声明的时候把惊叹号(!)放到类型的结尾,而不是在每次取值时,把惊叹号(!)放到可选名字的结尾。
注意:如果隐式解析可选没有值,尝试取值会触发运行时错误。和在没有值的普通可选后面加一个惊叹号是一样的。
当然,仍然可以把隐式解析可选当做普通可选来判断它是否包含值:
println(assumedString)
}
// prints "An implicitly unwrapped optional string."
在可选绑定中使用隐式解析可选来检查并解析它的值:
println(definiteString)
}
// prints "An implicitly unwrapped optional string."
注意:如果一个变量值可能变成 nil 的话请不要使用隐式解析可选。如果你需要在变量的生命周期中判断是否是 nil 的话,请使用普通可选。
5.24.断言
5.25.断言调试
断言会在运行时判断一个逻辑条件是否为true。从字面上讲,断言“断言”条件是true。使用断言来确保某些重要的条件被满足后再运行其他代码。
断言:如果条件的结果为 true,代码正常执行;如果条件的结果为 false,代码执行结束,并终止应用程序。
在调试环境中触发断言:
比如当你在 Xcode 构建和运行一个应用程序,断言触发时,你可以看到错误状态发生的确切位置和查询应用程序的状态。此外,断言允许你附加一条调试信息。
你可以使用全局 assert 函数来写一个断言。向 assert 函数传入一个结果为 true 或者 false 的表达式以及一条信息,当表达式为 false 的时候这条信息会被显示:
assert(age >= 0, "A person's age cannot be less than zero")
// this causes the assertion to trigger, because age is not >= 0
断言信息不能使用字符串插值。断言信息可以省略,就像这样:
5.26.何时使用断言
当条件可能为假时触发断言,但是最终一定要保证条件为真,这样代码才能继续运行。断言的适用情景:
整数下标索引被传递一个定制的下标实现,下标索引值可能太小或者太大。
给函数传入一个值,但是非法的值可能导致函数不能正常执行。
可选值现在是 nil,但是后面的代码运行需要一个非nil值。
注意:断言会导致应用终止运行,所以代码的设计要避免非法条件的出现。但是,在应用发布之前,非法条件的出现触发断言可以快速发现问题。