iOS 开发 Objective-C

Swift Language Guide5.2 第一篇:基础篇之

2020-03-14  本文已影响0人  望穿秋水小作坊

Tuples 元组

元组将多个值组成一个复合值。一个元组内可以多种不同类型的值。
下面例子中,(404, "Not Found") 是一个描述 HTTP 状态码的元组。HTTP 状态码是访问 Web 服务器页面时候返回的指定值。如果访问的 web 页面不存在就会返回 404 Not Found

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

(404, "Not Found") 元组由一个 IntString两个值组成 HTTP 的状态码:一个数字和一个易于理解的描述。它可以被描述成(Int,String)类型元组。

你可以从任何类型的排列中创建元组,只要你想你可以包含各种不同的类型。没有任何事情能阻止你拥有(Int, Int, Int)(String, Bool),或者其他任意你需要的类型排列。

可以也可以将一个元组分解成常量或者变量:

let (status, statusMessage) = http404Error
print("The status code is \(statusCode)")
print("The status message is \(statusMessage)")

如果你分解元组时仅仅需要元祖中的某些值,忽略元组中的其他值可以使用_下划线:

let (justTheStatusCode,_) = http404Error

或者,可以用从零开始的下标索引来访问元祖中各个元素的值:

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

你也可以在定义的元组的时候为每个独立元素命名:

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

如果你为元组的元素命名,那么你可以用这些元素的名称去访问它们的值:

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

当函数需要返回多个值的时候,元组特别好用。比如一个函数用于接收 web 访问的返回结果,就可以用 (Int, String)来描述页面返回的成功或者失败。相对于只能返回一个类型的一个值而言,元组可以返回多个不同值,相互之间类型不同这对于一个函数来说是十分好用的。

NOTE
元组适合简单的相关值,不适合用于复杂的数据结构。如果你的数据结构比较复杂,modelclassstructure是比元组更好的选择。

Optionals 选择

当一个值可能缺失的情况下使用optionals。一个 optional 呈现两种情况:要么它是一个具体值,你可以解开optional访问它的值,或它还是一个值。

NOTE
optionals是 C 或者 Objective-C 中不存在的概念。最相近的东西就是 Objective-C 中要么返回 nil要么返回一个object的方法,nil表示一个合法的缺省值。然而,它只能是objects 不能是 structures basic C Type enumeration values。对于这些类型,Objective-C 通常会指定方法的放回NSNotFound去表明这是一个缺省值。这种方法假定方法调用者知道这个指定类型,并去检测它。Swift 的 optionals 让你明确这个值要么是缺省值要么是特定类型,不需要特别的约束。

下面是一个optionals如何应对缺省值的例子。Swift 有个Int类别的初始化方法可将String转换成Int值。然而,并不是所有的 string 可以被转换成 int 值。比如字符串 "123" 是可以被转出数值 123的,但是字符串 "hello, word"没有明显的数字可以进行转换。

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

因为初始化程序可能会失败,所以它返回一个可选的Int,而不是Int。 可选的Int被写为Int ?,而不是Int。 问号表示它包含的值是可选的,这意味着它可能包含一些Int值,或者可能根本不包含任何值。 (它不能包含其他任何内容,例如Bool值或String值。它可以是Int或完全不包含任何字符。)

nil

你可以将 nil 设置给无值状态下的 optional variable

var serverResponseCode: Int? = 404
serverResponseCode = nil

在非可选变量或者常量中,你不可以使用 nil。如果你代码中的常量或者变量在特定情况需要缺省值,通常情况下我们会把它声明成可选的合适类型。

如果你定义的可选变量没有声明值,这个变量会被自动设置成 nil

var serverAnswer: String?
// serverAnswer is automatically set to nil

NOTE
Swift 的 nil 和 Objective-C 语言中的 nil是不同的。在 Objective-C 中,nil是一个指向不存在的对象的指针。在 Swift 中 nil不是指针,它是具体类型的缺省值。可以将任何类型的 Optionals 设置为 nil,不仅仅是对象类型。

If Statements and Forced Unwrapping If表达式和强制展开

你可以用 if表达式和 nil 对比来判断一个可选类型是否包含具体值。你可以指向相等比较符 ==和不相等比较符 !=

如果一个可选项有值,它会被认定不等于 nil:

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

一旦你确定可选类型有具体的值,你就可以在它的末尾加上!感叹号来访问它。感叹号的含义是“我知道这个可选类型有具体值,请使用它。”这就是对可选类型的强制展开。

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

NOTE
如果尝试用 !去访问一个不存在值的可选类型将会报运行时错误。在使用强制展开符 !之前必须先确认可选类型是非 nil的。

Optional Binding 可选绑定

你可以使用可选绑定来找出可选类型是否包含具体值,如果这样做,可以将该变量值作为临时常量或变量。可选绑定使用 ifwhile 表达式检测可选类型内部的值,提取它的值作为常量或变量,然后作为条件的一部分。

if 语句的可选绑定是像下面这样写的:

if let constantName = someOptional {
 statements
}

下面的例子你可以使用可选绑定实现,而不使用强制展开法:

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

上面的代码可以这样理解:

“如果可选值 Int 通过Int(possibleNumber) 创建含有真实的一个值,那么就把这个值设置给新的常量 actualNumber。”

如果转换成功,actualNumber 常量将会变成 if条件语句第一个分支的变量。他已经通过可选值包含的值初始化完成,所以在这个例子中不需要使用 !来强制展开从而访问到实际值。 actualNumber 是转换之后的简单值。

你可以在值绑定的时候使用 常量 或者 变量。如果你想在 if 的第一个分支里面操纵actualNumber ,你可以使用 if var actualNumber 替代,那么可选项中包含的值会被赋给变量而不是常量。

你可以包含许多个可选项绑定和布尔条件在一个if的条件判断语句中,使用逗号分隔。如果任何一个可选项的值为 nil 或 任意一个布尔条件评估为 false , 那么整个条件语句将会被认为是 false。

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"

NOTE
if 条件语句中使用可选绑定的创建的常量或变量只能在语句body内使用。相反,使用 guard 语句创建的常量或者变量在guard语句后面的代码中都可以使用。

Implicitly Unwrapped Optionals 隐式展开可选类

如上所述,可选类别意味着常量或变量允许没有值“no value”。可选类别在 if 条件语句中检测,如果存在可以用可选绑定访问它的值。

有时候在可选类别被首次设置之后,对于程序结构而言非常清楚它一直都会有值。在这种情况,每次使用时移除检测和展开操作是非常有必要的。因为可以安全地假定它始终具有值。

这种可选类别被定义成 implicitly unwrapped optionals。你可以使用感叹号来替换问好声明一个 implicitly unwrapped optionals 隐式展开可选类别。

在可选类别被第一次赋值之后,并且保证后续都一直有值,这种情况 implicitly unwrapped optionals是非常有用的。 implicitly unwrapped optionals主要的使用是在 Swift 的类初始化中。

implicitly unwrapped optionals 是一个常规的可选类别,但用起来像非可选类一样,每次访问的时候不必去展开它。下面的例子说明了隐式可选类和一般可选类访问时的区别。

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

你可以认为 implicitly unwrapped optional就是允许在使用的它地方自动展开。而不是每次使用它的时候使用 ! 来访问它,你在声明的时候使用 !感叹号。

NOTE
如果 implicitly unwrapped optionalnil 时,你尝试去访问它的值,你就会得到一个运行时错误。这个结果就像你使用 ! 感叹号去展开一个没有值的正常可选类别一样。

你可以把 implicitly unwrapped optional 当成常规可选类别,可以检查它所包含的值:

if assumedString != nil {
  print(assumedString!)
}

你也可以对 implicitly unwrapped optional 进行可选绑定,来展开它的值:

if let definiteString = assumedString {
    print(definiteString)
}
// Prints "An implicitly unwrapped optional string."

NOTE
如果 implicitly unwrapped optional 在后续可能变成 nil 请不要使用它。如果在变量生命周期中,需要在代码中检测 nil,请使用 常规可选类别。

Error Handing 错误处理

你可以使用 Error handing 处理你的程序在执行中遇到的错误情况。

和可选类别相反,它可以用值的存在或者不存在来来表达函数的成功和失败。 Error handing可以帮你确定错误的根本原因,如果需要,你还可以将错误传递给你的程序另一部分。

当程序遇到一个错误情况,它会抛出一个错误。程序调用者可以通过catch捕获错误然后用合适的方式相应。

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 条款中去。

下面是一个展示 error handing如何处理不同错误条件的例子:

func makeASandwich() throws {
  // ...
}

do {
  try makeASandwich()
  eatAsanwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在这个例子中, makeASandwich()可能会报一个没有干净盘子可用的或某种原材料缺失的错误。因为 makeASandwich()可能会抛出错误,所以用 try 表达式包裹。包裹这个函数调用的是 do 表达式,任何错误都会被传递到 catch 条款中。

如果没有错误抛出,则 eatASandwich()会被调用。如果有错误抛出并且匹配错误码是 SandwichError.outOfCleanDishes,那么 washDishes()函数会被调用。如果错误码是 SandwichError.missingIngredients ,那么 buyGroceries(_:)函数会被执行,并且有一个 [String]类型的参数会被 catch匹配到。

Assertions and Preconditions 断言和前提条件

断言和前提条件是发生在运行时检查。在执行下一步代码之前确保他们满足一个必要条件。如果在断言或者前提条件被判断为 true 的时候,代码会正常往下执行。如果条件被判断为 false ,当前程序状态是不合理的,代码会停止运行,你的 app 会被终止。

使用断言和前提条件来在代码中表达你的假设和期望,因此可以你可以将它们纳入代码的一部分。断言帮助你发现开发环境中错误和不正常的假设,前提条件帮助你在生成环境中检测问题。

此外断言和前提条件在运行时检验你的期待,所以也变成对代码非常有用的文档说明的。不像上面Error Handing 提到的错误条件,断言和前提条件不用于恢复或者期望的错误。因为失败的断言和前提条件意味着不合法的程序状态,他们没有办法去 catch 失败的断言。

使用断言和前提条件并不能替代设计代码,这样就不会出现无效条件。然而,使用它们来强制合法数据和状态会引起你的 app 在不合法状态下更容易崩溃终止。在检测到不合法状态时尽快终止代码运行可以帮忙限制所造成的损害。

断言和前提条件的不同之处在于它们检测的时间:断言只在调试版本检测,但是前提条件在调试和生成版本检测。在生产环境中断言中的条件是不会被运行的。这意味着你可以在开发阶段尽管使用断言,它们不会影响你的生产版本。

Debugging with Assertions

在 Swift 标准库中,你可以通过 assert(_:_:file:line:) 函数来写一个断言。你给这个函数传递一个结果为 truefalse 的表达式和一条当结果为false的消息。例子如下:

let age = -3
assert(age >= 0, "a persion's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.

在这个例子中,如果age是非负数,那么 age >= 0 将会是true,代码就会继续往下执行。如果 age 是负数,那么上面的代码 age >=0将会是 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.")
}
Enforcing Preconditions 强制前提条件

当一个条件有可能潜在为 false的情况,但是为了你的代码能正常运行,它必须为 true ,这种情况下用 precondition 前提条件。例如,使用前提条件检测下标是否越界,或检测函数是否传递一个合法值。

你可以用 precondition(_:_:file:line:) 函数写一个前提条件。传递一个结果值为truefalse 的表达式,以及一条false情况下的展示信息。例子如下:

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

你也可以调用 preconditionFailure(_:file:line:) 函数来指明失败前提条件出现的情况下。比如在 switch 条件语句中,所有正常情况都有相应处理,而遇到其他情况下使用。

NOTE
如果在-Ounchened模式下编译代码,前置条件也会不被检查。编译器会假设所有的前置条件都是true,从而提升你代码的效率。但是 fatalError(_:file:line:)会无视这个这个优化设置。

上一篇下一篇

猜你喜欢

热点阅读