Swift4 - The Basics

买了 DesignCode,今天正式开始研究 Swift4了。

Swift 4 官方文档

The Basics

Constants and Variables 常量和变量

常量的值一旦设定就不能改变,而变量的值可以随意更改。

Declaring Constants and Variables 声明常量和变量

  1. 常量和变量必须在使用前声明,用 关键字let来声明常量和关键字 var来声明变量。
    例:

    let maximumNumberOfLoginAttempts = 10
    var currentLoginAttempt = 0
    

    这两行代码可以被理解为:
    “声明一个名字是maximumNumberOfLoginAttempts的新常量,并给它一个值10。然后,声明一个名字是currentLoginAttempt的变量并将它的值初始化为0

  2. 可以在一行中声明多个常量或者多个变量,用逗号隔开:

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

Type Annotations 类型标注

当声明常量或者变量的时候可以加上 类型标注 (type annotation),说明常量或者变量中要存储的值的类型。通过在常量或变量名后面接一个冒号来写一个类型标注,后跟一个空格,后跟要使用的类型的名称。

例子给welcomeMessage 变量添加了类型标注,表示这个变量可以存储 String类型的值:
var welcomeMessage: String

声明中的冒号代表着“xx是xx类型”,所以这行代码可以被理解为:

  • 声明一个类型为 String,名字为welcomeMessage的变量
  • 类型为 String ”的意思是“可以存储任意 String 类型的值

可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double

一般来说很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型

Naming Constants and Variables 常量和变量的命名

常量与变量名不能包含空格,数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。

作者:Guards翻译组
链接:http://www.jianshu.com/p/13262bd2e453
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 现在是 "Bonjour!"

与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:

let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变

Printing Constants and Variables 输出常量和变量

print(_:separator:terminator:)

Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:

print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!

Comments 注释

单行注释: //

// 这是一个注释

多行注释: /* 开始,*/结束
/ 这是一个,多行注释 /

Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:

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

Semicolons 分号

Swift 并不强制要求你在每条语句的结尾处使用分号(;),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:

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

Integers 整数

整数就是没有小数部分的数字,整数可以是 有符号(正数、零和负数)或者无符号(正数、零)。

Integer Bounds 整数范围

以访问不同整数类型的minmax属性来获取对应类型的最小值和最大值:

let minValue = UInt8.min  // minValue 为 0,是 UInt8 类型
let maxValue = UInt8.max  // maxValue 为 255,是 UInt8 类型

Int

除非你需要特定长度的整数,一般来说使用 Int就够了

Uint

Swift 也提供了一个特殊的无符号类型 UInt,长度与当前平台的原生字长相同:

  • 在32位平台上,UInt 和 UInt32长度相同
  • 在64位平台上,UInt 和 UInt64长度相同

尽量不要使用UInt,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用Int,即使你要存储的值已知是非负的。统一使用Int可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断

Float

浮点数是有小数部分的数字,比如 3.141590.1-273.15

类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:

  • Double表示64位浮点数
  • Float表示32位浮点数

Double精确度很高,至少有15位数字,而Float只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都可以选择的情况下,将优先选择 Double

Type Safety and Type Inference 类型安全和类型推断

Swift 是一个类型安全(type-safe)的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个String,你绝对不可能错误地传进去一个Int,它会在编译你的代码时进行类型检查(type checks)

如果你没有显式指定类型,Swift 会使用类型推断(type inference)来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。

当推断浮点数的类型时,Swift 总是会优先选择 Double而不是Float。如果表达式中同时出现了整数和浮点数,会被推断为 Double:

let anotherPi = 3 + 0.14159  // anotherPi 会被推测为 Double 类型

原始值3 没有显式声明类型,而表达式中出现了一个浮点数,所以表达式会被推断为 Double类型

Numeric Literals 数值型字面量

整数字面量可以被写作:

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

Numeric Type Conversion 数值型类型转换

通常来讲,即使代码中的整数常量和变量已知非负,也请使用Int类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。

只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。

Integer Conversion 整数转换

不同整数类型的变量和常量可以存储不同范围的数字。Int8类型的常量或者变量可以存储的数字范围是-128-127,而UInt8类型的常量或者变量能存储的数字范围是0-255。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:

let cannotBeNegative: UInt8 = -1// UInt8 类型不能存储负数,所以会报错
let tooBig: Int8 = Int8.max + 1// Int8 类型不能存储超过最大值的数,所以会报错

常量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类型的初始化器并传入初始值的默认方式。

Integer and Floating-Point Conversion 整数和浮点数转换

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

let three = 3let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine  // pi 等于 3.14159,所以被推测为 Double 类型

这个例子中,常量three的值被用来创建一个Double类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。浮点数到整数的反向转换同样行,整数类型可以用Double或者 Float类型来初始化:

let integerPi = Int(pi)// integerPi 等于 3,所以被推测为 Int 类型

当用浮点类型来初始化一个新的整数值时,浮点值会被截断。也就是说4.75会变成4-3.9会变成-3

Type Aliases 类型别名

类型别名type aliases就是给现有类型定义另一个名字。你可以使用typealias关键字来定义类型别名

Booleans 布尔值

Swift 有一个基本的布尔Boolean类型,叫做Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,true和false:

let orangesAreOrange = truelet 
turnipsAreDelicious = false

Tuples 元组

元组tuples把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。

Note:元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。

Optionals 可选类型

值可能不存在的情况下,你可以使用可选类型。 一个可选类型代表两种可能性:要么它有值(你可以通过解包来访问该值), 或者没有值

一个对象要么返回确定的值要么返回nil,其中nil表示“对象不存在”。

Nil

通过赋值nil,你可以将一个可选类型设置为无值状态:

var serverResponseCode:Int? = 404
// serverResponseCode包含一个具体的Int值,即404
serverResponseCode = nil
// serverResponseCode现在不包含任何值

nil不能和非可选类型的常量和变量一起使用。 如果你的代码中的常量或变量在某些情况下需要以没有值的状态运行,那你应该始终将其声明为可选类型。

如果你定义了一个可选类型的变量, 但是不设置默认值,那么该变量的值将自动设置为nil

var surveyAnswer:String?
// surveyAnswer自动设置为nil

Swift中的nil不同于OC中的nil。 在OC中,nil 是一个指向不存在的对象的指针。 在Swift中,nil不是指针, 它是一个不存在值的特定类型的。 任何数据类型的可选类型都可以设置为 nil,而不仅仅是对象类型。

If Statements and Forced Unwrapping If语句和强制解包

你可以通过使用if语句比较可选类型和nil,进而确定这个可选类型是否包含具体的值。你可以使用等于 运算符== 或 不等于 运算符!=执行比较。

如果一个可选类型有一个值,即它被认为是“不等于”nil

if convertedNumber != nil {
    print(“convertedNumber包含一些整数值”)
}
//打印结果:“convertedNumber包含一些整数值”。

一旦你确定可选类型确实有值,你可以通过在可选类型名称的末尾添加感叹号(!)来访问其包含的值。这个感叹号如同在宣称:“ 我知道这个可选类型肯定有值,请使用它。” 这被称为可选类型的强制解包:

if convertedNumber != nil {
    print(“convertedNumber的值为\(convertedNumber!)”)
}
// 打印结果: “convertedNumber的值为123”

使用 (!) 时,访问不存在值的可选类型会触发运行错误, 所以在使用(!) 强制解包前, 要始终确保可选类型包含了非空的值。

Optional Binding 可选绑定

你可以使用可选绑定来确定可选类型是否包含值,如果这样做,你必须把这个绑定的值用作临时常量或变量。可选绑定可以与if-while 语句一起使用,以检查可选类型中的值,并作为这个操作的一部分将该值提取为常量或变量。if-while语句在控制流(Control Flow)中有更详细的描述。

if语句编写可选绑定如下所示:

  if let constantName = someOptional {
    //statements
}

你可以使用可选绑定重写 Optionals 部分的possibleNumber示例,而不是使用强制解包:

if let actualNumber = Int(possibleNumber){
     print("\"\(possibleNumber)\"的整数值为\(actualNumber)")
} else {
     print("\"\(possibleNumber)\"无法转换为Int类型")
}
// 打印结果: ”123“的整数值为123“

这段代码可以这样理解:
“如果Int(possibleNumber)方法返回的可选Int包含一个值,创建一个名为actualNumber的新常量并设置其值为可选类型包含的值。”
如果转换成功,则常量actualNumber可用于if语句的第一个分支中。它已经通过可选类型中的值初始化了,所以没有必要使用!后缀访问其值。在这个例子中,actualNumber只简单地用于打印转换后的结果。

Implicitly Unwrapped Optionals 隐式解包可选类型

使用可选类型意味着允许常量或变量可以“无值”。使用if语句可以查看可选类型的值是否存在,并且可以使用可选绑定解包以访问存在的可选类型值。

在设置了初始值后,有时从程序的结构中可以清楚地看到,可选类型将始终有值。在这种情况下,每次访问可选类型的值时没有必要进行检查和解包,因为我们可以安全地假定任何情况下它都是有值的。

这种可选类型被定义为隐式解包可选类型。通过在想要设置为可选类型的参数后添加一个感叹号(String!),而不是一个问号(String?),你就可以得到一个隐式解包的可选类型。(译者注: 即隐式解包即常说的 强制解包)

隐式解包可选类型是一个常规选择,但也可以像非可选类型一样使用,无需在每次访问时进行解包。 以下示例展示了当访问可选String类型与隐式解包可选String类型的一个具体String值时, 两者的差异:

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 要求加上!来解包

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要加上!也可以解包

如果一个隐式解包可选类型的值为nil,当你尝试访问其值时则会触发运行时错误。 其原因与给一个值为空的常规可选类型添加感叹号的情形完全相同。

你仍然可以将隐式解包可选类型视为常规的可选类型,以检查它是否包含一个值:

if assumedString != nil {
    print(assumedString)
}
// 打印结果: "An implicitly unwrapped optional string.”

还可以使用隐式解包可选类型与可选绑定,在单个语句中检查和解包其值:

if let definiteString = assumedString {
    print(definiteString)
}
// 打印结果: "An implicitly unwrapped optional string.”

Error Handling 错误处理

可以使用错误处理来应对你的程序在执行过程中可能遇到的错误。

与可选类型相反,错误处理可以通过值的存在或不存在来表示一个function的成功或失败,允许你确定error的根本原因,如有必要,它还能将error传送到程序的另一部分。

当方法遇到错误条件时,会抛出一个error。 该方法的调用者可以捕获错误并作出适当的响应。

func canThrowAnError() throws {
     //这个函数可能会抛出一个错误
}

一个函数通过在其声明中包含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 SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在此示例中,如果没有干净的碗碟或 缺少食材 ,那么makeASandwich()方法就会抛出error。我们将该函数的调用包装在try表达式中。 通过do语句中包装并调用函数,抛出的所有错误将被传送到catch语句进行处理。

如果没有错误发生,那么eatASandwich()方法就会被调用。 如果抛出一个错误并且匹配到SandwichError.outOfCleanDishes,那么washDishes()函数将会被调用。 如果抛出的错误匹配到SandwichError.missingIngredients,则会调用buyGroceries(_ :)方法,并把catch捕获的关联值(String类型)传给它。_

Assertions and Preconditions 断言和先决条件

断言和先决条件是会在运行时进行的检查手段。在执行任何其他代码之前,你可以使用它们来确保基本条件已经满足。如果断言或前提条件中的布尔条件为true,则代码照常执行。如果条件为false,那么程序当前处于无效状态;代码会结束执行,你的应用程序也会被终止。

写代码时,使用断言和先决条件你可以表达出你需要的期望条件,因此你可以将它们作为代码的一部分。在开发过程中,断言可帮助你发现错误和不正确的假设条件,先决条件可帮助你检测发布后的问题。除了在运行时验证你的期望条件外,断言和先决条件也是代码中一种有效的文档形式。与先前在错误处理中讨论的错误条件不同,断言和先决条件不用于可恢复的或期望的错误,因为失败的断言或先决条件表示无效的程序状态,所以无法捕获失败的断言。

使用断言和先决条件并不意味着在你的代码中无效条件就一定不会出现。 然而,使用它们来强制执行有效的数据,会使你的应用程序在错误发生并被强制终止时,更具备可监测性,而且有助于调试。 一旦检测到无效状态,停止执行也有助于将其引起的损坏最小化。

断言和先决条件之间的区别在于被执行的时间点有所不同:断言仅在调试版本中执行,但在调试和发布版本中先决条件都会被执行。 在发布版本中,断言内的条件不再被执行。 这意味着你可以在开发过程中使用尽可能多地使用断言,因为这并不会影响发布版本的性能。

Debugging with Assertions 用断言进行调试

通过从Swift标准库调用assert(_:_:file:line:)方法,你写下一个断言。 你将一段文字和一个结果为truefalse的表达式赋值给这个方法,如果表达式的结果为false,则显示这段文字。 例如:

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

在这个例子中,如果age\> = 0的值为true,那么代码继续执行,即age为正值。 如果age为负值,如上面的代码所示,age\> = 0false,断言失败,终止应用程序。

可以省略断言发布的文字——例如当它只是一个单调的重复条件。

assert(age >= 0)

Enforcing Preconditions 执行先决条件

当条件有可能为false的时候使用先决条件,但是必须确保你的代码会继续执行。 例如,你可以使用先决条件来判断下标是否越界,或者检查方法是否接收到一个有效的值。

通过调用precondition(_:_:file:line:)方法你可以编写先决条件。 你需要给此方法赋值一个表达式,其计算结果为truefalse,如果条件的结果为false,则显示一段文字(也是由你赋值)。 例如:

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