swift语法新手快速入门,10分钟足以

编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:

print("Hello, world!")

如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要 main() 函数。你同样不需要在每个语句结尾写上分号。

这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解到。

数据类型

在Swift中,类型分为两类:

第一种是值类型,存放在栈区;该类型的每个实例持有数据的副本,并且该副本对于每个实例来说是独一无二的一份,比如结构体(struct)、枚举(enum)、元组(tuple)都是值类型。

典型的: struct,enum,tuple

常用的: Int, Double,Float,String,Array,Dictionary,Set ,其实他们都是用结构体实现的,也是值类型。

Int类型:用于表示整数,包括正整数和负整数。

let age: Int = 30
let numberOfStudents = 50


Double和Float类型:用于表示浮点数,即带有小数点的数值。Double提供更高的精度,而Float提供较低的精度。默认推断为Double类型

let pi: Double = 3.14159
let percentage: Float = 0.75


String类型:用于表示文本字符串。 

let message: String = "Hello, World!"
let name = "John Doe"


Bool类型:用于表示布尔值,只有两个可能的值:true和false。

let isLoggedin: Bool = true
let isAdmin = false


Array类型:用于表示有序列表,可以包含相同或不同类型的元素。

let numbers: [Int] = [1, 2, 3, 4, 5]
let names = ["John", "Jane", "Tom"]

Dictionary类型:用于表示无序的键值对集合,其中每个元素都有一个唯一的键和对应的值。

let person: [String: Any] = ["name": "John", "age": 30, "isMarried": false]
let scores = ["Math": 90, "English": 85, "Science": 95]

Set类型:用于表示无序且唯一的值的集合。

let uniqueNumbers: Set<Int> = [1, 2, 3, 4, 5]
let vowels: Set<Character> = ["a", "e", "i", "o", "u"]

Tuple类型:用于创建包含多个不同类型的值的复合数据类型。

let coordinates: (Double, Double) = (37.7749, -122.4194)
let personInfo = ("John", 30, true)

AnyAnyObject 的类型转换

Swift 为不确定类型提供了两种特殊的类型别名:

  • Any 可以表示任何类型,包括函数类型。

  • AnyObject 可以表示任何类类型的实例。

var things: [Any] = []

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

things 数组包含两个 Int 值,两个 Double 值,一个 String 值,一个元组 (Double, Double),一个 Movie 实例“Ghostbusters”,以及一个接受 String 值并返回另一个 String 值的闭包表达式。

你可以在 switch 表达式的 case 中使用 isas 操作符来找出只知道是 AnyAnyObject 类型的常量或变量的具体类型。下面的示例迭代 things 数组中的每一项,并用 switch 语句查找每一项的类型。有几个 switch 语句的 case 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值:

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as an Int")
    case 0 as Double:
        print("zero as a Double")
    case let someInt as Int:
        print("an integer value of \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("a positive double value of \(someDouble)")
    case is Double:
        print("some other double value that I don't want to print")
    case let someString as String:
        print("a string value of \"\(someString)\"")
    case let (x, y) as (Double, Double):
        print("an (x, y) point at \(x), \(y)")
    case let movie as Movie:
        print("a movie called \(movie.name), dir. \(movie.director)")
    case let stringConverter as (String) -> String:
        print(stringConverter("Michael"))
    default:
        print("something else")
    }
}

// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael

 

第二种是引用类型,存放在堆区;该类型的实例共享数据唯一的一份副本(在native层面说的话,就是该类型的每个实例都指向内存中的同一个地址),比如类(class)就是引用类型。

引用类型:class 和 闭包

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

简单值

使用 let 来声明常量,使用 var 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就可在多个地方使用。

var myVariable = 42
myVariable = 50
let myConstant = 42

常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。在上面的例子中,编译器推断出 myVariable 是一个整数类型,因为它的初始值是整数。

如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

练习

创建一个常量,显式指定类型为 Float 并指定初始值为 4。

值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。

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

练习

删除最后一行中的 String,错误提示是什么?

有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠(\)。例如:

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

练习

使用 \() 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。

使用三个双引号(""")来包含多行字符串内容。每行行首的缩进会被去除,只要和结尾引号的缩进相匹配。举个例子:

let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""

使用方括号 [] 来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

数组在添加元素时会自动变大。

shoppingList.append("blue paint")
print(shoppingList)

使用初始化语法来创建一个空数组或者空字典。

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

如果类型信息可以被推断出来,你可以用 [][:] 来创建空数组和空字典——比如,在给变量赋新值或者给函数传参数的时候。

shoppingList = []
occupations = [:]

控制流

使用 ifswitch 来进行条件操作,使用 for-inwhilerepeat-while 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。

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

if 语句中,条件必须是一个布尔表达式——这意味着像 if score { ... } 这样的代码将报错,而不会隐形地与 0 做对比。

你可以一起使用 iflet 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 nil 以表示值缺失。在类型后面加一个问号(?)来标记这个变量的值是可选的。

var optionalString: String? = "Hello"
print(optionalString == nil)

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

练习

optionalName 改成 nil,greeting 会是什么?添加一个 else 语句,当 optionalNamenil 时给 greeting 赋一个不同的值。

如果变量的可选值是 nil,条件会判断为 false,大括号中的代码会被跳过。如果不是 nil,会将值解包并赋给 let 后面的常量,这样代码块中就可以使用这个值了。

另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。

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

你还可以使用较短的代码解包一个值,并且对该被包装值使用相同的名称。

if let nickname {
	print("Hey, \(nickName)")
}

switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。

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

练习

删除 default 语句,看看会有什么错误?

注意 let 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 x

运行 switch 中匹配到的 case 语句之后,程序会退出 switch 语句,并不会继续向下运行,所以不需要在每个子句结尾写 break

你可以使用 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 (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
// 输出 "25"

练习

将 _ 替换成变量名,以确定哪种类型的值是最大的。

使用 while 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。

var n = 2
while n < 100 {
    n *= 2
}
print(n)

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

你可以在循环中使用 ..< 来表示下标范围。

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

使用 ..< 创建的范围不包含上界,如果想包含的话需要使用 ...

函数和闭包

使用 func 来声明一个函数,使用名字和参数来调用函数。使用 -> 来指定函数返回值的类型。

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

练习

删除 day 参数,在这个欢迎语中添加一个参数来表示今天的特价菜。

默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 _ 表示不使用参数标签。

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "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)

函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。

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

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

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

函数也可以当做参数传入另一个函数。

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)

函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的——你已经在嵌套函数的例子中看过了。你可以使用 {} 来创建一个匿名闭包。使用 in 将参数和返回值类型的声明与闭包函数体进行分离。

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

练习

重写闭包,对所有奇数返回 0。

有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都忽略。单个语句闭包会把它语句的值当做结果返回。

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在圆括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略圆括号。

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

对象和类

使用 class 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

练习

使用 let 添加一个常量属性,再添加一个接收一个参数的方法。

要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。

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 创建一个析构函数。

子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。

子类如果要重写父类的方法的话,需要用 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()

练习

创建 NamedShape 的另一个子类 Circle,构造器接收两个参数,一个是半径一个是名称,在子类 Circle 中实现 area()simpleDescription() 方法。

除了简单的存储属性,还有使用 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)

perimeter 的 setter 中,新值的名字是 newValue。你可以在 set 之后的圆括号中显式地设置一个名字。

注意 EquilateralTriangle 类的构造器执行了三步:

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

  2. 调用父类的构造器

  3. 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。

如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 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)

处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ?。如果 ? 之前的值是 nil? 后面的东西都会被忽略,并且整个表达式返回 nil。否则,可选值会被解包,之后的所有代码都会按照解包后的值运行。在这两种情况下,整个表达式的值也是一个可选值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

枚举和结构体

使用 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
let aceRawValue = ace.rawValue

练习

写一个函数,通过比较它们的原始值来比较两个 Rank 值。

默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,Ace 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 rawValue 属性来访问一个枚举成员的原始值。

使用 init?(rawValue:) 初始化构造器来从原始值创建一个枚举实例。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 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()

练习

Suit 添加一个 color() 方法,对 spadesclubs 返回 “black” ,对 heartsdiamonds 返回 “red” 。

注意在上面的例子中用了两种方式引用 hearts 枚举成员:给 hearts 常量赋值时,枚举成员 Suit.hearts 需要用全名来引用,因为常量没有显式指定类型。在 switch 里,枚举成员使用缩写 .hearts 来引用,因为 self 的值已经是一个 suit 类型。在任何已知变量类型的情况下都可以使用缩写。

如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着同一枚举成员不同实例的关联值可以不相同。你可以把关联值想象成枚举成员实例的存储属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。

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

练习

ServerResponse 和 switch 添加第三种情况。

注意 ServerResponse 的值在与 switch 的分支匹配时,日升和日落时间是如何从该值中提取出来的。

使用 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()

练习

写一个方法,创建一副完整的扑克牌,这些牌是所有 rank 和 suit 的组合。

并发性

使用 async 标记异步运行的函数

func fetchUserID(from server: String) async -> Int {
    if server == "primary" {
        return 97
    }
    return 501
}

您还可以通过在函数名前添加 await 来标记对异步函数的调用

func fetchUsername(from server: String) async -> String {
    let userID = await fetchUserID(from: server)
    if userID == 501 {
        return "John Appleseed"
    }
    return "Guest"
}

使用 async let 来调用异步函数,并让其与其它异步函数并行运行。 使用 await 以使用该异步函数返回的值。

func connectUser(to server: String) async {
    async let userID = fetchUserID(from: server)
    async let username = fetchUsername(from: server)
    let greeting = await "Hello \(username), user ID \(userID)"
    print(greeting)
}

使用 Task 从同步代码中调用异步函数且不等待它们返回结果

Task {
	await connectUser(to: "primary")
}
//Prints "Hello Guest, user ID 97"

协议和扩展

使用 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()
let bDescription = b.simpleDescription

练习

ExampleProtocol 再增加一个要求。你需要怎么改 SimpleClassSimpleStructure 才能保证它们仍旧遵循这个协议?

注意声明 SimpleStructure 时候 mutating 关键字用来标记一个会修改结构体的方法。SimpleClass 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。

使用 extension 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型。

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

练习

Double 类型写一个扩展,添加 absoluteValue 属性。

你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(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)
}

练习

将 printer name 改为 "Never Has Toner" 使 send(job:toPrinter:) 函数抛出错误。

可以使用多个 catch 块来处理特定的错误。参照 switch 中的 case 风格来写 catch

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

练习

do 代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个 catch 块进行接收?怎么使第二个和第三个 catch 进行接收呢?

另一种处理错误的方式使用 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")
print(fridgeIsOpen)

泛型

在尖括号里写一个名字来创建一个泛型函数或者类型。

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)

你也可以创建泛型函数、方法、类、枚举和结构体。

// 重新实现 Swift 标准库中的可选类型
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
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])

练习

修改 anyCommonElements(_:_:) 函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。

<T: Equatable><T> ... where T: Equatable 的写法是等价的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/627870.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

是德keysight N1911A与N1913A单通道功率计

Agilent N1911A和N1912A P系列单通道和双通道功率计以及N192XA传感器可提供宽带宽和高性能测量&#xff0c;这是确保用户的产品符合其功率规范所需要的。 P系列功率计具有30MHz视频带宽和每秒100M/s 的持续采样率&#xff0c;可进行快速、准确、可重复的功率测量。当这些功…

音视频入门基础:像素格式专题(2)——不通过第三方库将RGB24格式视频转换为BMP格式图片

音视频入门基础&#xff1a;像素格式专题系列文章&#xff1a; 音视频入门基础&#xff1a;像素格式专题&#xff08;1&#xff09;——RGB简介 音视频入门基础&#xff1a;像素格式专题&#xff08;2&#xff09;——不通过第三方库将RGB24格式视频转换为BMP格式图片 一、引…

抖音、快手、百度极速版挂机项目分享、看广告收益最多的软件!快手极速版挂机脚本,刷金币脚本、挂机项目、免费分享!

“看小说就能赚钱”、“刷视频就能赚钱”...... 号称动动手指就能赚钱的APP越来越多&#xff0c;比如各种极速版的APP、电子书APP、新闻资讯APP、搜索APP等等。 很多人也都乐此不疲&#xff0c;一天到晚就是“刷刷刷”。 最近一位“阿姨”晒各种“赚钱的APP”收入截图在业内传…

【LeetCode:23. 合并 K 个升序链表 + 链表 + 归并 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

正点原子FreeRTOS学习笔记——列表与列表项

目录 一、什么是列表和列表项 1、概念 2、FreeRTOS代码 &#xff08;1&#xff09;列表 &#xff08;2&#xff09;列表项 &#xff08;3&#xff09;迷你列表项 二、列表与列表项初始化 1、列表初始化 2、列表项初始化 三、列表插入与删除列表项 1、原理解释 2、…

Django使用

一、根目录下安装 pip install django 二、创建djiango项目 django-admin startproject 项目名称 三、创建app python manage.py startapp app名称 四、启动 python manage.py runserver 五、编写URL与视图关系&#xff0c;相对路径 1、manage.py&#xff08;见资源绑定…

(十)Python基础练习题一(50道选择题)#Python

本文整理了Python基础知识相关的练习题&#xff0c;共50道&#xff0c;适用于刚入门初级Python想巩固基础的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-Python一&#xff09;。 1&#xff09; 2&#xff09; 3&#xff09; 4&#xff09; 5&#xff09; 6…

Vue的学习 —— <vue组件>

目录 前言 正文 一、选项式API与组合式API 二、生命周期函数 1、onBeforeMount() 2、onMounted() 3、onBeforeUpdate() 4、onUpdated() 5、onBeforeUnmount() 6、onUnmounted() 三、组件之间的样式冲突 四、父组件向子组件传递数据 1、定义props 2、静态绑定props…

java内容快速回顾+SSM+SpringBoot简要概述

文章目录 java基础知识基本知识列表面对对象堆与栈的关系值修改与引用修改异常&#xff1a;错误异常 SSMspringMVCServletSpringMVC&#xff1a;基于 Servlet的 Spring Web 框架&#xff0c; spring控制反转 IoC(Inversion of Control)面向切面 Aop MybatisJDBCMybatis SpringB…

【启明智显技术分享】工业级HMI芯片--Model系列(Model3C/Model3/Model4)烧录操作指南

前言 「Model系列」芯片是启明智显针对工业、行业以及车载产品市场推出的系列HMI芯片&#xff0c;主要应用于工业自动化、智能终端HMI、车载仪表盘、串口屏、智能中控、智能家居、充电桩显示屏、储能显示屏、工业触摸屏等领域。此系列具有高性能、低成本的特点&#xff0c;支持…

失误删除也能救回,推荐前10款手机数据恢复软件!

在手机的日常使用中&#xff0c;不可避免的会误删数据&#xff0c;这些数据可能包括照片、视频、联系人、短信等重要信息。不过好在市面上有很多优秀的手机数据恢复软件&#xff0c;可以帮助我们从各种情况下恢复丢失的数据。 本文将为您推荐十大手机数据恢复软件&#xff0c;…

【全开源】JAVA同城圈子达人交友系统源码支持微信小程序+微信公众号+H5+APP

同城达人 精准匹配&#xff1a;系统通过用户填写的个人信息和兴趣爱好&#xff0c;运用智能算法进行精准匹配&#xff0c;推荐合适的同城朋友。多种互动方式&#xff1a;提供在线聊天、语音通话、视频交流等多种互动方式&#xff0c;让用户能够随时随地与朋友保持联系。本地生…

外贸营销脚本,自动化营销工具的制作!

在当今全球化的商业环境下&#xff0c;外贸行业面临着日益激烈的竞争&#xff0c;为了提高营销效率、降低成本并增加销售额&#xff0c;许多外贸企业开始寻求自动化营销的解决方案。 本文将深入探讨外贸自动化营销脚本与工具的制作方法&#xff0c;并分享五段实用的源代码&…

2023年数维杯国际大学生数学建模挑战赛C题人工智能生成文本的智能识别与检测解题全过程论文及程序

2023年数维杯国际大学生数学建模挑战赛 C题 人工智能生成文本的智能识别与检测 原题再现&#xff1a; 近年来&#xff0c;随着信息技术的飞速发展&#xff0c;人工智能的各种应用应运而生。典型应用包括机器人导航、语音识别、图像识别、自然语言处理和智能推荐等。其中&…

【class8】人工智能初步(图像识别-----卷积神经网络)

上节回顾 上节课&#xff0c;我们简单了解了图像识别和深度学习的相关知识。 快速回顾一下吧&#xff5e; A图像识别是以图像的主要特征为基础的。B. 图像分辨率决定图像的质量。 C&#xff0e; 像素是图像中的最小单位D. 在图像识别的原理上&#xff0c;计算机和人类在本质…

富唯智能复合机器人:CNC铝块上下料安全新标准

在CNC铝块加工过程中&#xff0c;上下料环节的安全问题一直是企业关注的焦点。富唯智能复合机器人的应用&#xff0c;为这一环节树立了新的安全标准。 传统的上下料方式往往依赖于人工操作&#xff0c;存在着较大的安全隐患。而富唯智能复合机器人采用先进的视觉识别技术和精准…

机器学习中常用的几种距离——欧式、余弦等

目录 一、欧式距离&#xff08;L2距离&#xff09;二、曼哈顿距离&#xff08;L1距离&#xff09;三、汉明距离四、余弦相似度 一、欧式距离&#xff08;L2距离&#xff09; &#xff08;1&#xff09;二维空间的距离公式&#xff08;三维空间的在这个基础上类推&#xff09;&…

【机器学习:IT行业的现在与未来】技术应用与趋势探索

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f381; 引言&#x1f300; 当前机器学习技术的深度剖析与实践展示&#x1f3af; 算法与架构的革新轨迹&#x1f4c8; 数据科学与基础设施的融合进化&#x1f512; 安全、隐私与伦理&#xff1a;技术的道德指南针&…

电子商务电商数据采集接口||电子商务市场数据采集方法,你学到了吗?

小刘从某职业院校电子商务专业毕业后&#xff0c;-直在某品牌电商部负责运营工作&#xff0c;近期&#xff0c;同班同学小王邀请小刘加入创业大军&#xff0c;共同开设网店&#xff0c;销售家乡的螃蟹、鲜虾、扇贝等生鲜水产。 运营经验丰富的小刘决定&#xff0c;在创业开始前…

需要无广告、结构化信息的搜索引擎?秘塔AI搜索,你的新选择

工欲善其事&#xff0c;必先利其器。 随着AI技术与各个行业或细分场景的深度融合&#xff0c;日常工作可使用的AI工具呈现出井喷式发展的趋势&#xff0c;AI工具的类别也从最初的AI文本生成、AI绘画工具&#xff0c;逐渐扩展到AI思维导图工具、AI流程图工具、AI生成PPT工具、AI…