👨‍💻

Swift 基础篇

Tags
Coding
Technology
Published
December 10, 2024
Author

Xcode 工具 & Playground基本操作

Print

print("Hello world.") let name = "Xiaoming" print("Hello \(name)!") print("Hello", name, separator: ", ", terminator: "!")

变量

定义变量
let number: Int = 10 var phone: String = "16600000123"

数学运算

只能相同类型才可以运算
// 一元运算 正负号 + - -3 // 加减乘除 + - * / 1 + 2 // 优先顺序 括号 > 一元运算 > 乘除 > 加减 (1 + 2) - 2 * 3 // 余数 % 9 % 2 // 余 1 // 等于 = var name = "小明" name += "你好。" // 相同类型 var number = 0 number = number + 5 number += 5 number -= number // 除法 对于 0 不能除以 0 不能取余

数字类型与二进制

计算机采用二进制

Int 整数(根据系统bit分配大小)

Double 双精度浮点数(64 bit)

Float 单精度浮点数(32 bit)

示例:
let int: Int = 1 let float: Float = 2 let double: Double = 3 // 默认 Double let number = 1.0 let int2 = Int() // 默认为 0 let int3 = Int(1) // Int.init(1) let float2 = Float(2) let double2 = Double(3) // 方法和属性 print(Int.max) // 2^63 - 1 print(Int.min) // Double 最大值 非精确值 print(Double.greatestFiniteMagnitude)
计算需要相同类型:
let x = 1 / 2 print(x) // 0 let x2 = 1 / 2.0 print(x2) // 0.5 let x3: Double = 1 / 2 print(x3) // 0.5 let float: Float = 123.456 let double: Double = 12345 print(float + double) // 不能相加 print(Double(float) + double) // 12468.456001281738

Decimal 十进制

需要 import Foundation 才可以使用
let decimal: Decimal = 123.456 let decimal2 = Decimal(string: "123.456")! print(decimal) // 123.45599999999997952 print(decimal2) // 123.456 print(Decimal.greatestFiniteMagnitude) let a = Decimal(string: "0.1")! let b = Decimal(string: "0.2")! print(a + b) // 0.3

文本类型

Character、String

Character 是单个字,String 是由 Character 组成的。
编码方式,最初 ASCII 码 共 256 字,现在 UTF-8 编码,采用 Unicode 描述文字。
let emoji: String = "😀" var text: String = "hi" // 计算 print(text + emoji) text += emoji // 属性与方法 print(text.count) print(text.first!) print(text.last!) print(text.uppercased()) print(text.lowercased())

Unicode

let text: String = "🇨🇳 🐻‍❄️" print(text.count) print(text.unicodeScalars.count) print(text.unicodeScalars.first!) print(text.unicodeScalars.last!) print("🇨" + "🇳") // 🇨🇳 print("\u{4F60}\u{597D}") // 你好

特殊符号

\n // 换行 \t // 缩进 \u{...} // Unicode \" // 输入引号 \\ // 输入反斜线 // 多行 print 加入 \ 可以不换行 print(""" 你\ 好 """) print("你\"好") // \ 进行转译
let zalgo = "Ž͙͖͉̤̮̪̞̤͚̝̞͋̋̈́͒̀̈́͑͌̄̚ͅa̜̖͖͔̰͊̍͆͋͐́͐͆͌̉̿̚l̘̮̙͉̗͓̩̙̰͉̩̓̈̏͋̈̐̿̐̽̂͛̅̓́͂ͅg̰̩͎̞̘̰̝͙̲͖̳̰̤̜̏̏͋́͑̃o̟͉̖̯͍͕̳̐̌͗̍̂̐̌̊̐̀̈́̃̏̽̌͊.͇͎̱̫̫͓̥̪̞̖͓̖̤̊̐͑̽͋̄.̗̣͖̲͖̩̜͎̰̂͐̌̈́̔̅̂͋͛̆̚.̟̝̰̰̪̦̜̱̰̃́͌̓́̍" print("\(zalgo)") // \() 放入变量 print(zalgo.count) // 8 print(zalgo.unicodeScalars.count) // 172

Bool 布尔值

仅占用 1 bit 的空间,一般命名 is、has 等开头。
let yes = true let no: Bool = false let int: Int = 16 print(int.is) let double: Double = 1.1 print(double.isEqual(to: 1.1)) print(double.isLess(than: 0)) print(double.isLessThanOrEqualTo(1.1)) let string = "123" print(string.isEmpty) print(string.hasPrefix("1234")) print(string.hasSuffix("3")) print(string.contains("23"))

条件运算符

isVIP ? price * 0.8 : price isFree ? 0 : (isVIP ? price * 0.8 : price)

逻辑运算

大于、小于、等于

符号
含义
==
等于
!=
不等于
<
小于
>
大于
<=
小于等于
>=
大于等于

与、或、非

符号
含义
!
&&
并且
||
或者
!(我的身高 < 爸爸身高 && (我的身高 > 妈妈身高 || 我的身高 >= 爷爷身高))

Function

Function
Method
print
.init()
.isEqual()

没有参数的函数

参数可以为空,但函数调用仍然可以执行。
func sayHello() { print("Hello!") } sayHello() // 无参数,无引数

默认参数值

参数可以有默认值,调用时可以省略对应引数。
func greet(name: String = "Guest") { print("Hello, \(name)!") } greet() // 输出: Hello, Guest! greet(name: "Bob") // 输出: Hello, Bob!

可变参数

参数可以接收多个引数。
func sum(numbers: Int...) -> Int { return numbers.reduce(0, +) } print(sum(numbers: 1, 2, 3, 4)) // 输出: 10
  • 参数 numbers 是可变参数,表示可以接收多个引数。

inout 参数

参数可以在函数内部被修改,并将修改后的值返回给调用者。
func doubleValue(_ number: inout Int) { number *= 2 } var value = 5 doubleValue(&value) print(value) // 输出: 10
numberinout 参数,调用时需要用 & 传递引数。

参数标签与引数标签

函数可以指定参数标签,调用时需要明确引数的作用。
func greet(to person: String) { print("Hello, \(person)!") } greet(to: "Alice") // `to` 是参数标签,"Alice" 是引数

返回值

func add(a: Int, b: Int) -> Int { return a + b } let result = add(a: 5, b: 3) print(result) // 输出: 8 // 返回 (min: Int, max: Int) 元组,可以同时包含多个值 func minMax(numbers: [Int]) -> (min: Int, max: Int)? { guard let min = numbers.min(), let max = numbers.max() else { return nil } return (min, max) } if let result = minMax(numbers: [3, 1, 4, 1, 5]) { print("Min: \(result.min), Max: \(result.max)") } else { print("Invalid input") } // 如果返回值可能为空,可以使用 Optional func findElement(in array: [Int], value: Int) -> Int? { return array.firstIndex(of: value) } if let index = findElement(in: [1, 2, 3], value: 2) { print("Found at index \(index)") } else { print("Not found") } // 函数可以返回一个闭包 func makeMultiplier(by factor: Int) -> (Int) -> Int { return { number in return number * factor } } let multiplyByThree = makeMultiplier(by: 3) print(multiplyByThree(4)) // 输出: 12 // 函数可以返回泛型值,增强灵活性 func firstElement<T>(in array: [T]) -> T? { return array.first } print(firstElement(in: [1, 2, 3])) // 输出: Optional(1) print(firstElement(in: ["a", "b"])) // 输出: Optional("a") if let first = firstElement(in: [Bool]()) { print(first) // 安全解包后打印实际值 } else { print("No elements found") // 打印空数组的情况 }

省略参数标签

使用 _ 可以省略参数标签,使调用时更简洁。
func greet(_ name: String) { print("Hi, \(name)!") } greet("Alice") // 输出: Hi, Alice!

Closure 闭包

闭包(Closure) 是一种可以捕获和存储其所在上下文中变量和常量的代码块。它是一种类似于匿名函数的结构,可以在代码中像普通变量一样传递和使用。
闭包是 Swift 中的核心概念之一,广泛用于回调、异步操作、函数式编程等场景。
函数其实就是一种特殊类型的闭包,是具名的闭包
// 函数 func multiplyByTwo(_ value: Int) -> Int { return value * 2 } // 闭包 let closure = { (value: Int) -> Int in return value * 2 } print(multiplyByTwo(5)) // 输出: 10 print(closure(5)) // 输出: 10

基本语法

{ (parameters) -> returnType in statements } // parameters:闭包的输入参数列表。 // returnType:闭包的返回值类型。 // statements:闭包执行的代码块。 // 示例 let add: (Int, Int) -> Int = { (a, b) in return a + b } let result = add(3, 5) print(result) // 输出: 8 // (Int, Int) -> Int:指定闭包的参数类型和返回类型。 // { (a, b) in return a + b }:闭包体,定义了接收两个整数并返回它们之和的操作。

表达式简化

// 当闭包只有一行代码时,return 关键字可以省略,结果会自动返回: let add: (Int, Int) -> Int = { (a, b) in a + b } let result = add(3, 5) print(result) // 输出: 8 // 参数提供简写名称 $0, $1, $2 等 let add: (Int, Int) -> Int = { $0 + $1 } let result = add(3, 5) print(result) // 输出: 8

尾随闭包(Trailing Closure)

如果闭包作为函数的最后一个参数传递,Swift 允许使用尾随闭包的语法,使闭包体写在函数调用外面,而不需要将闭包放在圆括号内。
func performAction(action: () -> Void) { action() } performAction { print("Action performed!") } // 上面的代码中,action 参数是一个闭包,我们通过尾随闭包的方式调用 performAction 函数。

闭包的捕获值

闭包可以捕获并保存其所在上下文中的常量和变量。这意味着即使闭包在原本作用域之外被调用,它也可以访问这些被捕获的值。
func makeIncrementer(incrementAmount: Int) -> () -> Int { var total = 0 let incrementer: () -> Int = { total += incrementAmount return total } return incrementer } let incrementByTwo = makeIncrementer(incrementAmount: 2) print(incrementByTwo()) // 输出: 2 print(incrementByTwo()) // 输出: 4 // incrementAmount 和 total 被闭包捕获并存储,闭包每次调用时都能访问并修改它们; // 即使 makeIncrementer 函数已经结束执行。

回调(Callback)

func fetchData(completion: (String) -> Void) { let data = "Hello, world!" completion(data) } fetchData { data in print(data) // 输出: Hello, world! } // completion 是一个闭包,它会在数据加载完成后被调用。

异步操作

func downloadFile(url: String, completion: @escaping (String) -> Void) { DispatchQueue.global().async { // 模拟下载过程 sleep(2) let fileData = "File data from \(url)" DispatchQueue.main.async { completion(fileData) } } } downloadFile(url: "http://example.com/file") { data in print("Downloaded data: \(data)") } // 这里使用了 @escaping 关键字,表示闭包可能在函数返回后执行。 // DispatchQueue 用于模拟异步操作。

内存管理

由于闭包能够捕获外部的变量和常量,它们可能导致内存泄漏,尤其是在闭包引用自身或引用某个对象时。这种情况被称为 捕获循环(retain cycle)。为了避免这种情况,通常需要使用 weakunowned 来解决。

weakunowned 引用

  • weak:用于避免强引用循环,且可以变为 nil
  • unowned:用于避免强引用循环,且不会变为 nil
class Person { var name: String var closure: (() -> Void)? init(name: String) { self.name = name } func createClosure() { // 使用 `weak` 避免循环引用 closure = { [weak self] in print("Hello, \(self?.name ?? "Unknown")") } } } var person: Person? = Person(name: "John") person?.createClosure() person?.closure?() // 输出: Hello, John person = nil // 这里 `person` 被销毁 // [weak self] 使得闭包中的 self 被弱引用,不会增加对 person 对象的强引用,从而避免了循环引用。

.sorted(by: closure)

let numbers = [3, 1, 4, 1, 5, 9] let sortedNumbers = numbers.sorted(by: { $0 < $1 }) print(sortedNumbers) // 输出: [1, 1, 3, 4, 5, 9] let sortedNumbers = numbers.sorted(by: <) // 升序 let sortedNumbersDesc = numbers.sorted(by: >) // 降序 let words = ["banana", "apple", "grape"] let sortedByLength = words.sorted(by: { $0.count < $1.count }) print(sortedByLength) // 输出: ["apple", "grape", "banana"] let people = [ ["name": "Alice", "age": 30], ["name": "Bob", "age": 25], ["name": "Charlie", "age": 35] ] let sortedPeople = people.sorted { ($0["age"] as! Int) < ($1["age"] as! Int) } print(sortedPeople) // 输出: 按照 age 升序排列的数组

Tuple 元组

用于处理轻量级的数据组合需求
let person: (String, Int) = ("Alice", 25) print(person) // 输出: ("Alice", 25) print(person.0) // 输出: Alice print(person.1) // 输出: 25 // 元组的元素可以有名称,便于访问 typealias Human = (name: String, age: Int) let person: Human = (name: "Alice", age: 25) print(person.name) // 输出: Alice print(person.age) // 输出: 25 // 可以解构赋值 let (name, _) = person print(name)

if… else…

if 条件必须是 Bool 类型

let number = 5 // 错误:因为条件不是布尔类型 // if number { // print("This is true") // } // 正确:需要明确条件是布尔表达式 if number > 0 { print("Number is positive.") }

多条件分支:if...else if...else

let score = 75 if score >= 90 { print("Grade: A") } else if score >= 80 { print("Grade: B") } else if score >= 70 { print("Grade: C") } else { print("Grade: F") } // 输出 Grade: C

嵌套的 if

let number = 15 if number > 0 { if number % 2 == 0 { print("Positive even number") } else { print("Positive odd number") } } else { print("Number is not positive") }

条件绑定(Optional Binding)

在 Swift 中,if 常用于 Optional Binding,即判断可选值是否有内容,并将其解包。
let name: String? = "Alice" if let unwrappedName = name { print("Hello, \(unwrappedName)!") } else { print("No name provided.") }

四舍五入

let number = 123.456 let formatted = String(format: "%.2f", number) print(formatted) // 输出: 123.46

文本格式化

格式化标记
描述
示例输入
示例输出
%d
整数
42
42
%f
浮点数,默认显示 6 位小数
3.14
3.140000
%.nf
浮点数,保留 n 位小数
3.1415
3.14
%s
字符串
"Swift"
Swift
%@
对象(通常用于字符串)
"Test"
Test
%x
整数的十六进制表示
255
ff
%X
整数的十六进制大写表示
255
FF
%o
整数的八进制表示
8
10
%e
浮点数的科学计数法表示
12345.6
1.23456e+04

NumberFormatter

let formatter = NumberFormatter() formatter.numberStyle = .decimal // 设置格式为小数 formatter.maximumFractionDigits = 2 // 最多保留两位小数 let number = 1234.5678 if let formattedString = formatter.string(from: NSNumber(value: number)) { print(formattedString) // 输出: 1,234.57 } if let formattedString = formatter.string(for: number) { print(formattedString) // 输出: 1,234.57 }
转为中文
let formatter = NumberFormatter() formatter.numberStyle = .spellOut // 设置为中文读法 formatter.locale = Locale(identifier: "zh_Hans_CN") // 设置为简体中文地区 let number = 1234567.89 if let formattedString = formatter.string(from: NSNumber(value: number)) { print(formattedString) // 一百二十三万四千五百六十七点八九 }

Range 范围

Range 与数学表示的区别

数学表示
Swift 表示
是否包含结束值
[a, b]
a...b
包含结束值
[a, b)
a..<b
不包含结束值
[a, ∞)
a...
开放范围
(-∞, a]
...a
开放范围
(-∞, a)
..<a
开放范围

数字与文本

let range = 0..<5 print(range.lowerBound) // 输出: 0 print(range.upperBound) // 输出: 5 print(range.contains(3)) // 输出: true print(range.isEmpty) // 输出: false let range = 1...10 print(range.contains(5)) // 输出: true print(range.contains(15)) // 输出: false // 等同于 print(range ~= 5) // 输出: true print(range ~= 15) // 输出: false
let charRange: ClosedRange<Character> = "a"..."z" print(charRange.contains("c")) // 输出: true print(charRange.contains("A")) // 输出: false // string let stringRange: ClosedRange<String> = "apple"..."banana" print(stringRange.contains("avocado")) // 输出: true

数组、字符串切片

let numbers = [10, 20, 30, 40, 50] let subArray = numbers[1...3] print(subArray) // 输出: [20, 30, 40] let text = "Hello, Swift!" let substring = text[text.startIndex..<text.index(text.startIndex, offsetBy: 5)] print(substring) // 输出: Hello // 单侧范围 let array = [1, 2, 3, 4, 5] // 从索引 2 开始到结尾 let subArray1 = array[2...] print(subArray1) // 输出: [3, 4, 5] // 从开头到索引 2(不包括 2) let subArray2 = array[..<2] print(subArray2) // 输出: [1, 2]

生成随机数

let range = 1...100 // 闭区间 let randomInt = Int.random(in: range) print(randomInt) // 输出: 随机数,例如 66

Switch

Range

let value = 85 switch value { case ..<60: print("Grade: F") case 60..<70: print("Grade: D") case 70..<80: print("Grade: C") case 80..<90: print("Grade: B") // Grade: B case 90...100: print("Grade: A") default: print("Invalid score") }

Tuple

let point = (2, 3) switch point { case (0, 0): print("Origin") case (_, 0): print("On the x-axis") case (0, _): print("On the y-axis") case (1...5, 1...5): print("Inside the square") default: print("Outside") }

Where

let point = (3, -3) switch point { case let (x, y) where x == y: print("On the line x == y") case let (x, y) where x == -y: print("On the line x == -y") default: print("Just a point") }

Enum

enum Measurement { case weight(Double) case height(Double) case temperature(Double) } let value = Measurement.weight(72.5) switch value { case .weight(let w): if w > 70 { print("Heavy: \(w)kg") } else { print("case Weight") } case .height(let h): print("Height: \(h)cm") case .temperature(let t): print("Temperature: \(t)°C") } // fallthrough 会直接进入下一个代码块,不进行检查

Array

类型 与 创建数组

var emptyArray: [Int] = [] // 显式指定类型 print(emptyArray) // 输出: [] var anotherEmptyArray = [String]() // 使用初始化器 print(anotherEmptyArray) // 输出: [] // 类型 let numbers = [1, 2, 3, 4, 5] // 自动推断为 [Int] let names: [String] = ["Alice", "Bob", "Cathy"] let repeatedArray = Array(repeating: 0, count: 5) print(repeatedArray) // 输出: [0, 0, 0, 0, 0] let array1 = [1, 2] let array2 = [3, 4] let combinedArray = array1 + array2 print(combinedArray) // 输出: [1, 2, 3, 4]

数组的基本操作

let numbers = [10, 20, 30, 40, 50] print(numbers[2]) // 输出: 30 numbers[1] = 25 print(numbers) // 输出: [10, 25, 30, 40, 50] print(numbers.min()!) print(numbers.max()!) // 在末尾添加 numbers.append(40) // 插入元素到指定位置 numbers.insert(15, at: 1) // 删除元素 numbers.remove(at: 1) numbers.removeLast() numbers.removeAll() let numbers: [Int] = [10, 20, 30] print(numbers.count) // 输出: 3 print(numbers.isEmpty) // 输出: false print(numbers.first!) // 输出: Optional(10) print(numbers.last!) // 输出: Optional(30) // 查找第一个符合条件的元素 print(numbers.firstIndex(of: 30)!) // 筛选符合条件的元素 print(numbers.filter { $0 > 25 }) // map print(numbers.map { $0 * $0 }) // 排序 print(numbers.sorted()) print(numbers.sorted(by: >)) // 拼接字符串 let names = ["Alice", "Bob", "Cathy"] let joined = names.joined(separator: ", ") print(joined) // 输出: Alice, Bob, Cathy

遍历数组

let numbers = [10, 20, 30] for number in numbers { print(number) } // 输出: // 10 // 20 // 30 // 带索引 for (index, value) in numbers.enumerated() { print("Index \(index): \(value)") } // 输出: // Index 0: 10 // Index 1: 20 // Index 2: 30

For Loop

for number in 0...5 { if (number % 5 == 2) { break } print(number) } // 0 1 for number in 0...5 { if (number % 2 == 0) { continue // 跳过当前迭代 } print(number) } // 1 3 5

使用标签(Label)控制多层循环

outerLoop: for i in 1...3 { for j in 1...3 { if i * j == 4 { break outerLoop // 直接退出外层循环 } print("\(i), \(j)") } }

使用 stride 控制步长

for i in stride(from: 0, to: 10, by: 2) { print(i) } // 输出: 0, 2, 4, 6, 8 for i in stride(from: 10, through: 0, by: -2) { print(i) } // 输出: 10, 8, 6, 4, 2, 0

while loop & repeat while

var counter = 0 while true { print(counter) counter += 1 if counter == 3 { break // 手动退出循环 } } // 输出: 0, 1, 2 var apple = 1 var banana = 10 while apple != banana { apple += 1 print("apple: ", apple) } var counter = 0 repeat { print(counter) counter += 1 } while counter < 3 // 输出: 0, 1, 2

Set & Dictionary

Set

  • 无序性Set中元素无固定顺序,不同于数组的有序性。
  • 唯一性Set不允许重复元素。
  • 高效性:基于哈希表实现,查找和修改为O(1)。
  • 集合运算
    • 交集intersection
    • 并集union
    • 差集subtracting
    • 对称差集symmetricDifference
// 定义两个集合 let setA: Set = [1, 2, 3, 4, 5] let setB: Set = [4, 5, 6, 7, 8] // 交集:返回两个集合中都包含的元素 let intersection = setA.intersection(setB) print("交集: \(intersection)") // 输出: [4, 5] // 并集:返回两个集合中所有不同的元素 let union = setA.union(setB) print("并集: \(union)") // 输出: [1, 2, 3, 4, 5, 6, 7, 8] // 差集:返回存在于第一个集合但不在第二个集合中的元素 let difference = setA.subtracting(setB) print("差集 (A - B): \(difference)") // 输出: [1, 2, 3] // 对称差集:返回在两个集合中存在,但不同时存在于两者的元素 let symmetricDifference = setA.symmetricDifference(setB) print("对称差集: \(symmetricDifference)") // 输出: [1, 2, 3, 6, 7, 8]
  • 类型安全:只能存储同一类型元素。
  • 基本操作
    • insert(_:):插入一个元素。
    • remove(_:):移除一个元素。
    • contains(_:):检查集合中是否包含某个元素。
    • count:获取集合的元素数量。
    • isEmpty:判断集合是否为空。
var numbers: Set = [1, 2, 3, 4, 5] // 插入元素 numbers.insert(6) // 检查元素是否存在 if numbers.contains(3) { print("集合中包含 3") } // 移除元素 numbers.remove(4) // 输出集合 print(numbers) // 例如输出: [2, 3, 5, 6, 1]
  • 类型限制:元素必须遵循Hashable协议。
struct Person: Hashable { var name: String var age: Int // 必须实现 == 操作符,判断两个 Person 是否相等 // lhs:"left-hand side" 左边的操作数 static func == (lhs: Person, rhs: Person) -> Bool { return lhs.name == rhs.name && lhs.age == rhs.age } // 必须实现 hash(into:) 方法,用来计算哈希值 func hash(into hasher: inout Hasher) { // 将结构体的属性通过 hasher 添加到哈希计算中 hasher.combine(name) hasher.combine(age) } } // 使用 Person 作为 Set 的元素 var people: Set<Person> = [Person(name: "Alice", age: 30), Person(name: "Bob", age: 25)] people.insert(Person(name: "Alice", age: 30)) // 添加重复元素不会生效 print(people) // 输出:Set([Person(name: "Alice", age: 30), Person(name: "Bob", age: 25)])

Dictionary

  • 无序性:字典中的元素是无序的。与 Array 不同,字典中的键值对没有特定顺序。
  • 键唯一性:每个键必须是唯一的。不能有重复的键。如果插入了相同的键,新的值会替换掉原有的值。
  • 支持任意类型的键值:键(key)必须符合 Hashable 协议,而值(value)可以是任何类型。
  • 通过键访问值:可以通过键来访问对应的值,查找效率通常为 O(1)。
创建:
var studentGrades: [String: String] = ["Alice": "A", "Bob": "B", "Charlie": "A"] var emptyDict: [String: Int] = [String: Int]() var emptyDict: [String: Int] = [:]
常用方法:
var studentGrades: [String: String] = ["Alice": "A", "Bob": "B"] // 访问字典中的值 if let grade = studentGrades["Alice"] { print("Alice's grade: \(grade)") // 输出: Alice's grade: A } else { print("Grade not found") } // 添加新元素 studentGrades["David"] = "C" // 更新已存在的元素 studentGrades["Bob"] = "A" // Bob 的成绩更新为 A print(studentGrades) // 输出: ["Alice": "A", "Bob": "A", "David": "C"] // 删除指定的元素 studentGrades["Bob"] = nil print(studentGrades) // 输出: ["Alice": "A", "David": "C"] // 你也可以使用 removeValue(forKey:) 方法: let removedGrade = studentGrades.removeValue(forKey: "David") print(removedGrade) // 输出: Optional("C") print(studentGrades) // 输出: ["Alice": "A"] // 添加新元素 studentGrades["David"] = "C" // 遍历字典 for (name, grade) in studentGrades { print("\(name) has grade \(grade)") } // 输出: // Alice has grade A // David has grade C // 可以使用 contains 或 key 来检查某个键是否存在 if studentGrades.keys.contains("Alice") { print("Alice's grade is \(studentGrades["Alice"]!)") } // 获取所有的键或所有的值 let keys = studentGrades.keys print(keys) // 输出: ["Alice", "David"] // 获取所有值 let values = studentGrades.values print(values) // 输出: ["A", "C"]
高级操作:
var dict1 = ["a": 1, "b": 2] let dict2 = ["b": 3, "c": 4] // 合并字典 dict1.merge(dict2) { (current, new) in new } // 选择新值 print(dict1) // 输出: ["a": 1, "b": 3, "c": 4] // 使用字典进行快速查找 let sentence = "apple banana apple orange apple banana" var wordCount: [String: Int] = [:] let words = sentence.split(separator: " ") for word in words { let wordString = String(word) wordCount[wordString, default: 0] += 1 } print(wordCount) // 输出: ["apple": 3, "banana": 2, "orange": 1]

Protocol 协议

协议是用来定义一组行为规范的,它就像一个契约,确保遵循协议的类型提供了协议中所要求的方法和属性。协议可以声明实例方法、类方法、属性、初始化器等。

创建协议

使用 protocol 关键字创建,采用大驼峰命名法。

语法

protocol SomeProtocol { var someProperty: String { get set } func someMethod() }

协议的要求

  1. 属性要求:可以是只读可读写
    1. protocol Vehicle { var numberOfWheels: Int { get } }
  1. 方法要求
    1. protocol Flyable { func fly() }
  1. 初始化器
    1. protocol Initializable { init(name: String) }
  1. 关联类型
    1. protocol Container { associatedtype Item var item: Item { get } func add(item: Item) }

协议的遵循

类型通过 : 符号遵循协议。
protocol Shape { var area: Double { get } func description() -> String } struct Circle: Shape { var radius: Double var area: Double { .pi * radius * radius } func description() -> String { "A circle with radius \(radius)" } }

可选要求

使用 @objcoptional 关键字定义可选方法。
@objc protocol Animal { @objc optional func makeSound() } class Dog: Animal { func makeSound() { print("Woof") } } let dog = Dog() dog.makeSound?() // 输出: Woof

协议继承

协议可以继承其他协议,组合多个功能要求。
protocol Shape { var area: Double { get } } protocol Colorful { var color: String { get } } protocol ColoredShape: Shape, Colorful { } struct Square: ColoredShape { var area: Double var color: String }

协议类型

协议可作为类型使用,用于变量、参数和返回值。
// 协议类型作为参数 func printShapeArea(shape: Shape) { print("The area of the shape is \(shape.area)") } let square = Square(area: 25, color: "Red") printShapeArea(shape: square) // 输出: The area of the shape is 25.0

常见协议

  1. Equatable:相等性比较
  1. Comparable:要求类型提供比较大小的方法(<, <=, >, >=
  1. Hashable:要求类型提供一个哈希值方法,用于将实例用于集合类型(如 SetDictionary
  1. CustomStringConvertible:字符串描述
  1. Codable:类型能够被编码和解码(适用于 JSONPlist等序列化/反序列化)。
  1. Error:错误处理

Generic 泛型

泛型Generic)是一种允许我们编写可以与多种类型一起工作的代码的机制,而不需要在编写时明确指定具体类型。泛型通过占位符(类型参数)来表示数据类型,直到代码使用时,具体的类型才会被确定。
泛型可以使得函数、方法、类、结构体和枚举等更加灵活和通用。通过使用泛型,我们可以避免重复编写针对不同类型的代码,而是可以使用一个泛型类型的定义来处理多个不同的类型。

泛型函数

// 这里的 T 是一个类型参数,它表示一个占位符类型,表示函数可以接受任何类型的参数。 // inout 表示参数是传入和返回的,也就是说,这两个参数将被修改。 func swap<T>(a: inout T, b: inout T) { let temp = a a = b b = temp } // 调用泛型函数 var x = 5 var y = 10 swap(&x, &y) print(x) // 输出: 10 print(y) // 输出: 5

泛型类型

// Element 是一个类型占位符,代表栈中元素的类型。 // 可以通过指定具体的类型来创建不同类型的栈,例如 Stack<Int> 或 Stack<String> struct Stack<Element> { var items: [Element] = [] mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element? { return items.popLast() } } // 使用 var intStack = Stack<Int>() intStack.push(10) intStack.push(20) print(intStack.pop()) // 输出: Optional(20) var stringStack = Stack<String>() stringStack.push("Hello") stringStack.push("World") print(stringStack.pop()) // 输出: Optional("World")

泛型约束

// T: CustomStringConvertible 这是一个约束,表示 T 必须是遵循了 CustomStringConvertible 协议的类型。 // 这个函数只能接受遵循 CustomStringConvertible 协议的类型,例如 String、Int 等。 func printDescription<T: CustomStringConvertible>(value: T) { print(value.description) } // 使用 printDescription(value: 123) // 输出: 123 printDescription(value: "Hello") // 输出: Hello

多个类型参数

func swapTwoValues<T, U>(a: inout T, b: inout U) { let temp = a a = b as! T // 强制转换 b = temp as! U }

类型约束

有时我们希望泛型类型或函数的类型参数继承自某个父类或遵循某个协议。这样我们可以确保泛型类型能够访问到某些特定的属性或方法。
func printArea<T: Shape>(shape: T) where T: Shape { print("Area: \(shape.area)") } // T: Shape 表示 T 必须是 Shape 类或协议的子类。 // where 子句是一种可选的方式,对泛型类型的进一步约束,这里有些多余。

Collection 协议

Collection 是一个非常重要的协议(Protocol),它定义了一系列用于操作有序集合的数据类型的接口。任何遵循 Collection 协议的类型都可以被视为一个集合,可以按照特定的方式进行遍历、索引和操作。
Collection 协议继承自 Sequence 协议,提供了比 Sequence 更丰富的功能。与 Sequence 不同,Collection 要求集合是可以多次遍历的,并且需要提供起始和结束位置的索引,以及通过索引访问元素的能力。

主要属性和方法

  • startIndexendIndex:表示集合的开始和结束位置的索引。
  • Index:表示集合中元素的位置的类型,必须遵循 Comparable 协议。
  • indices:表示集合中所有有效索引的范围。
  • subscript(position: Index) -> Element下标访问:通过索引获取对应位置的元素。
  • count:集合中元素的数量。
  • index(after:):返回指定索引的下一个索引。
  • formIndex(after:):将索引移动到下一个位置。

常见 Collection 类型

Swift 标准库中的许多类型都遵循了 Collection 协议,例如:
  • Array:数组,存储有序的元素序列。
  • Set:集合,存储无序但唯一的元素集。
  • Dictionary:字典,存储键值对的集合。
  • String:字符串,被视为字符的集合。

Collection 的特性

  1. 有序性:集合中的元素是按照特定顺序排列的,允许通过索引访问。
  1. 多次遍历:集合可以被多次遍历,每次遍历的结果相同。
  1. 索引的可比性:集合的索引必须是可比较的(Comparable),这使得可以判断索引的相对位置。

使用示例

// 示例 1:遍历集合 let array = [10, 20, 30, 40, 50] // 使用 for-in 循环遍历 for element in array { print(element) } // 输出: // 10 // 20 // 30 // 40 // 50
// 示例 2:通过索引访问元素 let array = ["Apple", "Banana", "Cherry", "Date"] // 获取集合的起始和结束索引 let startIndex = array.startIndex let endIndex = array.endIndex // 通过索引访问元素 let firstElement = array[startIndex] print("First Element: \(firstElement)") // 输出: First Element: Apple // 访问第二个元素 let secondIndex = array.index(after: startIndex) let secondElement = array[secondIndex] print("Second Element: \(secondElement)") // 输出: Second Element: Banana
// 示例 3:使用索引范围 let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 获取索引范围 let indices = numbers.indices // 相当于 numbers.startIndex..<numbers.endIndex // 遍历索引并访问元素 for index in indices { print("Index \(index): \(numbers[index])") } // 输出: // Index 0: 1 // Index 1: 2 // Index 2: 3 // ... // Index 9: 10

自定义类型遵循 Collection 协议

你也可以创建自己的类型,并使其遵循 Collection 协议。需要实现协议要求的属性和方法。
struct Countdown: Collection { // 起始值和结束值 let start: Int let end: Int // 定义 Index 类型 typealias Index = Int // 定义 Element 类型 typealias Element = Int // 起始索引 var startIndex: Index { return start } // 结束索引 var endIndex: Index { return end + 1 } // 下标访问 subscript(position: Index) -> Element { return position } // 返回下一个索引 func index(after i: Index) -> Index { return i - 1 } } // 创建一个倒计时集合,从 10 到 0 let countdown = Countdown(start: 10, end: 0) // 遍历倒计时集合 for number in countdown { print(number) } // 输出: // 10 // 9 // 8 // ... // 0
  • startIndexendIndex:由于是倒计时,startIndex 为起始值,endIndex 为结束值加一。
  • subscript:通过索引返回对应的值。
  • index(after:):因为是倒计时,所以下一个索引是当前索引减一。

协议的方法扩展

Swift 标准库为所有遵循 Collection 协议的类型提供了许多默认实现的功能,例如:
  • map(_:):将集合中的每个元素转换为新的值,返回一个新的集合。
  • filter(_:):筛选集合中的元素,返回满足条件的元素组成的集合。
  • reduce(_:_:):将集合中的元素组合成一个值。
  • forEach(_:):对集合中的每个元素执行指定的操作。
let numbers = [1, 2, 3, 4, 5] // 使用 map 将每个元素乘以 2 let doubledNumbers = numbers.map { $0 * 2 } print(doubledNumbers) // 输出: [2, 4, 6, 8, 10] // 使用 filter 筛选出偶数 let evenNumbers = numbers.filter { $0 % 2 == 0 } print(evenNumbers) // 输出: [2, 4] // 使用 reduce 计算总和 let sum = numbers.reduce(0, +) print(sum) // 输出: 15

Sequence 的区别

  • Sequence:表示一系列元素,可以一次遍历,不一定支持多次遍历,也不一定支持索引访问。
  • Collection:继承自 Sequence,保证了集合是可以多次遍历的,并且支持索引访问。

常用的 Collection 协议的扩展

  • RandomAccessCollection:进一步继承自 Collection,要求索引支持 O(1) 时间复杂度的随机访问,如数组(Array)就是这种类型。
  • BidirectionalCollection:继承自 Collection,要求索引可以双向移动,支持 index(before:) 方法,如双向链表。

实际应用中的意义

  • 统一性:由于许多类型都遵循了 Collection 协议,因此可以编写针对 Collection 类型的通用代码,适用于数组、集合、字典等。
  • 灵活性:通过协议扩展,可以为所有集合类型添加新的方法和功能。
  • 类型安全Collection 协议明确了集合的行为,提供了类型安全的操作方式。

总结

  • Collection 协议定义了集合类型需要遵循的一系列要求,包括索引、元素访问和遍历等。
  • 标准库中的集合类型(如 ArraySetDictionary)都遵循了 Collection 协议。
  • 自定义集合类型可以通过遵循 Collection 协议,获得与标准集合类型相同的功能和接口。
  • 丰富的默认实现:通过协议扩展,Collection 提供了许多默认的方法,如 mapfilterreduce 等,方便进行集合操作。

mapfilterreduce

在 Swift 中,mapreducefilter 是三个常用的高阶函数,属于函数式编程的核心工具,通常用于对集合(如数组、字典、集合等)进行变换、过滤和聚合操作。这些方法以简洁的语法和强大的功能深受开发者的喜爱。

map

map 方法用于将集合中的每个元素通过某种规则转换成新的值,最终返回一个包含所有转换结果的集合。
array.map { transformation }
  • transformation 是一个闭包,用于定义每个元素的转换规则。
  • 返回值是一个新数组,包含所有转换后的结果。
将整数数组转换为字符串数组
let numbers = [1, 2, 3, 4] let stringNumbers = numbers.map { "Number: \($0)" } print(stringNumbers) // 输出: ["Number: 1", "Number: 2", "Number: 3", "Number: 4"]
计算数组每个元素的平方
let squares = numbers.map { $0 * $0 } print(squares) // 输出: [1, 4, 9, 16]
从字典中提取键
let people = ["Alice": 30, "Bob": 25, "Charlie": 35] let names = people.map { $0.key } print(names) // 输出: ["Alice", "Bob", "Charlie"]

filter

filter 方法用于筛选集合中的元素,返回满足条件的元素组成的新集合。
array.filter { condition }
  • condition 是一个闭包,用于定义筛选条件。
  • 返回值是一个新数组,包含所有满足条件的元素。
筛选出偶数
let numbers = [1, 2, 3, 4, 5, 6] let evenNumbers = numbers.filter { $0 % 2 == 0 } print(evenNumbers) // 输出: [2, 4, 6]
筛选出大于 25 的人
let people = ["Alice": 30, "Bob": 25, "Charlie": 35] let olderPeople = people.filter { $0.value > 25 } print(olderPeople) // 输出: ["Alice": 30, "Charlie": 35]
从数组中去除 nil
let optionalNumbers: [Int?] = [1, nil, 3, nil, 5] let validNumbers = optionalNumbers.compactMap { $0 } // 使用 compactMap 是更好的选择 print(validNumbers) // 输出: [1, 3, 5]

reduce

reduce 方法用于将集合中的所有元素聚合为一个值。你需要提供一个初始值和一个操作闭包,该闭包将集合中的元素逐个合并。
array.reduce(initialResult) { accumulator, element in combinationLogic }
  • initialResult:聚合的初始值。
  • accumulator:当前的聚合结果。
  • element:集合中的当前元素。
  • 返回值是一个单一值。
计算数组的总和
let numbers = [1, 2, 3, 4, 5] let sum = numbers.reduce(0) { $0 + $1 } print(sum) // 输出: 15
拼接字符串
let words = ["Swift", "is", "awesome"] let sentence = words.reduce("") { $0 + " " + $1 }.trimmingCharacters(in: .whitespaces) print(sentence) // 输出: "Swift is awesome"
计算数组元素的乘积
let product = numbers.reduce(1) { $0 * $1 } print(product) // 输出: 120
在字典中合并值
let scores = ["Alice": 30, "Bob": 20, "Charlie": 50] let totalScore = scores.reduce(0) { $0 + $1.value } print(totalScore) // 输出: 100

综合应用

通过组合使用 mapfilterreduce,可以轻松完成复杂的数据操作。
从一个整数数组中,筛选出偶数,将其平方并计算总和。
let numbers = [1, 2, 3, 4, 5, 6] let result = numbers .filter { $0 % 2 == 0 } // 筛选偶数 .map { $0 * $0 } // 计算平方 .reduce(0) { $0 + $1 } // 计算总和 print(result) // 输出: 56 (2² + 4² + 6² = 4 + 16 + 36 = 56)
筛选年龄大于 30 的人,并生成名字的数组。
let people = ["Alice": 30, "Bob": 25, "Charlie": 35] let names = people .filter { $0.value > 30 } // 筛选出年龄大于 30 的人 .map { $0.key } // 提取名字 print(names) // 输出: ["Charlie"]

高阶函数的特点

  1. 简洁:相比传统的循环代码,mapfilterreduce 更加简洁,提升代码可读性。
  1. 函数式风格:与其他函数式编程语言(如 JavaScript、Python)保持一致,便于学习和迁移。
  1. 链式操作:支持链式调用,可以组合多个操作完成复杂任务。
缺点
  1. 性能问题:链式调用会生成中间数组,在处理大数据时可能增加内存开销。
  1. 学习曲线:对于初学者来说,这些方法的概念可能不太直观。

使用 lazy 优化性能

在处理大数据集合时,使用 lazy 可以避免中间数组的创建,提升性能。
let numbers = Array(1...1_000_000) let result = numbers.lazy .filter { $0 % 2 == 0 } // 筛选偶数 .map { $0 * $0 } // 计算平方 .reduce(0, +) // 计算总和 print(result) // 输出: 166667166667000000
lazy 会将集合转换为延迟序列,只在需要时才执行每一步操作,从而避免创建不必要的中间结果。

总结

方法
作用
返回值
map
对集合中的每个元素进行转换
转换后的新集合
filter
筛选集合中的元素,满足条件的留下
筛选后的新集合
reduce
聚合集合中的所有元素,生成单一值
单一的聚合结果