1.Constants and Variables(常量和变量)
let
定义常量,var
定义变量。
[Note] If a stored value in your code won’t change, always declare it as a constant with the
let
keyword. Use variables only for storing values that need to be able to change.
如果代码中的存储值不会更改,则始终使用let关键字将其声明为常量。仅将变量用于存储需要更改的值。
2.Type Annotations(类型注解)——不同与C语言
Write a type annotation by placing a colon(冒号) after the constant or variable name, followed by a space, followed by the name of the type to use.var/let <变量名>(:)(空格)<类型名>
var welcomeMessage: String
welcomeMessage = "Hello"
The colon in the declaration means “…of type…,” so the code above can be read as:
“Declare a variable called welcomeMessage that is of type String.”
You can define multiple related variables of the same type on a single line, separated by commas, with a single type annotation after the final variable name:
var red, green, blue: Double
[Note]在实践中很少需要编写类型注释。如果在定义常量或变量时为其提供一个初始值,Swift几乎总是可以推断出该常量或变量所使用的类型,如类型安全性和类型推断中所述。在上面的welcomeMessage示例中,没有提供初始值,因此welcomeMessage变量的类型是用类型注释指定的,而不是从初始值推断出来的。
3.Naming Constants and Variables(命名常量和变量)
Constant and variable names can contain almost any character, including Unicode characters:
let π = 3.14159
let 你好 = "你好世界"
let = "dogcow"
常量和变量名不能包含空白字符、数学符号、箭头、私有使用的Unicode标量值或行和箱形图字符(如1⃣️)。它们也不能以数字开头,尽管数字可能包含在名称的其他地方。
[Note]如果您需要为一个常量或变量提供与保留的Swift关键字相同的名称,那么在使用该关键字作为名称时,请在其周围加上反引号(')。但是,除非别无选择,否则不要使用关键字作为名称。
let `let` = 0
4.Printing Constants and Variables(打印常量和变量)
You can print the current value of a constant or variable with the print(_:separator:terminator:)
function:
print(friendlyWelcome)
print(_:separator:terminator:)
函数是一个全局函数,它将一个或多个值打印到适当的输出。例如,在Xcode中,print(_:separator:terminator:)
函数在Xcode的“console”窗格中打印输出。分隔符和终止符参数有默认值,因此在调用此函数时可以省略它们。默认情况下,函数通过添加换行符来终止它打印的行。要打印一个后面没有换行符的值,需要传递一个空字符串作为结束符——例如,print(someValue, terminator: "")。有关具有默认值的参数的信息,请参阅默认参数值。
Swift使用字符串插值将一个常量或变量的名称作为一个更长的字符串的占位符,并提示Swift将其替换为该常量或变量的当前值。将名称括在括号内,并在左括号前加上反斜杠:
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// Prints "The current value of friendlyWelcome is Bonjour!"
5.Comments(//、/…/)
与C语言中的多行注释不同,Swift中的多行注释可以嵌套在其他多行注释中。通过启动一个多行注释块,然后在第一个块中启动第二个多行注释,可以编写嵌套注释。然后关闭第二个块,然后关闭第一个块:
/* This is the start of the first multiline comment.
/* This is the second, nested multiline comment. */
This is the end of the first multiline comment. */
6.Semicolons(分号)
与许多其他语言不同的是,Swift并不要求你在代码中的每个语句后面都写一个分号(;),尽管你可以这样做。但是,如果你想在一行中写多个独立的语句,分号是必须的.
7.Integers(整数)
Like all types in Swift, these integer types have capitalized names.
与Swift中的所有类型一样,这些整数类型的名称都是大写的。
你可以访问最小值和最大值的每一个整数类型与它的最小和最大的属性:
let minValue = UInt8.min // minValue等于0,类型为UInt8
let maxValue = UInt8.max // maxValue等于255,类型为UInt8
这些属性的值是适当大小的数字类型(如上面示例中的UInt8),因此可以在表达式中与相同类型的其他值一起使用。
在大多数情况下,不需要选择要在代码中使用的整数的特定大小。Swift提供了一个额外的整数类型Int,它的大小与当前平台的本机字大小相同.
Swift还提供了一个无符号整数类型UInt,它的大小与当前平台的本机字大小相同.
8.Floating-Point Numbers(Double、Float)
In situations where either type would be appropriate, Double is preferred.
在两种类型都合适的情况下,Double是首选。
9.Type Safety and Type Inference(类型安全性和类型推断)
在声明具有初始值的常量或变量时,类型推断特别有用。这通常是通过在声明常量或变量时为其赋值来实现的。(文字值是直接出现在源代码中的值,如下面示例中的42和3.14159。)
例如,如果你给一个新的常量赋值42,而不告诉它是什么类型的常量,Swift会推断出你希望这个常量是一个整型,因为你已经用一个看起来像整数的数字初始化了它:
let meaningOfLife = 42
// meaningOfLife is inferred to be of type Int
let pi = 3.14159
// pi is inferred to be of type Double
在推断浮点数类型时,Swift总是选择Double(而不是Float)。
10.Numeric Literals(数字字面值)
- 整数字面值可以写成:
- 一个没有前缀的十进制数
- 一个带0b前缀的二进制数
- 一个带有0o前缀的八进制数
- 带0x前缀的十六进制数
浮点文字可以是十进制(没有前缀),也可以是十六进制(有0x前缀)。它们必须在小数点的两边都有一个数字(或十六进制数字)。十进制浮点数也可以有一个可选的指数,由大写或小写e表示(\[10^{exp}\]);十六进制浮点数必须有一个由大写或小写p表示(\[2^{exp}\])的指数。
数字文字可以包含额外的格式,以便于阅读。整数和浮点数都可以用额外的零来填充,并且可以包含下划线来提高可读性。这两种格式都不会影响文字的基础值:
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
11.Numeric Type Conversion(数值类型转换)
①Integer Conversion
因为每种数字类型可以存储不同范围的值,所以必须根据具体情况选择数字类型转换。这种选择进入方法可以防止隐藏的转换错误,并有助于在代码中明确类型转换意图。
要将一种特定的数字类型转换为另一种类型,需要使用现有值初始化所需类型的新数字。在下面的示例中,常量twoThousand的类型是UInt16,而常量one的类型是UInt8。它们不能直接相加,因为它们不是同一类型的。相反,这个例子调用UInt16(1)来创建一个新的UInt16,初始化的值为1,并使用这个值代替原来的值:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
②Integer and Floating-Point Conversion
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi equals 3.14159, and is inferred to be of type Double
let integerPi = Int(pi)
// integerPi equals 3, and is inferred to be of type Int
[Note]组合数值常量和变量的规则与数值文字的规则不同。文字值3可以直接添加到文字值0.14159,因为数字文字本身并没有显式的类型。它们的类型仅在编译器计算它们时才被推断出来。
12.Type Aliases(类型别名)
Type aliases define an alternative name for an existing type. You define type aliases with the typealias
keyword. 与C语言的typeof类似
类型别名在您希望通过上下文更合适的名称引用现有类型时非常有用,例如在处理来自外部源的特定大小的数据时,一旦你定义了一个类型别名,你可以在任何你可能使用原始名称的地方使用别名:
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound is now 0
13.Booleans(布尔值)
布尔值在处理条件语句时特别有用,例如if语句:
if turnipsAreDelicious {
print("Mmm, tasty turnips!")
} else {
print("Eww, turnips are horrible.")
}
// Prints "Eww, turnips are horrible."
14.Tuples(元组)
Tuples(元组) enable you to create and pass around groupings of values. You can use a tuple to return multiple values from a function as a single compound value.
元组将多个值分组为单个复合值。元组中的值可以是任何类型的,并且不必是彼此相同的类型。
在本例中,(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”)元组将一个Int和一个字符串组合在一起,为HTTP状态码提供两个单独的值:一个数字和一个人类可读的描述。它可以被描述为“类型(Int, String)的元组”。
你可以将元组的内容分解成单独的常量或变量,然后像往常一样访问它们:
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"
或者,使用索引号从0开始访问元组中的单个元素值:
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"
当tuple被定义时,您可以命名tuple中的各个元素:
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"
元组作为函数的返回值特别有用。试图检索web页面的函数可能会返回(Int、String)元组类型来描述检索页面的成功或失败。通过返回具有两个不同值(每个值的类型不同)的元组,与只能返回单一类型的单个值相比,该函数提供了关于其结果的更有用的信息。
[Note]元组对于简单的相关值组非常有用。它们不适合创建复杂的数据结构。如果您的数据结构可能更复杂,请将其建模为类或结构,而不是元组。
115.Optionals(可选链)
在可能没有值的情况下使用选项。可选表示两种可能性:要么有一个值,您可以打开可选来访问该值,要么根本就没有值。
[Note]optionals的概念在C或Objective-C中并不存在。Objective-C中最接近的东西就是从一个方法返回nil的能力,否则这个方法会返回一个对象,nil的意思是“没有一个有效的对象”。然而,这只适用于对象——它不适用于结构、基本C类型或枚举值。对于这些类型,Objective-C方法通常会返回一个特殊的值(比如NSNotFound)来表示没有值。这种方法假设方法的调用者知道有一个要测试的特殊值,并记得检查它。Swift的选项允许您指出任何类型都不存在值,而不需要特殊的常量。
下面是一个示例,演示如何使用选项来处理缺少值的情况。Swift的Int类型有一个初始化器,它试图将一个字符串值转换成一个Int值。然而,并不是每个字符串都可以转换成整数。字符串“123”可以转换为数值123,但是字符串“hello, world”没有明显的数值可以转换。
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber is inferred to be of type "Int?", or "optional Int"
因为初始化器可能失败,所以它返回一个可选的整型,而不是整型。问号表示它包含的值是可选的,这意味着它可能包含一些整型值,也可能根本不包含任何值。(它不能包含任何其他东西,比如Bool值或字符串值。它要么是一个整数,要么什么都不是。)
①nil
你设置一个可选的变量到一个没有价值的状态,通过分配它的特殊值nil:
var serverResponseCode: Int? = 404 // serverResponseCode包含一个实际的404整型值
serverResponseCode = nil // serverResponseCode现在不包含任何值
[Note]不能将nil与非可选的常量和变量一起使用。如果代码中的常量或变量需要在某些情况下处理没有值的情况,请始终将其声明为适当类型的可选值。
如果你定义了一个可选的变量而没有提供一个默认值,这个变量会自动为你设置为nil.
②If Statements and Forced Unwrapping(If语句并强制展开)
可以使用if语句通过将optional与nil进行比较来确定一个可选的值是否包含一个值。您可以使用“等于”操作符(==)或“不等于”操作符(!=)执行此比较。
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// Prints "convertedNumber contains some integer value."
一旦确定optional确实包含一个值,就可以通过在这个可选的名称后面添加感叹号(!)来访问它的底层值。感叹号有效地表达了,“我知道这个可选项肯定是有价值的;请使用它。这就是所谓的强制打开可选项的值:
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// Prints "convertedNumber has an integer value of 123."
[Note]尝试使用!若要访问不存在的可选值,将触发运行时错误。在使用之前一定要确保一个可选的包含一个非空值!强制打开其值。
③Optional Binding(可选的绑定)
您可以使用可选绑定来确定某个可选值是否包含某个值,如果包含,则使该值作为临时常量或变量可用。可选绑定可以与if和while语句一起使用,以检查可选语句中的值,并将该值提取为常量或变量,作为单个操作的一部分。
Write an optional binding for an if statement as follows:
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(possibleNumber)返回的可选的Int包含一个值,则将一个名为actualNumber的新常量设置为可选的值中包含的值。
如果转换成功,在If语句的第一个分支中就可以使用actualNumber常量。它已经用可选项中包含的值进行了初始化,因此不需要使用!访问其值的后缀。在本例中,仅使用actualNumber打印转换的结果。
您可以在可选绑定中同时使用常量和变量。如果您希望在If语句的第一个分支中操作actualNumber的值,那么您可以编写If var actualNumber,并且可选语句中包含的值将作为变量而不是常量可用。
您可以根据需要在一个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"
[Note]在if语句中使用可选绑定创建的常量和变量只能在if语句的主体中使用。相反,使用guard语句创建的常量和变量可以在guard语句之后的代码行中使用,如Early Exit中所述。
④Implicitly Unwrapped Optionals(隐式地打开可选)
您可以通过在要使其成为可选类型的类型后面放置感叹号(String!)而不是问号(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(感叹号)
您可以将隐式取消包装的可选项视为在使用可选项时允许自动取消包装。不是每次使用可选项时都在其名称后面加上感叹号,而是在声明可选项类型时在其类型后面加上感叹号。
[Note]If an implicitly unwrapped optional is nil and you try to access its wrapped value, you’ll trigger a runtime error. The result is exactly the same as if you place an exclamation mark after a normal optional that doesn’t contain a value.如果一个隐式未包装的可选项是nil,并且您试图访问它的包装值,那么您将触发一个运行时错误。结果与在不包含值的常规可选项后放置感叹号完全相同。
16.Error Handling(错误处理)
与可选项不同,可使用值的存在或不存在来传达函数的成功或失败,错误处理允许您确定失败的潜在原因,并在必要时将错误传播到程序的另一部分。
当一个函数遇到错误条件时,它会抛出一个错误。该函数的调用者可以捕获错误并做出适当的响应。
func canThrowAnError() throws {
// this function may or may not throw an error
}
A function indicates that it can throw an error by including the throws
keyword in its declaration. When you call a function that can throw an error, you prepend the try
keyword to the expression.当调用可能抛出错误的函数时,要在表达式前加上“try”关键字。
Swift automatically propagates errors out of their current scope until they’re handled by a catch
clause.Swift会自动将错误传播到当前范围之外,直到它们被“catch”子句处理为止。
do {
try canThrowAnError()
// no error was thrown
} catch {
// an error was thrown
}
do语句创建一个新的包含范围,允许将错误传播到一个或多个catch子句。
下面是一个如何使用错误处理来响应不同错误条件的例子:
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
在本例中,如果没有干净的餐具或缺少任何配料,makeASandwich()函数将抛出一个错误。因为makeASandwich()可能抛出一个错误,所以函数调用被封装在一个try表达式中。通过在do语句中包装函数调用,抛出的任何错误都将传播到提供的catch子句。
如果没有抛出错误,则调用eatASandwich()函数。如果抛出一个错误,并且该错误与SandwichError.outOfCleanDishes匹配。outOfCleanDishes()函数将被调用。如果抛出一个错误,并且该错误与SandwichError.missingIngredients 匹配。然后使用catch模式捕获的相关[String]值调用buygrocery(_:)函数。
17.Assertions and Preconditions(断言和先决条件)
断言和先决条件是在运行时进行的检查。在执行任何进一步的代码之前,使用它们来确保满足基本条件。如果断言或前置条件中的布尔条件计算结果为true,则代码执行照常进行。如果条件的计算结果为false,则程序的当前状态无效;代码执行结束,应用程序终止。
您可以使用断言和先决条件来表达您在编码时所做的假设和期望,因此您可以将它们作为代码的一部分。断言可以帮助您发现开发过程中的错误和不正确的假设,而前提条件可以帮助您检测生产中的问题。
除了在运行时验证您的期望之外,断言和先决条件也成为代码中有用的文档形式。与上面错误处理中讨论的错误条件不同,断言和前置条件不用于可恢复的或预期的错误。因为失败的断言或前置条件指示无效的程序状态,所以无法捕获失败的断言。
使用断言和前置条件并不能代替以不太可能出现无效条件的方式设计代码。但是,使用它们来强制执行有效的数据和状态会使应用程序在出现无效状态时更容易终止,这有助于使问题更容易调试。一旦检测到无效状态,就立即停止执行,这也有助于限制该无效状态造成的损害。
断言和前置条件的区别在于检查它们的时间:仅在调试构建中检查断言,但在调试和生产构建中都检查前置条件。在生产构建中,不计算断言中的条件。这意味着您可以在开发过程中使用任意数量的断言,而不会影响生产中的性能。
①Debugging with Assertions(调试与断言)
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// assert(_:_:file:line:) function from the Swift standard library.
// This assertion fails because -3 is not >= 0.
在本例中,如果age >= 0的计算结果为true,则代码执行将继续,也就是说,如果age的值是非负的。如果age的值是负数(如上面的代码所示),则age >= 0将计算为false,并且断言将失败,从而终止应用程序。
如果代码已经检查了条件,则使用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(执行先决条件)
只有条件可能为假,但是必须为真,代码才能继续执行,那么就使用先决条件。例如,使用先决条件来检查下标是否出界,或者检查函数是否传递了有效值。
通过调用预处理(::file:line:)函数来编写先决条件。
// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")
您还可以调用preconditionFailure(_:file:line:)函数来指示发生了故障.
[Note]如果以未检查模式(-Ounchecked)编译,则不会检查前置条件。编译器假设前提条件始终为真,并相应地优化代码。但是,无论优化设置如何,fatalError(:file:line:)函数总是会暂停执行。
您可以在原型设计和早期开发期间使用fatalError(:file:line:)函数创建尚未实现的功能存根,方法是将fatalError(“未实现的”)编写为存根实现。由于与断言或先决条件不同,致命错误永远不会优化,因此可以确保在遇到存根实现时执行始终会停止