johnhao
johnhao
发布于 2018-03-17 / 2 阅读
0

Swift-高级特性教程

1. 可选类型与错误处理

1.1 可选类型

1.1.1 概念

  • 可选类型(Optional):表示一个值可能存在或不存在的类型

  • 可选类型通过在类型名后添加问号?来表示

  • 可选类型的可能值:

    • 一个具体的值(如Int类型的42

    • nil(表示值不存在)

1.1.2 原理

Swift引入可选类型是为了解决空指针问题,这是许多其他语言(如Java、C++)中常见的崩溃原因。通过明确标注可选类型,Swift编译器可以在编译时检查可选值的使用,确保开发者正确处理值可能不存在的情况。

1.1.3 使用示例

// 声明可选类型
var optionalInt: Int?
var optionalString: String? = "Hello"
var optionalDouble: Double? = nil
// 检查可选值是否存在
if optionalInt != nil {
 print("optionalInt has a value: (optionalInt!)")
} else {
 print("optionalInt is nil")
}

// 强制解包(只有确定可选值存在时才能使用)
// let unwrappedInt = optionalInt! // 运行时错误,因为optionalInt是nil

let unwrappedString = optionalString! // 安全,因为optionalString有值
print(unwrappedString) // 输出: Hello

1.2 可选绑定

1.2.1 概念

可选绑定(Optional Binding)是一种安全地访问可选值的方式,它可以检查可选值是否存在,并将其解包赋值给一个临时变量或常量。

1.2.2 原理

可选绑定避免了强制解包可能导致的运行时错误,它通过ifguardfor-in等语句来实现。如果可选值存在,则解包并执行相应的代码块;否则,跳过代码块。

1.2.3 使用示例

// if let 可选绑定
var optionalName: String? = "Alice"
if let name = optionalName {
 print("Hello, (name)") // 输出: Hello, Alice
 } else {
 print("Hello, unknown")
 }

// 多个可选绑定
 var optionalAge: Int? = 25

if let name = optionalName, let age = optionalAge {
 print("(name) is (age) years old") // 输出: Alice is 25 years old
 }

// 带条件的可选绑定
 if let name = optionalName, let age = optionalAge, age >= 18 {
 print("(name) is an adult") // 输出: Alice is an adult
 }

// guard let 可选绑定(用于提前退出)
 func greet(person: [String: String]) {
 guard let name = person["name"] else {
 print("No name provided")
 return
 }
print("Hello, \(name)!")
}

let user = ["name": "Bob", "age": "30"]
 greet(person: user) // 输出: Hello, Bob!

let anonymousUser = ["age": "25"]
 greet(person: anonymousUser) // 输出: No name provided

1.3 可选链

1.3.1 概念

可选链(Optional Chaining)是一种在可选值上调用属性、方法和下标脚本的方式,如果可选值为nil,则整个表达式的值为nil

1.3.2 原理

可选链允许我们在一条链中访问多个可选值的属性或方法,而不需要为每个可选值单独进行可选绑定。如果链中的任何一个环节为nil,则整个链的结果为nil,不会导致运行时错误。

1.3.3 使用示例

// 定义类结构
class Person {
    var residence: Residence?
}
class Residence {
 var rooms: [Room] = []
 var numberOfRooms: Int {
   return rooms.count
 }
}

class Room {
 let name: String
 init(name: String) {
self.name = name
 }
 }

// 创建实例
 let john = Person()

// 可选链调用属性
 if let roomCount = john.residence?.numberOfRooms {
 print("John’s residence has (roomCount) rooms.")
 } else {
 print("Unable to retrieve the number of rooms.") // 输出: Unable to retrieve the number of rooms.
 }

// 可选链调用方法
 john.residence?.printNumberOfRooms() // 无输出,因为residence是nil

// 可选链调用下标
 if let firstRoomName = john.residence?[0].name {
 print("The first room name is (firstRoomName).")
 } else {
 print("Unable to retrieve the first room name.") // 输出: Unable to retrieve the first room name.
 }

// 赋值给可选链
 john.residence?[0] = Room(name: "Bathroom") // 无效果,因为residence是nil

// 设置residence后再使用可选链
 let someResidence = Residence()
 someResidence.rooms.append(Room(name: "Living Room"))
 someResidence.rooms.append(Room(name: "Kitchen"))
 john.residence = someResidence

if let roomCount = john.residence?.numberOfRooms {
 print("John’s residence has (roomCount) rooms.") // 输出: John’s residence has 2 rooms.
 }

1.4 错误处理

1.4.1 概念

错误处理(Error Handling)是一种处理程序执行过程中可能出现的错误的机制。Swift提供了抛出、捕获、传递和操作可恢复错误的功能。

1.4.2 原理

Swift的错误处理基于抛出、捕获和处理错误的思想。错误用符合Error协议的类型表示,通过throw关键字抛出错误,通过do-try-catch语句捕获和处理错误。

1.4.3 使用示例

// 定义错误类型
enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}
// 模拟自动售货机
 struct Item {
 let price: Int
 let count: Int
 }

class VendingMachine {
 var inventory = [
 "Candy Bar": Item(price: 12, count: 7),
 "Chips": Item(price: 10, count: 4),
 "Pretzels": Item(price: 7, count: 11)
 ]

var coinsDeposited = 0

func vend(itemNamed name: String) throws {
    guard let item = inventory[name] else {
        throw VendingMachineError.invalidSelection
    }
    
    guard item.count > 0 else {
        throw VendingMachineError.outOfStock
    }
    
    guard item.price <= coinsDeposited else {
        throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
    }
    
    coinsDeposited -= item.price
    
    var newItem = item
    newItem.count -= 1
    inventory[name] = newItem
    
    print("Dispensing \(name)")
}
}

// 使用do-try-catch处理错误
 let vendingMachine = VendingMachine()
 vendingMachine.coinsDeposited = 8

do {
 try vendingMachine.vend(itemNamed: "Candy Bar")
 } catch VendingMachineError.invalidSelection {
 print("Invalid selection.")
 } catch VendingMachineError.outOfStock {
 print("Out of stock.")
 } catch VendingMachineError.insufficientFunds(let coinsNeeded) {
 print("Insufficient funds. Please insert an additional (coinsNeeded) coins.") // 输出: Insufficient funds. Please insert an additional 4 coins.
 } catch {
 print("Unexpected error: (error).")
 }

// 传播错误
 func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
 let favoriteSnack = favoriteSnack(person: person)
 try vendingMachine.vend(itemNamed: favoriteSnack)
 }

func favoriteSnack(person: String) -> String {
 switch person {
 case "Alice": return "Chips"
 case "Bob": return "Pretzels"
 default: return "Candy Bar"
 }
 }

// try? 和 try! 表达式
 let snackName = "Chips"
 vendingMachine.coinsDeposited = 10

// try? 返回可选值,如果出错则返回nil
 if let result = try? vendingMachine.vend(itemNamed: snackName) {
 print("Successfully vended (snackName)") // 输出: Dispensing Chips
 } else {
 print("Failed to vend (snackName)")
 }

// try! 强制解包,如果出错则崩溃(只有确定不会出错时才能使用)
 vendingMachine.coinsDeposited = 10
 try! vendingMachine.vend(itemNamed: "Chips") // 输出: Dispensing Chips

2. 内存管理

2.1 自动引用计数(ARC)

2.1.1 概念

自动引用计数(Automatic Reference Counting,ARC)是Swift用于管理内存的机制,它会自动跟踪和管理对象的内存使用。

2.1.2 原理

  • 当创建一个对象时,ARC会分配一块内存来存储该对象的信息

  • ARC会跟踪有多少强引用指向该对象

  • 当强引用计数变为0时,ARC会释放该对象占用的内存

  • ARC只管理引用类型的内存,值类型由编译器直接管理

2.1.3 使用示例

// 定义类
class Person {
    let name: String
    init(name: String) {
    self.name = name
    print("\(name) is being initialized")
   }

  deinit {
    print("\(name) is being deinitialized")
  }
}

// 创建强引用
 var reference1: Person?
 var reference2: Person?
 var reference3: Person?

reference1 = Person(name: "John") // 输出: John is being initialized
 // 强引用计数: 1

reference2 = reference1
 // 强引用计数: 2

reference3 = reference1
 // 强引用计数: 3

// 释放强引用
 reference1 = nil
 // 强引用计数: 2

reference2 = nil
 // 强引用计数: 1

reference3 = nil
 // 强引用计数: 0,对象被释放
 // 输出: John is being deinitialized

2.2 强引用、弱引用、无主引用

2.2.1 概念

  • 强引用(Strong Reference):默认的引用类型,会增加对象的引用计数

  • 弱引用(Weak Reference):不会增加对象的引用计数,对象释放后会自动变为nil,使用weak关键字声明

  • 无主引用(Unowned Reference):不会增加对象的引用计数,假设对象永远不会被释放,使用unowned关键字声明

2.2.2 原理

弱引用和无主引用用于解决循环引用问题。弱引用适用于引用的对象可能为nil的情况,无主引用适用于引用的对象永远不会为nil的情况。

2.2.3 使用示例

// 弱引用示例
class Apartment {
    let unit: String
    weak var tenant: Person?
init(unit: String) {
    self.unit = unit
    print("Apartment \(unit) is being initialized")
}

deinit {
    print("Apartment \(unit) is being deinitialized")
}

}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John") // 输出: John is being initialized
unit4A = Apartment(unit: "4A") // 输出: Apartment 4A is being initialized

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // 输出: John is being deinitialized
// 此时,Person对象被释放,但Apartment对象仍存在

unit4A = nil // 输出: Apartment 4A is being deinitialized
// 现在,Apartment对象也被释放

// 无主引用示例
class Customer {
let name: String
var card: CreditCard?

init(name: String) {
    self.name = name
    print("Customer \(name) is being initialized")
}

deinit {
    print("Customer \(name) is being deinitialized")
}

}

class CreditCard {
let number: UInt64
unowned let customer: Customer

init(number: UInt64, customer: Customer) {
    self.number = number
    self.customer = customer
    print("Credit card \(number) is being initialized")
}

deinit {
    print("Credit card \(number) is being deinitialized")
}

}

var bob: Customer?
bob = Customer(name: "Bob") // 输出: Customer Bob is being initialized
bob?.card = CreditCard(number: 1234_5678_9012_3456, customer: bob!) // 输出: Credit card 1234567890123456 is being initialized

bob = nil // 输出: Customer Bob is being deinitialized 和 Credit card 1234567890123456 is being deinitialized
// 两个对象都被释放,因为CreditCard对Customer的引用是无主引用

2.3 闭包与循环引用

2.3.1 概念

当闭包捕获了其外部作用域中的变量或常量时,可能会导致循环引用。这是因为闭包默认会对其捕获的对象创建强引用。

2.3.2 原理

为了解决闭包导致的循环引用,Swift提供了捕获列表(Capture List),用于指定闭包对捕获对象的引用类型(强引用、弱引用或无主引用)。

2.3.3 使用示例

// 闭包导致的循环引用
class HTMLElement {
    let name: String
    let text: String?
lazy var asHTML: () -> String = {
    if let text = self.text {
        return "<\(self.name)>\(text)</\(self.name)>"
    } else {
        return "<\(self.name) />"
    }
}

init(name: String, text: String? = nil) {
    self.name = name
    self.text = text
    print("HTMLElement \(name) is being initialized")
}

deinit {
    print("HTMLElement \(name) is being deinitialized")
}

}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "Hello, world!")
print(paragraph?.asHTML() ?? "") // 输出: <p>Hello, world!</p>

paragraph = nil // 没有输出,因为存在循环引用,对象没有被释放

// 使用弱引用解决循环引用
class HTMLElement2 {
let name: String
let text: String?

lazy var asHTML: () -&gt; String = {
    [weak self] in // 捕获列表,使用弱引用
    guard let self = self else { // 检查self是否存在
        return &quot;&quot;
    }
    if let text = self.text {
        return &quot;&lt;\(self.name)&gt;\(text)&lt;/\(self.name)&gt;&quot;
    } else {
        return &quot;&lt;\(self.name) /&gt;&quot;
    }
}

init(name: String, text: String? = nil) {
    self.name = name
    self.text = text
    print(&quot;HTMLElement2 \(name) is being initialized&quot;)
}

deinit {
    print(&quot;HTMLElement2 \(name) is being deinitialized&quot;)
}

}

var paragraph2: HTMLElement2? = HTMLElement2(name: "p", text: "Hello, world!")
print(paragraph2?.asHTML() ?? "") // 输出: <p>Hello, world!</p>

paragraph2 = nil // 输出: HTMLElement2 p is being deinitialized,对象被正确释放

// 使用无主引用解决循环引用(当self永远不会为nil时)
class HTMLElement3 {
let name: String
let text: String?

lazy var asHTML: () -&gt; String = {
    [unowned self] in // 捕获列表,使用无主引用
    if let text = self.text {
        return &quot;&lt;\(self.name)&gt;\(text)&lt;/\(self.name)&gt;&quot;
    } else {
        return &quot;&lt;\(self.name) /&gt;&quot;
    }
}

init(name: String, text: String? = nil) {
    self.name = name
    self.text = text
    print(&quot;HTMLElement3 \(name) is being initialized&quot;)
}

deinit {
    print(&quot;HTMLElement3 \(name) is being deinitialized&quot;)
}

}

var paragraph3: HTMLElement3? = HTMLElement3(name: "p", text: "Hello, world!")
print(paragraph3?.asHTML() ?? "") // 输出: <p>Hello, world!</p>

paragraph3 = nil // 输出: HTMLElement3 p is being deinitialized,对象被正确释放

3. 高级类型

3.1 枚举高级用法

3.1.1 概念

Swift的枚举(Enumeration)是一种灵活的类型,可以包含关联值和原始值,还可以定义方法和属性。

3.1.2 原理

枚举的高级用法包括:

  • 关联值:为枚举成员添加额外的数据

  • 原始值:为枚举成员指定默认值

  • 方法和属性:为枚举添加实例方法和类型方法

  • 协议遵守:让枚举遵守协议

3.1.3 使用示例

// 关联值
enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

// 匹配关联值
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: (numberSystem), (manufacturer), (product), (check).")
case .qrCode(let productCode):
print("QR code: (productCode).")
}

// 原始值
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}

// 隐式原始值
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let earthsOrder = Planet.earth.rawValue // 输出: 3
let mars = Planet(rawValue: 4) // 输出: Optional(Planet.mars)

// 枚举方法
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)

func evaluate() -&gt; Int {
    switch self {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return left.evaluate() + right.evaluate()
    case .multiplication(let left, let right):
        return left.evaluate() * right.evaluate()
    }
}

}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

print(product.evaluate()) // 输出: (5+4)*2 = 18

3.2 关联类型

3.2.1 概念

关联类型(Associated Type)是在协议中定义的一种占位符类型,用于指定协议中方法或属性的类型,具体类型由遵守协议的类型来确定。

3.2.2 原理

关联类型允许协议在不指定具体类型的情况下定义通用的接口,实现了泛型协议的功能。遵守协议的类型需要为关联类型指定具体的类型。

3.2.3 使用示例

// 定义带有关联类型的协议
protocol Container {
    associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -&gt; ItemType { get }

}

// 结构体遵守协议,指定关联类型
struct IntStack: Container {
// 原始实现
var items = Int

mutating func push(_ item: Int) {
    items.append(item)
}

mutating func pop() -&gt; Int {
    return items.removeLast()
}

// 遵守Container协议
typealias ItemType = Int

mutating func append(_ item: Int) {
    self.push(item)
}

var count: Int {
    return items.count
}

subscript(i: Int) -&gt; Int {
    return items[i]
}

}

// 泛型类型遵守协议
struct Stack<Element>: Container {
// 原始实现
var items = Element

mutating func push(_ item: Element) {
    items.append(item)
}

mutating func pop() -&gt; Element {
    return items.removeLast()
}

// 遵守Container协议,Swift会自动推断ItemType为Element
mutating func append(_ item: Element) {
    self.push(item)
}

var count: Int {
    return items.count
}

subscript(i: Int) -&gt; Element {
    return items[i]
}

}

// 扩展现有类型以遵守协议
extension Array: Container {
// Array已经实现了append、count和subscript,所以只需要指定ItemType
// Swift会自动推断ItemType为Array.Element
}

3.3 类型擦除

3.3.1 概念

类型擦除(Type Erasure)是一种将具体类型的信息隐藏起来,只暴露必要接口的技术。它允许我们使用不同的具体类型,但通过相同的抽象接口来操作它们。

3.3.2 原理

在Swift中,当我们需要处理多种具体类型但又不想暴露它们的具体类型时,可以使用类型擦除。常见的实现方式是创建一个包装器类型,该类型内部持有具体类型的实例,但对外只暴露协议定义的接口。

3.3.3 使用示例

// 定义协议
protocol Animal {
    associatedtype Food
    func eat(_ food: Food)
    func sleep()
}

// 具体类型1
class Dog: Animal {
typealias Food = Bone

func eat(_ food: Bone) {
    print(&quot;Dog is eating \(food.type)&quot;)
}

func sleep() {
    print(&quot;Dog is sleeping&quot;)
}

}

struct Bone {
let type: String
}

// 具体类型2
class Cat: Animal {
typealias Food = Fish

func eat(_ food: Fish) {
    print(&quot;Cat is eating \(food.type)&quot;)
}

func sleep() {
    print(&quot;Cat is sleeping&quot;)
}

}

struct Fish {
let type: String
}

// 类型擦除包装器
class AnyAnimal<F>: Animal {
private let _eat: (F) -> Void
private let _sleep: () -> Void

init&lt;A: Animal&gt;(_ animal: A) where A.Food == F {
    self._eat = animal.eat
    self._sleep = animal.sleep
}

func eat(_ food: F) {
    _eat(food)
}

func sleep() {
    _sleep()
}

}

// 使用类型擦除
let dog = AnyAnimal(Dog())
dog.sleep() // 输出: Dog is sleeping

let cat = AnyAnimal(Cat())
cat.sleep() // 输出: Cat is sleeping

// 注意:由于类型擦除后,Food类型被固定,所以不能混合不同Food类型的Animal
// 可以创建一个更通用的类型擦除,或者使用不同的Food类型分别处理

3.4 元类型

3.4.1 概念

元类型(Meta Type)是指类型本身,而不是类型的实例。在Swift中,元类型使用Type关键字表示,如Int.Type表示Int类型本身。

3.4.2 原理

元类型用于:

  • 获取类型的信息

  • 动态创建实例

  • 访问类型的静态属性和方法

3.4.3 使用示例

// 获取元类型
let intType: Int.Type = Int.self
let stringType: String.Type = String.self

// 使用元类型创建实例
class Person {
var name: String

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

static func createInstance(name: String) -&gt; Self {
    return self.init(name: name)
}

func introduce() {
    print(&quot;Hello, my name is \(name)&quot;)
}

}

let personType: Person.Type = Person.self
let person = personType.init(name: "Alice")
person.introduce() // 输出: Hello, my name is Alice

// 类型约束
func createInstance<T: Person>(of type: T.Type, name: String) -> T {
return type.init(name: name)
}

let bob = createInstance(of: Person.self, name: "Bob")
bob.introduce() // 输出: Hello, my name is Bob

// 元类型作为函数参数
func printTypeInfo(_ type: Any.Type) {
print("Type: (type)")
print("Type name: (String(describing: type))")
}

printTypeInfo(Int.self) // 输出: Type: Int
// Type name: Int

printTypeInfo(Person.self) // 输出: Type: Person
// Type name: Person

4. 总结

Swift的高级特性包括可选类型与错误处理、内存管理和高级类型等,这些特性共同构成了Swift强大的表达能力和安全性:

  • 可选类型与错误处理:提供了安全的方式来处理可能不存在的值和可能发生的错误,避免了运行时崩溃

  • 内存管理:通过ARC自动管理内存,同时提供了弱引用、无主引用和捕获列表来解决循环引用问题

  • 高级类型:包括枚举的关联值和原始值、关联类型、类型擦除和元类型等,使Swift类型系统更加灵活和强大

掌握这些高级特性对于编写高质量的Swift代码至关重要,它们可以帮助你编写更加安全、高效和易于维护的代码。

5. 练习

  1. 定义一个Result枚举,用于表示操作的成功或失败,并实现相应的错误处理。

  2. 创建一个类层次结构,演示强引用、弱引用和无主引用的使用,并观察对象的生命周期。

  3. 编写一个闭包,使用捕获列表来避免循环引用。

  4. 创建一个带有关联值的枚举,用于表示不同类型的网络请求,并实现一个处理函数。

  5. 定义一个带有关联类型的协议,然后让多个类型遵守该协议,并使用类型擦除来统一处理这些类型。

通过这些练习,你可以巩固所学的Swift高级特性知识,提高自己的Swift编程能力。