此页面由 Cloud Translation API 翻译。
Switch to English

快速浏览

改编自Swift.org上的原始A Swift Tour 。原始内容是由Apple Inc.创作的。根据国际知识共享署名4.0(CC BY 4.0)许可获得许可
在TensorFlow.org上查看 在GitHub上查看源代码

传统建议使用新语言的第一个程序应打印单词“ Hello,world!”。屏幕上。在Swift中,这可以单行完成:

 print("Hello, world!")
 
Hello, world!

如果您使用C或Objective-C编写代码,则此语法看起来很熟悉-在Swift中,这行代码是完整的程序。您不需要导入单独的库来实现输入/输出或字符串处理之类的功能。在全局范围内编写的代码用作该程序的入口点,因此您不需要main()函数。您也不需要在每个语句的末尾写分号。

本教程通过向您展示如何完成各种编程任务,为您提供了足够的信息来开始在Swift中编写代码。如果您不了解某些内容,请不要担心,本教程的其余部分将详细介绍本教程中介绍的所有内容。

简单的价值观

使用let来创建常量,使用var来生成变量。常量的值不需要在编译时就知道,但是必须为它赋值一次。这意味着您可以使用常量来命名一次确定但在许多地方使用的值。

 var myVariable = 42
myVariable = 50
let myConstant = 42
 

常量或变量的类型必须与要分配给它的值的类型相同。但是,您不必总是显式地编写类型。在创建常量或变量时提供一个值,可使编译器推断其类型。在上面的示例中,编译器推断myVariable是整数,因为其初始值是整数。

如果初始值不能提供足够的信息(或者没有初始值),请通过在变量后写一个类型(以冒号分隔)来指定类型。注意:对Float使用Double而不是Float可以提高精度,这是Swift中的默认浮点类型。

 let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
 
 // Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.
 

值永远不会隐式转换为另一种类型。如果需要将值转换为其他类型,请显式创建所需类型的实例。

 let label = "The width is "
let width = 94
label + String(width)
 
"The width is 94"

 // Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?
 

有一种甚至更简单的方法来在字符串中包含值:在括号中写值,并在括号前写反斜杠(``)。例如:

 let apples = 3
"I have \(apples) apples."
 
"I have 3 apples."

 let oranges = 5
"I have \(apples + oranges) pieces of fruit."
 
"I have 8 pieces of fruit."

 // Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.
 

对于占用多行的字符串,请使用三个双引号( """ )。只要每个引号行的开头与右引号的缩进匹配,都将删除该缩进。例如:

 let quotation = """
    Even though there's whitespace to the left,
    the actual lines aren't indented.
        Except for this line.
    Double quotes (") can appear without being escaped.

    I still have \(apples + oranges) pieces of fruit.
    """
print(quotation)
 
Even though there's whitespace to the left,
the actual lines aren't indented.
    Except for this line.
Double quotes (") can appear without being escaped.

I still have 8 pieces of fruit.

使用方括号( [] )创建数组和字典,并通过在方括号中写入索引或键来访问其元素。最后一个元素之后允许使用逗号。

 var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
occupations
 
▿ 3 elements
  ▿ 0 : 2 elements

    - key : "Malcolm"
    - value : "Captain"
  ▿ 1 : 2 elements
    - key : "Jayne"
    - value : "Public Relations"
  ▿ 2 : 2 elements
    - key : "Kaylee"
    - value : "Mechanic"

数组随着添加元素而自动增长。

 shoppingList.append("blue paint")
shoppingList
 
▿ 5 elements

  - 0 : "catfish"
  - 1 : "bottle of water"
  - 2 : "tulips"
  - 3 : "blue paint"
  - 4 : "blue paint"

要创建一个空数组或字典,请使用初始化程序语法。

 let emptyArray = [String]()
let emptyDictionary = [String: Float]()
 

如果可以推断类型信息,则可以将空数组写为[] ,将空字典写成[:] —例如,当您为变量设置新值或将参数传递给函数时。

 shoppingList = []
occupations = [:]
 

控制流

使用ifswitch进行条件创建,并使用for - inforwhilerepeat - while进行循环。条件或循环变量的括号是可选的。身体周围需要支撑。

 let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
teamScore
 
11

if语句中,条件必须是布尔表达式-这意味着诸如if score { ... }代码是错误,而不是与零的隐式比较。

您可以使用iflet一起使用可能缺少的值。这些值表示为可选值。可选值包含一个值或包含nil来指示缺少一个值。在值的类型后写一个问号( ? ),以将该值标记为可选。

 var optionalString: String? = "Hello"
optionalString == nil
 
false

 var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
greeting
 
"Hello, John Appleseed"

 // Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.
 

如果可选值为nil ,则条件为false并且括号中的代码将被跳过。否则,将对可选值进行解包,并在let之后let其分配给常量,这将使解包后的值在代码块内可用。

处理可选值的另一种方法是使用??提供默认值。操作员。如果缺少可选值,则使用默认值。

 let nickName: String? = nil
let fullName: String = "John Appleseed"
"Hi \(nickName ?? fullName)"
 
"Hi John Appleseed"

开关支持任何类型的数据和各种各样的比较操作-它们不限于整数和相等性测试。

 let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}
 
Is it a spicy red pepper?

 // Experiment:
// Try removing the default case. What error do you get?
 

请注意如何在模式中使用let来将与模式的该部分匹配的值分配给常量。

在匹配的switch case中执行代码后,程序将从switch语句退出。执行不会继续到下一个案例,因此无需在每个案例代码的末尾显式地退出开关。

您使用for - in超过项目在一本词典提供一对名称的使用为每个键-值对迭代。字典是无序集合,因此它们的键和值以任意顺序进行迭代。

 let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
largest
 
25

 // Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.
 

使用while重复代码块,直到条件发生变化。循环的条件可以在末尾,以确保循环至少运行一次。

 var n = 2
while n < 100 {
    n = n * 2
}

n
 
128

 var m = 2
repeat {
    m = m * 2
} while m < 100

m
 
128

您可以使索引保持循环状态-通过使用..<来形成一系列索引,或者通过编写显式的初始化,条件和增量。这两个循环执行相同的操作:

 var total = 0
for i in 0..<4 {
    total += i
}

total
 
6

使用..<可以使范围忽略其上限值,使用...可以使范围包括两个值。

功能和关闭

使用func声明一个函数。通过在函数名称后加上括号中的参数列表来调用该函数。使用->将参数名称和类型与函数的返回类型分开。

 func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet(name: "Bob", day: "Tuesday")
 
"Hello Bob, today is Tuesday."

 // Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.
 

默认情况下,函数使用其参数名称作为其参数的标签。在参数名称之前写一个自定义参数标签,或写_以不使用任何参数标签。

 func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
 
"Hello John, today is Wednesday."

使用元组生成复合值,例如,从函数返回多个值。元组的元素可以通过名称或数字来引用。

 func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    
    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }
    
    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
 
120
120

函数可以嵌套。嵌套函数可以访问在外部函数中声明的变量。您可以使用嵌套函数将代码组织为长函数或复杂函数。

 func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()
 
15

函数是一流的类型。这意味着一个函数可以返回另一个函数作为其值。

 func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)
 
8

一个函数可以将另一个函数作为其参数之一。

 func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
 
true

函数实际上是闭包的一种特殊情况:可以稍后调用的代码块。闭包中的代码可以访问在创建闭包的范围内可用的变量和函数之类的内容,即使闭包在执行时位于不同的范围内,您也已经看到了嵌套函数的示例。您可以使用大括号( {} )将代码括起来,以编写没有名称的闭包。使用in分隔参数并从主体返回类型。

 numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
 
▿ 4 elements

  - 0 : 60
  - 1 : 57
  - 2 : 21
  - 3 : 36

 // Experiment:
// Rewrite the closure to return zero for all odd numbers.
 

您有几种选择可以更简洁地编写闭包。当已知闭包的类型(例如委托的回调)时,可以省略其参数的类型,返回类型或两者。单条语句闭包隐式返回其唯一语句的值。

 let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
 
[60, 57, 21, 36]

您可以通过数字而不是名称来引用参数-这种方法在很短的闭包中特别有用。作为最后一个参数传递给函数的闭包可以在括号后立即显示。如果闭包是函数的唯一参数,则可以完全省略括号。

 let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
 
[20, 19, 12, 7]

对象和类

使用class及其后的名称来创建一个类。类中的属性声明与常量或变量声明的编写方式相同,只不过它是在类的上下文中编写的。同样,方法和函数声明的编写方式相同。

 class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
 
 // Experiment:
// Add a constant property with `let`, and add another method that takes an argument.
 

通过在类名称后加上括号来创建类的实例。使用点语法访问实例的属性和方法。

 var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
 

此版本的Shape类缺少重要的东西:创建实例时用于设置该类的初始化程序。使用init创建一个。

 class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
 

注意,如何使用self来区分name属性和初始化程序的name参数。创建类的实例时,初始化函数的参数像函数调用一样传递。每个属性都需要分配一个值-在其声明中(如numberOfSides )或在初始化程序中(如name )。

如果需要在释放对象之前执行一些清理操作,请使用deinit创建一个deinit

子类在其类名之后包括其超类名,并用冒号分隔。不需要类继承任何标准根类,因此您可以根据需要包含或忽略超类。

覆盖超类的实现的子类上的方法被标记为override编译器将偶然地override方法而不override的错误检测为错误。编译器还会检测带有override方法,这些方法实际上不会覆盖超类中的任何方法。

 class Square: NamedShape {
    var sideLength: Double
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
    
    func area() -> Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
 
"A square with sides of length 5.2."

 // Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
//   arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.
 

除了存储的简单属性外,属性还可以具有getter和setter。

 class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
 
9.3
3.3000000000000003

perimeter设置器中,新值具有隐式名称newValue 。您可以在set之后在括号中提供一个明确的名称。

请注意, EquilateralTriangle类的初始化程序具有三个不同的步骤:

  1. 设置子类声明的属性的值。

  2. 调用超类的初始化程序。

  3. 更改超类定义的属性的值。此时,也可以完成使用方法,getter或setter的任何其他设置工作。

如果不需要计算属性,但仍然需要提供在设置新值之前和之后运行的代码,请使用willSetdidSet 。只要提供的代码在初始化程序之外更改,就可以运行您提供的代码。例如,下面的类确保其三角形的边长始终与其正方形的边长相同。

 class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
 
10.0
10.0
50.0

使用可选值时,您可以编写?在操作之前,例如方法,属性和下标。如果前值?nil ,之后的一切?被忽略,整个表达式的值为nil 。否则,将取消包装可选值,以及?之后的所有内容?作用于展开的值。在这两种情况下,整个表达式的值都是一个可选值。

 let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
optionalSquare?.sideLength
 
▿ Optional<Double>

  - some : 2.5

枚举和结构

使用enum创建一个枚举。像类和所有其他命名类型一样,枚举可以具有与之关联的方法。

 enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
 
ace
1

 // Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.
 

默认情况下,Swift会分配从零开始的原始值,并每次递增一,但是您可以通过显式指定值来更改此行为。在上面的示例中,Ace显式地指定为原始值1 ,其余原始值按顺序分配。您还可以使用字符串或浮点数作为枚举的原始类型。使用rawValue属性访问枚举案例的原始值。

使用init?(rawValue:)初始化程序从原始值创建枚举的实例。它返回与原始值匹配的枚举大小写,或者在没有匹配的Rank返回nil

 if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}
 

枚举的大小写值是实际值,而不仅仅是写原始值的另一种方法。实际上,在没有有意义的原始价值的情况下,您不必提供原始价值。

 enum Suit {
    case spades, hearts, diamonds, clubs

    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
 
 // Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.
 

请注意上面引用Hearts案例的两种方式:当给hearts常量分配值时,枚举案例Suit.Hearts以其全名引用,因为常量没有指定显式类型。在交换机内部,枚举用缩写形式表示.Hearts因为已经知道self的值是.Hearts的。只要知道值的类型,就可以使用缩写形式。

如果枚举具有原始值,则将这些值确定为声明的一部分,这意味着特定枚举用例的每个实例始终具有相同的原始值。枚举用例的另一种选择是具有与用例关联的值-这些值是在创建实例时确定的,并且对于枚举用例的每个实例它们可以不同。您可以认为关联值的行为类似于枚举案例实例的存储属性。

例如,考虑从服务器请求日出和日落时间的情况。服务器要么以请求的信息作为响应,要么以错误的描述作为响应。

 enum ServerResponse {
    case result(String, String)
    case failure(String)
}

let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure...  \(message)")
}
 
Sunrise is at 6:00 am and sunset is at 8:09 pm.

 // Experiment:
// Add a third case to `ServerResponse` and to the switch.
 

请注意,如何将ServerResponse值中的日出和日落时间提取出来,作为将值与切换案例进行匹配的一部分。

使用struct创建结构。结构支持许多与类相同的行为,包括方法和初始化程序。结构和类之间最重要的区别之一是,结构在代码中传递时始终会被复制,而类是通过引用传递的。

 struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
 
 // Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.
 

协议和扩展

使用protocol声明协议。

 protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}
 

类,枚举和结构都可以采用协议。

 class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
 
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
b.simpleDescription
 
"A simple structure (adjusted)"

 // Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?
 

请注意,在SimpleStructure的声明中使用了mutating关键字来标记修改结构的方法。 SimpleClass的声明不需要其任何标记为mutation的方法,因为类上的方法始终可以修改该类。

使用extension将功能添加到现有类型中,例如新方法和计算属性。您可以使用扩展将协议一致性添加到在其他地方声明的类型,甚至添加到从库或框架导入的类型。

 extension Int: ExampleProtocol {
    public var simpleDescription: String {
        return "The number \(self)"
    }
    public mutating func adjust() {
        self += 42
    }
}
7.simpleDescription
 
"The number 7"

 // Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.
 

您可以像使用其他任何命名类型一样使用协议名称,例如,创建具有不同类型但都符合一个协议的对象的集合。当使用类型为协议类型的值时,协议定义之外的方法不可用。

 let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
 
"A very simple class.  Now 100% adjusted."

 // Uncomment to see the error.
// protocolValue.anotherProperty
 

即使变量protocolValue的运行时类型为SimpleClass ,编译器也将其视为ExampleProtocol的给定类型。这意味着除了协议一致性之外,您不能意外访问该类实现的方法或属性。

错误处理

您可以使用采用Error协议的任何类型来表示错误。

 enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}
 

throw抛出一个错误,并throws来标记,可以抛出一个错误的功能。如果在函数中引发错误,该函数将立即返回,并且调用该函数的代码将处理该错误。

 func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}
 

有几种处理错误的方法。一种方法是使用do-catch 。在do块中,您可以在前面编写try来标记可能引发错误的代码。在catch块内,错误会自动被赋予error名称,除非您给它改了名称。

 do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
 
Job sent

 // Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.
 

您可以提供多个处理特定错误的catch块。您在catch后编写模式,就像在切换后进行case

 do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
 
Job sent

 // Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?
 

处理错误的另一种方法是使用try?将结果转换为可选的。如果该函数引发错误,则将丢弃特定错误,并且结果为nil 。否则,结果是一个可选值,其中包含函数返回的值。

 let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
 

使用defer编写一个代码块,该代码块在函数中所有其他代码之后,就在函数返回之前执行。无论函数是否引发错误,都将执行代码。您可以使用defer彼此相邻编写设置和清除代码,即使它们需要在不同的时间执行。

 var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
fridgeIsOpen
 
false

泛型

在尖括号内写一个名称,以构成通用函数或类型。

 func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
 
▿ 4 elements

  - 0 : "knock"
  - 1 : "knock"
  - 2 : "knock"
  - 3 : "knock"

您可以使函数和方法以及类,枚举和结构形成通用形式。

 // Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
print(possibleInteger)
 
some(100)

在类型名称后使用where来指定要求列表,例如,要求类型实现协议,要求两个类型相同或要求一个类具有特定的超类。

 func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])
 
true

编写<T: Equatable>与编写<T> ... where T: Equatable相同, <T> ... where T: Equatable