Swift 6 学习笔记(一)A Swift Tour

这个坑是新开的一个有关 swift 6 的学习笔记系列,因为我近期需要在 Apple Vision OS 上进行具身遥操作应用的开发,需要用到 swift 和 swiftUI 这两个开发语言,在此之前我完全没有接触过 swift,也没有在 iOS 平台上进行过开发,因此这个系列博客是我个人的一个纯零基础笔记,和其他的几个系列博客相比,可能中间存在一些错误。

这篇博客的主要内容是官方教程中的《A Swift Tour》 部分。

该系列博客的前期主要是在 MacOS + VSCode 上学习,后面需要构建工程后就会转移到 MacOS + XCode,所以先确保自己的环境中已经安装了 Swift 6.1.2 版本,同时 VSCode 中安装了 Swift 插件。

【Note】:由于 Apple 会删除旧版本的官方教程,因此这个博客是基于当前最新的 Swift 版本推进的,5.X 和 6.X 在很多地方都存在差异,如果你明确需要使用 5.X 的版本建议买实体书。

可以使用下面的命令将 Swift 更新到最新版本:

$ curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg && \
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory && \
~/.swiftly/bin/swiftly init --quiet-shell-followup && \
. ${SWIFTLY_HOME_DIR:-~/.swiftly}/env.sh && \
hash -r

然后确定当前使用的 swift 版本:

$ swift --version

Apple Swift version 6.1.2 (swift-6.1.2-RELEASE)
Target: arm64-apple-macosx15.0

1. Hello World

所有编程语的第一个 case 都是 “Hello World”:

print("Hello World")

在 Swift 中,这行代码就是一个完整的程序,无需导入单独的库来实现输出文本或处理字符串等功能。在全局作用域中编写的代码将用作程序的入口点,因此不需要 main() 函数,而每个语句末尾添加分号也是可有可无的。

这里比较困惑的是官方教程中没有说怎么运行,估计 Apple 打算让所有人用 XCode 开发,点击 “Play” 按钮运行,但因为我这用的是 VSCode 编辑器,所以还可以通过终端来执行这个文件,将这个文件保存为 deom.swift 然后在终端执行下面的命令:

$ swift demo.swift

Hello World

后面的所有代码都用上面命令行方式运行。


2. 简单变量

2.1 常量与变量

使用 let 来创建常量,使用 var 来创建变量。常量的值在编译时无需确定,但必须赋值一次,并且在全局上都依旧可用。

var myVariable = 42     // 变量
myVariable = 50
let myConstant = 43     // 常量
myConstant = 40			// 报错:常量不可修改

常量或变量必须与要赋给它的值具有相同的类型,但不必总是明确指定类型,创建时提供值可以让编译器推断其类型,在上面的示例中,编译器推断 myVariable 是一个整数。

如果初始值没有提供足够的信息(或者没有初始值)需要在变量后写上类型,并用冒号分隔,如下面的代码

let implicitInteger = 70        // 自动推导为 Int 类型
let implicitDouble = 70.0   	// 自动推导为 Double 类型
let explicitDouble: Double = 70 // 显式声明为 Double 类型
let var1:Int, var2:Int;			// 定义好变量但暂时不使用

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

let label = "The width is "
let width = 94
let widthLabel = label + String(width)	// 需要显示进行变量转换

2.2 字符串中的运算

使用 () 可以在字符串中包含涉及计算的部分,有点类似 python 语法中的字符串运算,但更直接:

let apples = 10		# 常量
var oranges = 5		# 变量

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

print(quotation)		// I still have 15 pieces of fruit.

2.3 数组与字典

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

数组:

var fruits = ["strawberries", "limes", "tangerines"]
fruits[1] = "grapes"    		// 使用下标修改元素
fruits.append("blueberries")	// 使用append追加元素

字典:

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

occupations["Jayne"] = "Public Relations"   // 向字典中追加元素
print(occupations)      // ["Malcolm": "Captain", "Jayne": "Public Relations", "Kaylee": "Mechanic"]

清空元素:

fruits = []
occupations = [:]

如果先要给 空数组空字典 分配新的值需要明确数据类型

let new_fruits:[String] = []
let new_occupations:[String:String] = [:]

3. 流程控制

使用 ifswitch 语句创建条件语句,使用 for-inwhilerepeat-while 语句创建循环。条件或循环变量周围的括号是可选的,但语句主体周围的括号是必需的。

3.1 for-in 循环

这个 for-in 循环很像 C++ 的形式,需要用 {} 限定其作用域。

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0

for score in individualScores{
    if score > 50{
        teamScore += 3
    }else{
        teamScore += 1
    }
}

print(teamScore)

在 6.X 版本中支持直接在代码中使用 emoji 同样也支持变量的条件定义,但在 5.X 版本中不支持,下面的代码在实用条件定义的同时也用到了 emoji:

let teamScore = 15
let scoreDecortation = if teamScore > 10 {
    "🎉"
}else{
    ""
}

print("Score: ", teamScore, scoreDecortation)
// Score:  15 🎉

3.2 if let 条件判断

下面这部分的官方 demo 在说明上有些不足,对初学者不太友好,没有很好的解释什么是 可选类型。Swift 明确区分了可选类型和明确类型,下面以字符串类型举例。

如果你学过 C++ 那么会发现所有对象都是一个明确类型,你不能给一个 std::string 对象赋值为 null,但有时候又需要表示函数并没有获得字符串,如果直接用空字符串 "" 表示则会丢失一种情况:对方真的返回了一个空字符串,那么最常见的方法就是再给函数传入一个变量或者返回一个 pair:

std::string RequestFunction(std::string request, bool& is_responsed);
std::pair<bool, std::string> RequestFunction(std::string request);

而在 Swift 中就不用这么麻烦,你可以在定义变量的时候带上一个 ? 表示这个变量可以是一个空对象 nil 即 C++ 中的 null,但不限于字符串,任何数据类型都可以通过这个方式明确其当前没有值:

var optionalString: String? = "Hello"	// 定义一个可选对象,类型是 String
print(optionalString == nil)			// false
optionalString = nil					// 将这个对象赋值为空
print(optionalString == nil)			// true
optionalString = ""						// 赋值为空字符串
print(optionalString == nil)			// false, 空字符串不是空对象

但是,如果数据类型后面没有 ? 则表示这是一个明确对象,不能进行 nil 操作:

var explicitString: String = ""
print(explicitString == nil)		// Error 报错
explicitString = nil				// Error 报错

同理,将上面的对象换成 IntDouble 也是可以,有了上面的基础再去理解官方教程就轻松多了:

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

var optionalName: String? = "John Appleseed"
var greeting = "Hello"

if let name = optionalName{ // 如果 optionalName 非 nil 则给 greeting 赋值
    greeting = "Hello \(name)"
}
print(greeting)
// false
Hello John Appleseed

可以通过 ?? 来判断可选值和明确值内容是否相同:

let nickname: String? = nil
let fullname: String = "John Appleseed"

let informalGreeting = "Hi \(nickname ?? fullname)"
print(informalGreeting)
// Hi John Appleseed

想要判断一个可选值是否为 nil 可以为其加上一个 let ,但一个明确值不能这做:

let nickname: String? = nil		// 可选值
let fullname: String = "John"	// 明确值

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

if let fullname {       // 报错
    print("Het \(fullname)")
}

3.3 switch 判断

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

3.4 字典遍历

可以使用 for-in 循环遍历字典中的项,字典是无序集合,因此它们的键和值可以按任意顺序进行迭代。但是这里官方的demo又写的不好,体现不出 “无序” ,我将代码进行了一点修改,在实验时可以多运行几次就能发现当前扫描的对象顺序是不定的:

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 (label, numbers) in interestingNumbers{
    print("Current is scaning \(label)")
    for number in numbers{
        if number > largest{
            largest = number
        }
    }
}

print(largest)

我这里执行了两次可以看到第一个扫描的对象存在差别:

bogon:SwiftLearning gaohao$ swift demo.swift 
	Current is scaning Square
	Current is scaning Prime
	Current is scaning Fibonacci
	25
bogon:SwiftLearning gaohao$ swift demo.swift 
	Current is scaning Prime
	Current is scaning Fibonacci
	Current is scaning Square
	25

3.5 while 循环

使用 while 语句重复执行一段代码,直到条件发生变化。

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

3.6 repeat-while 循环

将循环的条件也可以放在末尾,以确保循环至少运行一次。

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

3.7 创建索引范围

可以在循环中用 start..<end 表示创建了一个索引范围:

var total = 0

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

4. 函数与闭包

swift 中函数的使用和 python 比较相似,需要以 func 定义并且在调用的时候传入适当的参数:

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

print(greet(person: "Bob", day: "Tuesday"))
// Hello Bob, today is Tuesday

4.1 形参与形参标签

默认情况下,使用 形参名 作为参数函数调用是传入参数标签,swift 提供了一个机制可以让你自定义参数标签,或者使用 _ 表示不使用标签:

下面这个函数就是一个很好的例子:

  • 第一个参数 person 因为在前面用了 _ 进行修饰,所以在调用的时候不能写形参标签名;
  • 第二个参数在 day 前用了一个新的标签名 on 进行修饰,所以在使用的时候只能用新的标签名 on
  • 第三个参数 val 没有任何修饰,在调用时使用原始形参名;
func greet(_ person: String, on day: String, val: String) -> String{
    return "Hello, \(person), today is \(day), value \(val)"
}

print(greet("John", on: "Wedensday", val: "100"))

print(greet("abc", day: "Wen", val: "50"))			// 报错
print(greet(person: "abc", day: "Wen", val: "50"))	// 报错

4.2 返回元组

如果函数需要返回多个值,可以以元组的方式返回,这个元组的个数没有上限,返回得到的元组也可以使用元素名和索引使用:

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
        }
        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)     // 索引号方式使用

4.3 函数嵌套

在嵌套的函数中可以使用外边函数的变量。

func returnFiteen() -> Int{
    var y = 10
    func add(){		// 定义嵌套函数
        y += 5
    }
    add()			// 调用嵌套函数
    return y
}

print(returnFiteen())

4.4 函数作为返回值

函数可以作为另一个函数的返回值,在 C++ 中这个就是函数指针,只不过swift中定义和用起来更方便:

func makeIncrementer() -> ((Int) -> Int) {
    // 定义了一个参数和返回值匹配的函数
    func addOne(number: Int) -> Int{
        return 1 + number
    }
    return addOne   // 将这个函数作为返回值
}

var increment = makeIncrementer()   // 这里拿到的是一个函数
print(increment(7))
// 8

4.4 函数作为参数

同样,函数也可以作为参数传递给函数:

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]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
// true

4.5 匿名函数闭包

可以使用括号 ({}) 括起代码,从而编写一个没有名称的闭包。使用 in 将参数和返回类型与闭包主体分隔开。

var numbers = [20, 19, 7, 12]

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

print(value)
// [60, 57, 21, 36]

和 python 中的 lambda 表达式一样,形参和返回值在简单的情况下可以省略:

var numbers = [20, 19, 7, 12]
let mappedNumbers = numbers.map({
    number in 3 * number
})

print(mappedNumbers)
// [60, 57, 21, 36]

可以通过编号而来引用参数,这种方法在简单闭包中尤其有用。作为函数最后一个参数的闭包可以紧跟在括号之后。当闭包是函数的唯一参数时,可以完全省略括号。

var numbers = [20, 19, 7, 12]

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

5. 对象和类

使用 class 后跟类名来创建类。类中的属性声明与常量或变量声明的写法相同,方法和函数声明的写法也相同。

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

var shape = Shape()					// 初始化类对象
shape.numberOfSides = 10			// 修改类成员变量
print(shape.simpleDescription())	// 调用类成员函数

5.1 构造函数

初始化类其实就是构造函数,在对象实例化的时候自动调用,swift 中规定类的构造函数:

class NamedShape{
    var numberOfSides: Int = 0
    var name: String

    init(name:String){
        self.name = name
    }

    func simpleDescription() -> String{
        return "A shape \(name) with \(numberOfSides) sides."
    }
}

var namedShape = NamedShape(name: "8")
print(namedShape.simpleDescription())

5.2 成员函数覆写

使用 override 可以将父类的函数在子类进行覆写,同样 self 关键字可以明确使用的是当前类的成员:

// 父类
class NamedShape{
    var numberOfSides: Int = 0
    var name: String

    init(name:String){
        self.name = name
    }

    func simpleDescription() -> String{
        return "A shape \(name) with \(numberOfSides) sides."
    }
}

// 子类
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 side of length \(sideLength)"
    }
}

let test = Square(sideLength: 5.2, name: "squre")
print(test.name)
print(test.area())
print(test.simpleDescription())

5.3 成员变量的 get 和 set

上面的例子中已经涉及到了类的成员变量使用 test.name ,你也可以显示定义每次获取/赋值成员变量时的操作:

【Note】:getset 必须同时定义。

class EquilateralTriangle{
    var sideLength: Double = 10
    var name: String = ""

    init(sideLength: Double, name: String){
        self.sideLength = sideLength
        self.name = name
    }

    var perimeter: Double{
        get{
            return 3.0 * sideLength // 每次获取perimeter变量时返回 3.0 * sideLength
        }
        set{
            sideLength = newValue / 3.0     // 设置值时新值隐式命名为 newValue 这个变量
        }
    }
}

var trangle = EquilateralTriangle(sideLength: 3.1, name: "trangle")
print(trangle.perimeter)
trangle.perimeter = 10
print(trangle.sideLength)

5.4 willSet & didSet

在给对象成员变量赋值的时候可以使用 willSetdidSet 来对赋值前后进行操作,这里有一个关键概念 生效,修改完变量之后可以通过这两个函数来控制生效的实际。

【Note】:willSetdidSet 是可以单独使用的。

class Square{
    var sideLength: Double = 1.5
    init(sideLength: Double){
        self.sideLength = sideLength
    }
}

class EquilateralTriangle{
    var sideLength: Double = 2.0
    init(sideLength: Double){
        self.sideLength = sideLength
    }
}

class TriangleAndSquare{
    var triangle: EquilateralTriangle{
        willSet{
            print("triangle old value \(triangle.sideLength)")
            triangle.sideLength = newValue.sideLength
        }
        didSet{
            print("triangle new value \(triangle.sideLength)")
        }
    }
    var square: Square{
        willSet{
            print("square old value \(square.sideLength)")
            square.sideLength = newValue.sideLength
        }
    }
    init(size: Double){
        square = Square(sideLength: size)
        triangle = EquilateralTriangle(sideLength: size)
    }
}

var triangleAndSquare = TriangleAndSquare(size: 10)
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)

triangleAndSquare.square = Square(sideLength: 20)
triangleAndSquare.triangle = EquilateralTriangle(sideLength: 18.2)

5.5 可选表达式

使用可选值时,可以在方法、属性和下标等操作前使用 ? 。如果可选值为 nil,则后面的所有内容都将被忽略,并且整个表达式的值也为 nil;否则可选值将被解包,并且之后的所有内容都将作用于解包后的值。在这两种情况下,整个表达式的值都是可选值。

class Square{
    var sideLength: Double = 1.0
    var name: String = ""
    init(sideLength:Double, name:String){
        self.sideLength = sideLength
        self.name = name
    }
}

// 可选值为实例
var optionalSquare: Square? = Square(sideLength:2.5, name:"optional square")
var sideLength = optionalSquare?.sideLength

// 可选值为 nil
optionalSquare = nil
sideLength = optionalSquare?.sideLength

6. 枚举类和结构体

使用 enum 创建枚举,与类和所有其他命名类型一样,枚举可以拥有与其关联的方法。但 swift 的枚举类功能相比 C++ 和 pyton 而言更全。

默认情况下,Swift 会从零开始每次递增 1 来分配原始值,但可以通过明确指定值来更改此行为。在上面的示例中,ace 被明确赋予了原始值 1,其余原始值则按顺序分配,还可以使用字符串或浮点数作为枚举的原始类型。使用 rawValue 可以访问枚举成员的原始值。

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
let val = Rank.two

print(ace)
print(aceRawValue)
print(val)

print(Rank.eight)

6.1 原始值创建枚举对象

使用 init?(rawValue:) 初始化器可以从 原始值创建枚举实例,返回与原始值匹配的枚举成员,如果没有匹配的 Rank,则返回 nil

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

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

枚举的 case 值是实际值,而不仅仅是原始值的另一种写法,如果 case 中没有有意义的原始值,则无需提供。

6.2 枚举成员变量的引用

下面这个例子中引用枚举的 hearts 情况的两种方式:

  1. 在为变量 hearts 常量赋值时,Suit.hearts 使用其全名引用,因为该常量没有指定显式类型;
  2. 在 switch 内部,枚举使用缩写形式 .hearts 引用,因为 self 的值已知。
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 heartDescription = hearts.simpleDescription()

print(hearts)
print(heartDescription)

6.3 枚举的关联行为

如果枚举具有原始值,则这些值在声明过程中确定,这意味着 特定枚举的每个实例始终具有相同的原始值;枚举的另一种选择是将这些值在创建实例时确定。可以将关联值的行为视为枚举情况实例的存储属性。例如,从服务器请求日出和日落时间的情况,服务器要么响应所请求的信息,要么响应出错的描述。

下面的例子中日出和日落时间是从 ServerResponse 值中提取出来的,并将其与 switch case 的值进行匹配。

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

6.4 结构体

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

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

    func simpleDescription() -> String{
        return String(rawValue)
    }
}

enum Suit {
    case spades, hearts, diamonds, clubs
    func simpleDescription() -> String{
        switch self{
            case .spades: return "spades"
            case .hearts: return "hearts"
            default: return "others"
        }
    }
}

struct Card{
    var rank: Rank
    var suit: Suit

    func simpleDesctription() -> String{
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}

let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDesctription()

print(threeOfSpades)
print(threeOfSpadesDescription)

7. 并发

使用 async 标记异步运行的函数,在使用的时候用 await 关键字:

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

var value = await fetchUserID(from: "192.168.0.1")
print(value)
// 501

7.1 Task 同步调用异步函数

下面这个例子就是一个复合型示例,使用 Task 从同步代码调用异步函数,而无需等待它们返回:

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

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

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

Task{
    await connectUser(to: "primary")
}

7.2 任务组并发调用

同样可以用 “任务组” 的方式并发调用:

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

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

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

let userIDs = await withTaskGroup(of: Int.self) { group in 
    for server in ["primary", "secondary", "development"]{
        group.addTask{
            return await fetchUserID(from: server)
        }
    }

    var results: [Int] = []
    for await result in group{
        results.append(result)
    }
    return results
}

print(userIDs)

7.3 Actors

Actors 与类相似,不同之处在于它确保不同的异步函数可以同时安全地与同一 actor 的实例进行交互。

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

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

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

actor ServerConnection{
    var server: String = "primary"
    private var activeUsers: [Int] = []
    func connect() async -> Int{
        let userID = await fetchUserID(from: server)
        activeUsers.append(userID)
        return userID
    }
}

let server = ServerConnection()
let userID = await server.connect()

print(server)	// demo.ServerConnection
print(userID)	// 97

8. 协议和扩展

8.1 协议 Protocols

protocos 来声明一个协议 Protocols,协议的功能类似于 Java 和 C++ 中的接口,

protocol ExampleProtocol{
    var simpleDescription: String { get }   // 表示实现这个协议的类型必须提供这个属性的“只读”访问
    mutating func adject()  // mutating 表示该方法可能修改self值,默认是不能修改的
}

类、枚举、结构体都可以继承于协议。

8.1.1 继承于协议的类

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

class SimpleClass: ExampleProtocol{
    var simpleDescription: String = "A simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += " Now 100% adjusted."
    }
}

var a = SimpleClass() 
a.adjust()
print(a.simpleDescription)

8.1.2 继承于协议的结构体

【Note】:下面的代码中在实现 adjust() 函数时使用了 mutating 关键字来让结构体能够修改自身,而上面的类不需要,因为类本身就可以直接修改自身。

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

struct SimpleStructure: ExampleProtocol{
    var simpleDescription: String = "A simple struct"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}

var b = SimpleStructure()
b.adjust()
print(b.simpleDescription)

8.2 扩展 Extension

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

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

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

print(7.simpleDescription)		// A simple struct (adjusted)

9. 异常处理

可以对自带的 Error 协议进行适配异常类:

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

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

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

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

9.1 do-catch try 捕获异常

处理错误的方法有很多种,其中一种方法是使用 do-catch 语句。在 do 语句块中,通过在可能抛出错误的代码前面添加 try 来标记它。在 catch 语句块中,除非指定其他名称,否则错误会自动被命名为 error

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

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

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

可以提供多个 catch 块来处理特定的错误。在 catch 之后编写一个模式,就像在 switch 中的 case 之后编写一样。

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

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

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

9.2 try? 捕获异常

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

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

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

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

print(printerSuccess == nil)		// false
print(printerFailure == nil)		// true

9.3 defer 返回

使用 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
}

if fridgeContains("banana"){
    print("Found a banana")
}

print(fridgeIsOpen)		// false

10. 泛型

在尖括号 <> 内写一个名称来创建通用函数或类型。

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

var res = makeArray(repeating: "knock", numberOfTimes: 4)
print(res)	// ["knock", "knock", "knock", "knock"]

可以创建函数和方法的通用形式,以及类、枚举、结构体。

enum OptionalValue<Wrapped>{
    case none
    case some(Wrapped)
}

var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)

print(possibleInteger)		// some(100)

在主体之前使用 where 来指定一系列条件:要求类型实现协议、要求两种类型相同或要求类具有特定的超类。

下面这个代码定一了一个泛型函数 anyCommonElements 作用是:判断两个序列之间是否有共同的元素。官网在这里又没做解释而直接使用了关键字 where,我在这里补充下:

  • where T.Element: EquatableT 的元素需要支持等号 == 比较;
  • T.Element == U.Element:两个序列中的元素类型必须相同。
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
}

print(anyCommonElements([1, 2, 3], [3]))	// true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值