Swift学习记录

简介

Swift 是一门全新的用于开发 iOS, OS X 以及 watchOS 应用的编程语言。

Swift 为所有 C 和 Objective-C 的类型提供了自己的版本,包括整型值的 Int ,浮点数值的 Double 和 Float ,布尔量值的 Bool ,字符串值的 String 。如同集合类型中描述的那样, Swift 同样也为三个主要的集合类型提供了更高效的版本, Array , Set 和 Dictionary 。

和 C 一样,Swift 用变量存储和调用值,通过变量名来做区分。Swift 中也大量采用了值不可变的变量。它们就是所谓的常量,但是它们比 C 中的常量更加给力。当你所处理的值不需要更改时,使用常量会让你的代码更加安全、简洁地表达你的意图。

除了我们熟悉的类型以外,Swift 还增加了 Objective-C 中没有的类型,比如元组。元组允许你来创建和传递一组数据。你可以利用元组在一个函数中以单个复合值的形式返回多个值。

Swift 还增加了可选项,用来处理没有值的情况。可选项意味着要么“这里一个值,它等于 x”要么“这里根本没有值”。可选项类似于 Objective-C 中的 nil 指针,但是不只是类,可选项也可以用在所有的类型上。可选项比 Objective-C 中的 nil 指针更安全、更易读,他也是 Swift 语言中许多重要功能的核心。

可选项充分证明了 Swift 是一门类型安全的语言。Swift 帮助你明确代码可以操作值的类型。如果你的一段代码预期得到一个 String ,类型会安全地阻止你不小心传入 Int 。在开发过程中,这个限制能帮助你在开发过程中更早地发现并修复错误。

常量和变量

声明

使用let声明一个常量,使用var声明一个变量。

let maximumNumberOfLoginAttempts = 10 
var currentLoginAttempt = 0

你可以在一行中声明多个变量或常量,用逗号分隔:

var x = 0.0, y = 0.0, z = 0.0

类型标注

你可以在声明一个变量或常量的时候提供类型标注,来明确变量或常量能够储存值的类型。添加类型标注的方法是在变量或常量的名字后边加一个冒号,再跟一个空格,最后加上要使用的类型名称。

var welcomeMessage: String
let welcomeMessage: String

你可以在一行中定义多个相关的变量为相同的类型,用逗号分隔,只要在最后的变量名字后边加上类型标注。

var red, green, blue: Double

命名

常量和变量的名字几乎可以使用任何字符,甚至包括 Unicode 字符:

let π = 3.14159
let 你好 = "你好世界"
let ️️ = "dogcow"

常量和变量的名字不能包含空白字符、数学符号、箭头、保留的(或者无效的)Unicode 码位、连线和制表符。也不能以数字开头,尽管数字几乎可以使用在名字其他的任何地方。

一旦你声明了一个确定类型的常量或者变量,就不能使用相同的名字再次进行声明,也不能让它改存其他类型的值。常量和变量之间也不能互换。

如果你需要使用 Swift 保留的关键字来给常量或变量命名,可以使用反引号( ` )包围它来作为名称。总之,除非别无选择,避免使用关键字作为名字除非你确实别无选择。

输出

你可以使用 print(_:separator:terminator:) 函数来打印当前常量和变量中的值。

print(friendlyWelcome)
// 输出 "Bonjour!"

注释

使用注释来将不需要执行的文本放入的代码当中,作为标记或者你自己的提醒。当 Swift 编译器在编译代码的时候会忽略掉你的注释。

Swift 中的注释和 C 的注释基本相同。单行注释用两个斜杠开头( // ):

// 这是一个注释

多行的注释以一个斜杠加一个星号开头( /* ),以一个星号加斜杠结尾( */ )。

/* this is also a comment,
but written over multiple lines */

和 C 中的多行注释不同的是, Swift 语言中的多行的注释可以内嵌在其它的多行注释之中,你可以在多行注释中先开启一个注释块,接着再开启另一个注释块。然后关闭第二个注释块,再关闭第一个注释块。

/* 这是第一个多行注释的开头
/* 这是第二个嵌套在内的注释块 */
这是第一个注释块的结尾*/

内嵌多行注释,可以便捷地注释掉一大段代码块,即使这段代码块中已经有了多行注释。

分号

和许多其他的语言不同,Swift 并不要求你在每一句代码结尾写分号( ; ),当然如果你想写的话也没问题。总之,如果你想在一行里写多句代码,分号还是需要的。

let cat = "️"; print(cat)
// 输出 "️"

整数

Swift 提供了 8,16,32 和 64 位编码的有符号和无符号整数,这些整数类型的命名方式和 C 相似,例如 8 位无符号整数的类型是 UInt8 ,32 位有符号整数的类型是 Int32 。与 Swift 中的其他类型相同,这些整数类型也用开头大写命名法。

范围

你可以通过 min 和 max 属性来访问每个整数类型的最小值和最大值:

let minValue = UInt8.min // 最小值是 0, 值的类型是 UInt8
let maxValue = UInt8.max // 最大值是 255, 值得类型是 UInt8

Int

在大多数情况下,你不需要在你的代码中为整数设置一个特定的长度。Swift 提供了一个额外的整数类型: Int ,它拥有与当前平台的原生字相同的长度。

  • 在32位平台上, Int 的长度和 Int32 相同。
  • 在64位平台上, Int 的长度和 Int64 相同。

除非你需操作特定长度的整数,否则请尽量在代码中使用 Int 作为你的整数的值类型。这样能提高代码的统一性和兼容性,即使在 32 位的平台上, Int 也可以存 -2,147,483,648 到 2,147,483,647 之间的任意值,对于大多数整数区间来说完全够用了。

UInt

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 是一门类型安全的语言。类型安全的语言可以让你清楚地知道代码可以处理的值的类型。如果你的一部分代码期望获得 String ,你就不能错误的传给它一个 Int 。

因为 Swift 是类型安全的,他在编译代码的时候会进行类型检查,任何不匹配的类型都会被标记为错误。这会帮助你在开发阶段更早的发现并修复错误。

数值型字面量

整数型字面量可以写作:

  • 一个十进制数,没有前缀
  • 一个二进制数,前缀是 0b
  • 一个八进制数,前缀是 0o
  • 一个十六进制数,前缀是 0x

下面的这些所有整数字面量的十进制值都是 17

let decimalInteger = 17
let binaryInteger = 0b10001 // 17 in binary notation
let octalInteger = 0o21 // 17 in octal notation
let hexadecimalInteger = 0x11 // 17 in hexadecimal notation

数值类型转换

整数转换

要将一种数字类型转换成另外一种类型,你需要用当前值来初始化一个期望的类型。在下面的栗子中,常量 twoThousand 的类型是 UInt16 ,而常量 one 的类型是 UInt8 。他们不能直接被相加在一起,因为他们的类型不同。所以,这里让 UInt16 (one ) 创建一个新的 UInt16 类型并用 one 的值初始化,这样就可以在原来的地方使用了。

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

因为加号两边的类型现在都是 UInt16 ,所以现在是可以相加的。输出的常量( twoThousandAndOne )被推断为 UInt16 类型,因为他是两个 UInt16 类型的和。

SomeType(ofInitialValue) 是调用 Swift 类型初始化器并传入一个初始值的默认方法。在语言的内部, UInt16 有一个初始化器,可以接受一个 UInt8 类型的值,所以这个初始化器可以用现有的 UInt8来创建一个新的 UInt16 。这里需要注意的是并不能传入任意类型的值,只能传入 UInt16 内部有对应初始化器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考扩展

整数和浮点数转换

整数和浮点数类型的转换必须显式地指定类型:

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double

在这里,常量 three 的值被用来创建一个类型为 Double 的新的值,所以加号两边的值的类型是相同的。没有这个转换,加法就无法进行。

类型别名

类型别名可以为已经存在的类型定义了一个新的可选名字。用 typealias 关键字定义类型别名。

当你根据上下文的语境想要给类型一个更有意义的名字的时候,类型别名会非常高效,例如处理外部资源中特定长度的数据时:

typealias AudioSample = UInt16

布尔值

Swift 有一个基础的布尔量类型,就是 Bool ,布尔量被作为逻辑值来引用,因为他的值只能是真或者假。Swift为布尔量提供了两个常量值, true 和 false 。

let orangesAreOrange = true
let turnipsAreDelicious = false

Swift 的类型安全机制会阻止你用一个非布尔量的值替换掉 Bool 。下面的栗子中报告了一个发生在编译时的错误:

let i = 1
if i {
    // this example will not compile, and will report an error
}

let i = 1
if i == 1 {
    // this example will compile successfully
}

元组

元组把多个值合并成单一的复合型的值。元组内的值可以是任何类型,而且可以不必是同一类型。

在下面的示例中, (404, "Not Found") 是一个描述了 HTTP 状态代码 的元组。HTTP 状态代码是当你请求网页的时候 web 服务器返回的一个特殊值。当你请求不存在的网页时,就会返回 404 Not Found


let  http404Error  =  (404,  "Not Found")

// http404Error is of type (Int, String), and equals (404, "Not Found")

(404, "Not Found")元组把一个 Int 和一个 String 组合起来表示 HTTP 状态代码的两种不同的值:数字和人类可读的描述。他可以被描述为“一个类型为 (Int, String) 的元组”

任何类型的排列都可以被用来创建一个元组,他可以包含任意多的类型。例如 (Int, Int, Int) 或者 (String, Bool) ,实际上,任何类型的组合都是可以的。

你也可以将一个元组的内容分解成单独的常量或变量,这样你就可以正常的使用它们了:

let  (statusCode,  statusMessage)  =  http404Error

print("The status code is \(statusCode)")

// prints "The status code is 404"

print("The status message is \(statusMessage)")

// prints "The status message is Not Found"

当你分解元组的时候,如果只需要使用其中的一部分数据,不需要的数据可以用下滑线( _ )代替:

let  (justTheStatusCode,  _)  =  http404Error

print("The status code is \(justTheStatusCode)")

// prints "The status code is 404"

另外一种方法就是利用从零开始的索引数字访问元组中的单独元素:


print("The status code is \(http404Error.0)")

// prints "The status code is 404"

print("The status message is \(http404Error.1)")

// prints "The status message is Not Found"

你可以在定义元组的时候给其中的单个元素命名:

let  http200Status  =  (statusCode:  200,  description:  "OK")

在命名之后,你就可以通过访问名字来获取元素的值了:


print("The status code is \(http200Status.statusCode)")

// prints "The status code is 200"

print("The status message is \(http200Status.description)")

// prints "The status message is OK"

作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。相比只能返回一个类型的值,元组能包含两个不同类型值,他可以让函数的返回信息更有用。更多内容请参考多返回值的函数

可选项

可以利用可选项来处理值可能缺失的情况。

在 C 和 Objective-C 中,没有可选项的概念。在 Objective-C 中有一个近似的特性,一个方法可以返回一个对象或者返回 nil 。 nil 的意思是“缺少一个可用对象”。然而,他只能用在对象上,却不能作用在结构体,基础的 C 类型和枚举值上。对于这些类型,Objective-C 会返回一个特殊的值(例如 NSNotFound )来表示值的缺失。这种方法是建立在假设调用者知道这个特殊的值并记得去检查他。然而,Swift 中的可选项就可以让你知道任何类型的值的缺失,他并不需要一个特殊的值

下面的栗子演示了可选项如何作用于值的缺失,Swift 的 Int 类型中有一个初始化器,可以将 String 值转换为一个 Int 值。然而并不是所有的字符串都可以转换成整数。字符串 “123” 可以被转换为数字值 123 ,但是字符串 "hello, world" 就显然不能转换为一个数字值。

在下面的栗子中,试图利用初始化器将一个 String 转换为 Int :

et possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"

因为这个初始化器可能会失败,所以他会返回一个可选的 Int ,而不是 Int 。可选的 Int 写做 Int? ,而不是 Int 。问号明确了它储存的值是一个可选项,意思就是说它可能包含某些 Int 值,或者可能根本不包含值。(他不能包含其他的值,例如 Bool 值或者 String 值。它要么是 Int 要么什么都没有。)

nil

你可以通过给可选变量赋值一个 nil 来将之设置为没有值:

var serverResponseCode: Int? = 404
// serverResponseCode contains an actual Int value of 404
serverResponseCode = nil
// serverResponseCode now contains no value

如果你定义的可选变量没有提供一个默认值,变量会被自动设置成 nil 。

var surveyAnswer: String?

Swift 中的 nil 和Objective-C 中的 nil 不同,在 Objective-C 中 nil 是一个指向不存在对象的指针。在 Swift中, nil 不是指针,他是值缺失的一种特殊类型,任何类型的可选项都可以设置成 nil 而不仅仅是对象类型。

If 语句以及强制展开

你可以利用 if 语句通过比较 nil 来判断一个可选中是否包含值。利用相等运算符 ( == )和不等运算符( != )。

如果一个可选有值,他就“不等于” nil :

if convertedNumber != nil {
   print("convertedNumber contains some integer value.")
}
// prints "convertedNumber contains some integer value."

一旦你确定可选中包含值,你可以在可选的名字后面加一个感叹号 ( ! ) 来获取值,感叹号的意思就是说“我知道这个可选项里边有值,展开吧。”这就是所谓的可选值的强制展开。

if convertedNumber != nil {
   print("convertedNumber has an integer value of \(convertedNumber!).")
}
// prints "convertedNumber has an integer value of 123."

可选项绑定

可以使用可选项绑定来判断可选项是否包含值,如果包含就把值赋给一个临时的常量或者变量。可选绑定可以与 if 和 while 的语句使用来检查可选项内部的值,并赋值给一个变量或常量。 if 和 while 语句的更多详细描述,请参考控制流

在 if 语句中,这样书写可选绑定:

if let constantName = someOptional { 
   statements 
} 

if let actualNumber = Int(possibleNumber) {
   print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
   print("\'\(possibleNumber)\' could not be converted to an integer")
}
// prints "'123' has an integer value of 123"

你可以在同一个 if 语句中包含多可选项绑定,用逗号分隔即可。如果任一可选绑定结果是 nil 或者布尔值为 false ,那么整个 if 判断会被看作 false 。下面的两个 if 语句是等价的:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
   print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"

if let firstNumber = Int("4") {
   if let secondNumber = Int("42") {
       if firstNumber < secondNumber && secondNumber < 100 {
           print("\(firstNumber) < \(secondNumber) < 100")
       }
   }
}
// Prints "4 < 42 < 100"

如同提前退出中描述的那样,使用 if 语句创建的常量和变量只在if语句的函数体内有效。相反,在 guard 语句中创建的常量和变量在 guard 语句后的代码中也可用。

隐式展开可选项

如上所述,可选项明确了常量或者变量可以“没有值”。可选项可以通过 if 语句来判断是否有值,如果有值的话可以通过可选项绑定来获取里边的值。

有时在一些程序结构中可选项一旦被设定值之后,就会一直拥有值。在这种情况下,就可以去掉检查的需求,也不必每次访问的时候都进行展开,因为它可以安全的确认每次访问的时候都有一个值。

这种类型的可选项被定义为隐式展开可选项。通过在声明的类型后边添加一个叹号( String! )而非问号( String? )来书写隐式展开可选项。与在使用可选项时在名称后加一个叹号不同的是,声明的时候要把叹号放在类型的后面。

在可选项被定义的时候就能立即确认其中有值的情况下,隐式展开可选项非常有用。如同无主引用和隐式展开的可选属性中描述的那样,隐式展开可选项主要被用在 Swift 类的初始化过程中。

隐式展开可选项是后台场景中通用的可选项,但是同样可以像非可选值那样来使用,每次访问的时候都不需要展开。下面的栗子展示了在访问被明确为 String 的可选项展开值时,可选字符串和隐式展开可选字符串的行为区别:

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark
 
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // no need for an exclamation mark

错误处理

在程序执行阶段,你可以使用错误处理机制来为错误状况负责。

相比于可选项的通过值是否缺失来判断程序的执行正确与否,而错误处理机制能允许你判断错误的形成原因,在必要的情况下,还能将你的代码中的错误传递到程序的其他地方。

当一个函数遇到错误情况,他会抛出一个错误,这个函数的访问者会捕捉到这个错误,并作出合适的反应。

func canThrowAnError() throws {
    // this function may or may not throw an error
}

通过在函数声明过程当中加入 throws 关键字来表明这个函数会抛出一个错误。当你调用了一个可以抛出错误的函数时,需要在表达式前预置 try 关键字。

Swift 会自动将错误传递到它们的生效范围之外,直到它们被 catch 分句处理。

do {
    try canThrowAnError()
    // no error was thrown
} catch {
    // an error was thrown
}

do 语句创建了一个新的容器范围,可以让错误被传递到到不止一个的 catch 分句里。

func makeASandwich() throws {
    // ...
}
 
do {
    try makeASandwich()
    eatASandwich()
} catch Error.OutOfCleanDishes {
    washDishes()
} catch Error.MissingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在上面的栗子中,在没有干净的盘子或者缺少原料的情况下,方法 makeASandwich() 就会抛出一个错误。由于 makeASandwich() 的抛出,方法的调用被包裹在了一个 try 的表达式中。通过将方法的调用包裹在 do 语句中,任何抛出来的错误都会被传递到预先提供的 catch 分句中。

如果没有错误抛出,方法 eatASandwich() 就会被调用,如果有错误抛出且满足 Error.OutOfCleanDishes 这个条件,方法 washDishes() 就会被执行。如果一个错误被抛出,而它又满足 Error.MissingIngredients 的条件,那么 buyGroceries(_:) 就会协同被 catch 模式捕获的 String值一起调用。

断言和先决条件

断言和先决条件用来检测运行时发生的事情。你可以使用它们来保证在执行后续代码前某必要条件是满足的。如果布尔条件在断言或先决条件中计算为 true ,代码就正常继续执行。如果条件计算为 false ,那么程序当前的状态就是非法的;代码执行结束,然后你的 app 终止。

断言和先决条件的不同之处在于他们什么时候做检查:断言只在 debug 构建的时候检查,但先决条件则在 debug 和生产构建中生效。在生产构建中,断言中的条件不会被计算。这就是说你可以在开发的过程当中随便使用断言而无需担心影响生产性能。

使用断言进行调试

断言会在运行的时候检查一个逻辑条件是否为 true 。顾名思义,断言可以“断言”一个条件是否为真。你可以使用断言确保在运行其他代码之前必要的条件已经被满足。如果条件判断为 true,代码运行会继续进行;如果条件判断为 false,代码运行结束,你的应用也就中止了。

如果你的代码在调试环境下触发了一个断言,例如你在 Xcode 中创建并运行一个应用,你可以明确的知道不可用的状态发生在什么地方,还能检查断言被触发时你的应用的状态。另外,断言还允许你附加一条调试的信息。

你可以使用全局函数 assert(::) 函数来写断言。向 assert(::) 函数传入一个结果为 true 或者 false 的表达式以及一条会在结果为 false 的时候显式的信息:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// this causes the assertion to trigger, because age is not >= 0

断言信息可以删掉如果你想的话,就像下边的栗子:

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.")
}

强制先决条件

在你代码中任何条件可能潜在为假但必须肯定为真才能继续执行的地方使用先决条件。比如说,使用先决条件来检测下标没有越界,或者检测函数是否收到了一个合法的值。

你可以通过调用 precondition(::file:line:) 函数来写先决条件。给这个函数传入表达式计算为 true 或 false ,如果条件的结果是 false 信息就会显示出来。比如说:

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

你可以调用 preconditionFailure(_:file:line:) 函数来标明错误发生了——比如说,如果 switch 的默认情况被选中,但所有的合法输入数据应该被其他 switch 的情况处理。

如果你在不检查模式编译( -Ounchecked ),先决条件不会检查。编译器假定先决条件永远为真,并且它根据你的代码进行优化。总之, fatalError(:file:line:) 函数一定会终止执行,无论你优化设定如何。
你可以在草拟和早期开发过程中使用 fatalError(
:file:line:) 函数标记那些还没实现的功能,通过使用 fatalError("Unimplemented") 来作为代替。由于致命错误永远不会被优化,不同于断言和先决条件,你可以确定执行遇到这些临时占位永远会停止。

你可能感兴趣的:(Swift学习记录)