《SWIFTER -Swift开发者必备Tips》学习笔记

96 阅读1小时+

Swift 新元素

1.2.1 柯里化 (Currying)

重点说明

柯里化是将一个多参数的函数,拆解成多个单参数函数的链式调用的技术。每个函数接收一个参数,返回一个接收下一个参数的新函数,直到所有参数接收完毕,返回最终结果。Swift支持柯里化函数的定义,适合参数分步传入、函数复用的场景。

代码示例

// 1. 基础柯里化函数:加法函数
func add(_ a: Int) -> (Int) -> Int {
    return { b in
        return a + b
    }
}

// 分步调用
let add5 = add(5) // 先传入第一个参数,返回一个接收第二个参数的函数
let result1 = add5(3) // 8
let result2 = add5(10) // 15

// 2. 多参数柯里化
func createUser(name: String) -> (Int) -> (Bool) -> String {
    return { age in
        return { isVip in
            return "用户:\(name),年龄:\(age),VIP:\(isVip ? "是" : "否")"
        }
    }
}

// 分步传入参数
let userWithName = createUser(name: "王五")
let userWithNameAndAge = userWithName(30)
let userInfo = userWithNameAndAge(true)
print(userInfo) // 用户:王五,年龄:30,VIP:是

1.2.2 将 protocol 的方法声明为 mutating

重点说明

Swift中值类型(struct、enum)在方法内修改自身属性时,必须用mutating修饰方法。协议中声明mutating方法,可让遵循协议的值类型和引用类型都能正确实现修改自身的逻辑;引用类型(class)实现时无需加mutating,因为class实例可自由修改自身属性。

代码示例

// 定义带mutating方法的协议
protocol Toggleable {
    mutating func toggle()
}

// 枚举(值类型)实现协议
enum Switch: Toggleable {
    case on, off
    mutating func toggle() {
        self = self == .on ? .off : .on
    }
}

// 类(引用类型)实现协议,无需mutating
class Light: Toggleable {
    var isOn = false
    func toggle() {
        isOn = !isOn
    }
}

// 使用
var mySwitch = Switch.off
mySwitch.toggle() // 变为.on

let myLight = Light()
myLight.toggle() // isOn变为true

1.2.3 Sequence

重点说明

Sequence协议是Swift集合类型的基础,遵循该协议可获得for-in循环能力,只需实现makeIterator()方法返回一个遵守IteratorProtocol协议的迭代器。迭代器需实现next()方法,返回序列的下一个元素,无元素时返回nil

代码示例

// 定义自定义序列,生成从0到指定最大值的偶数序列
struct EvenNumberSequence: Sequence {
    let maxValue: Int
    
    func makeIterator() -> EvenNumberIterator {
        return EvenNumberIterator(max: maxValue)
    }
}

// 定义迭代器
struct EvenNumberIterator: IteratorProtocol {
    let max: Int
    var current = 0
    
    mutating func next() -> Int? {
        guard current <= max else { return nil }
        defer { current += 2 }
        return current
    }
}

// 使用,支持for-in循环
for evenNum in EvenNumberSequence(maxValue: 10) {
    print(evenNum) // 输出0,2,4,6,8,10
}

1.2.4 tuple(元组)

重点说明

元组是Swift中轻量级的复合类型,可将多个不同类型的值组合成一个复合值,无需提前定义结构体/类。支持元素命名、解包、作为函数返回值(可返回多个值),适合临时数据组合,不适合复杂数据结构。

代码示例

// 1. 基础元组,带元素命名
let userInfo = (name: "张三", age: 25, isVip: true)
// 访问元素
print(userInfo.name) // 张三
print(userInfo.1) // 25

// 2. 元组解包
let (userName, userAge, _) = userInfo
print("用户名:\(userName),年龄:\(userAge)")

// 3. 作为函数返回值,返回多个结果
func calculateStatistics(_ numbers: [Int]) -> (sum: Int, average: Double, max: Int)? {
    guard !numbers.isEmpty else { return nil }
    let sum = numbers.reduce(0, +)
    let average = Double(sum) / Double(numbers.count)
    let max = numbers.max()!
    return (sum, average, max)
}

if let result = calculateStatistics([1,2,3,4,5]) {
    print("总和:\(result.sum),平均值:\(result.average),最大值:\(result.max)")
}

1.2.5 @autoclosure 和 ??

重点说明

@autoclosure可将传入的表达式自动封装成闭包,延迟执行,避免不必要的性能开销。空合运算符??底层使用了@autoclosure,当左侧可选值为nil时,才会执行右侧的默认值表达式,否则直接解包左侧值,右侧不会执行。

代码示例

// 1. @autoclosure基础使用
func logIfError(_ condition: Bool, errorMessage: @autoclosure () -> String) {
    if condition {
        print("错误:\(errorMessage())")
    }
}

// 只有condition为true时,才会执行拼接字符串的表达式
logIfError(1 > 2, errorMessage: "数值超出范围:\(1) 大于 \(2)") // 无输出,右侧不执行
logIfError(2 > 1, errorMessage: "数值超出范围:\(2) 大于 \(1)") // 执行,输出错误信息

// 2. ??运算符使用
let defaultName = "未知用户"
var inputName: String? = nil
let finalName = inputName ?? defaultName // inputName为nil,执行defaultName,结果为"未知用户"

inputName = "李四"
let finalName2 = inputName ?? defaultName // inputName非nil,直接解包,defaultName不执行

1.2.6 @escaping

重点说明

@escaping(逃逸闭包)用于修饰函数参数中的闭包,当闭包的生命周期超过函数本身(比如被保存到外部变量、异步执行)时,必须用@escaping标记。逃逸闭包内需要显式引用self,避免循环引用;非逃逸闭包默认不能持有self,生命周期随函数结束而结束。

代码示例

import Foundation

// 定义存储逃逸闭包的数组
var completionHandlers: [() -> Void] = []

// 1. 逃逸闭包:闭包被保存到外部数组,生命周期超过函数
func addCompletionHandler(handler: @escaping () -> Void) {
    completionHandlers.append(handler)
}

// 2. 非逃逸闭包:闭包在函数内同步执行,生命周期随函数结束
func syncExecute(handler: () -> Void) {
    handler()
}

// 3. 异步场景的逃逸闭包
func fetchUserData(completion: @escaping (String) -> Void) {
    // 模拟网络请求,异步执行
    DispatchQueue.global().async {
        let userName = "异步获取的用户"
        DispatchQueue.main.async {
            completion(userName) // 闭包在函数返回后才执行,必须标记@escaping
        }
    }
}

// 使用
addCompletionHandler {
    print("逃逸闭包执行")
}
completionHandlers.first?() // 执行保存的逃逸闭包

fetchUserData { name in
    print(name)
}

1.2.7 Optional Chaining(可选链式调用)

重点说明

可选链式调用是一种在可选值上调用属性、方法、下标的方式,若可选值为nil,整个链式调用直接返回nil,不会崩溃;若可选值有值,则解包并执行后续调用,返回结果自动包装为可选值。支持多层链式调用,任意一层为nil则整个链返回nil

代码示例

// 定义模型
class User {
    var address: Address?
}

class Address {
    var street: String?
    func getFullAddress() -> String? {
        return street
    }
}

// 可选链式调用
let user = User()
// 1. 访问属性,address为nil,直接返回nil,不会崩溃
let street = user.address?.street
print(type(of: street)) // String?,值为nil

// 2. 调用方法
let fullAddress = user.address?.getFullAddress() // nil

// 3. 赋值,address为nil,赋值操作不会执行
user.address?.street = "中关村大街"
print(user.address?.street) // nil

// 赋值后正常执行
user.address = Address()
user.address?.street = "中关村大街"
print(user.address?.street) // Optional("中关村大街")

1.2.8 操作符

重点说明

Swift支持自定义操作符,可分为前缀、中缀、后缀操作符,需先声明操作符,再实现对应的函数。支持对已有操作符进行重载,为自定义类型添加运算能力;也可定义全新的操作符,指定优先级和结合性,适配自定义业务逻辑。

代码示例

import Foundation

// 1. 重载已有操作符,为二维向量实现加法
struct Vector2D {
    var x: Double
    var y: Double
}

// 重载+运算符
func + (left: Vector2D, right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}

// 2. 自定义前缀操作符,实现向量取反
prefix operator -
prefix func - (vector: Vector2D) -> Vector2D {
    return Vector2D(x: -vector.x, y: -vector.y)
}

// 3. 自定义中缀操作符,指定优先级
infix operator **: MultiplicationPrecedence // 乘法优先级
func ** (base: Double, power: Double) -> Double {
    return pow(base, power)
}

// 使用
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
let sumVector = v1 + v2 // (4,6)
let reverseVector = -v1 // (-1,-2)
let powerResult = 2 ** 3 // 8.0

1.2.9 func 的参数修饰

重点说明

Swift函数参数默认是let常量,不可在函数内修改。常用参数修饰符包括:inout(输入输出参数,可修改外部变量的值,调用时加&)、_(省略参数标签,调用时无需写参数名)、自定义参数标签(区分外部标签和内部参数名)。Swift 3+后移除了var参数修饰,需在函数内手动定义可变变量。

代码示例

// 1. 省略参数标签,用_修饰
func sum(_ a: Int, _ b: Int) -> Int {
    return a + b
}
sum(1, 2) // 调用时无需写参数标签

// 2. 自定义外部标签和内部参数名
func greet(to userName: String, from sender: String) {
    // 内部使用userName和sender,外部调用用to和from
    print("\(sender)\(userName) 问好")
}
greet(to: "张三", from: "李四")

// 3. inout参数,修改外部变量
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}
var num1 = 10
var num2 = 20
swapTwoInts(&num1, &num2) // 调用时加&
print(num1, num2) // 20 10

1.2.10 字面量表达

重点说明

Swift通过字面量协议,让自定义类型可直接通过字面量(如数字、字符串、布尔值、数组、字典)初始化,无需显式调用初始化方法。常用字面量协议:ExpressibleByIntegerLiteralExpressibleByStringLiteralExpressibleByArrayLiteral等,只需实现协议对应的初始化方法。

代码示例

// 让自定义的用户ID类型支持字符串字面量初始化
struct UserID: ExpressibleByStringLiteral {
    let idString: String
    
    // 实现字符串字面量初始化方法
    init(stringLiteral value: StringLiteralType) {
        self.idString = value
    }
}

// 让自定义的分数类型支持整数字面量初始化
struct Score: ExpressibleByIntegerLiteral {
    let value: Int
    init(integerLiteral value: IntegerLiteralType) {
        self.value = value
    }
}

// 使用,直接通过字面量赋值
let userId: UserID = "user_123456" // 无需调用UserID(stringLiteral:)
print(userId.idString) // user_123456

let myScore: Score = 99
print(myScore.value) // 99

1.2.11 下标

重点说明

下标是Swift中访问集合、序列、结构体/类元素的快捷方式,可通过subscript关键字为自定义类型添加下标能力,支持单个/多个参数、读写/只读、重载,无需调用专门的get/set方法,语法更简洁。

代码示例

// 为班级类添加下标,通过学号访问学生
class ClassRoom {
    private var students: [Int: String] = [:]
    
    // 定义下标,读写类型
    subscript(studentID: Int) -> String? {
        get {
            return students[studentID]
        }
        set {
            students[studentID] = newValue
        }
    }
    
    // 重载下标,通过姓名首字母筛选学生
    subscript(firstLetter: Character) -> [String] {
        return students.values.filter { $0.hasPrefix(String(firstLetter)) }
    }
}

// 使用
let classRoom = ClassRoom()
classRoom[1001] = "张三"
classRoom[1002] = "李四"
classRoom[1003] = "张四"

print(classRoom[1001] ?? "不存在") // 张三
print(classRoom["张"]) // ["张三", "张四"]

1.2.12 方法嵌套

重点说明

Swift支持在函数/方法内部定义另一个函数,即嵌套方法。嵌套方法仅能在外层函数内部访问,外部无法调用;可访问外层函数的参数和变量,适合封装仅在外层函数内使用的逻辑,简化代码结构,避免全局命名污染。

代码示例

// 外层函数:计算阶乘
func calculateFactorial(of number: Int) -> Int {
    // 嵌套方法:校验输入是否合法
    func validateInput() -> Bool {
        return number >= 0
    }
    
    // 嵌套方法:递归计算阶乘
    func factorial(_ n: Int) -> Int {
        return n <= 1 ? 1 : n * factorial(n-1)
    }
    
    // 调用嵌套方法
    guard validateInput() else {
        fatalError("输入不能为负数")
    }
    return factorial(number)
}

// 使用
print(calculateFactorial(of: 5)) // 120
// 外部无法访问嵌套方法,factorial()和validateInput()在外部不可见

1.2.13 命名空间

重点说明

Swift没有传统的全局命名空间关键字,通过模块、嵌套类型、枚举(无case的空枚举)实现命名空间功能,避免不同模块/代码间的命名冲突。推荐使用空枚举作为命名空间载体,因为空枚举无法实例化,仅作为命名空间使用,更安全。

代码示例

import UIKit

// 定义命名空间,使用空枚举
enum MyAppNamespace {
    // 嵌套类型,仅在命名空间内生效
    enum Network {
        static func getRequest(url: String) {
            print("发送GET请求到:\(url)")
        }
    }
    
    enum UI {
        static func createButton(title: String) -> UIButton {
            let btn = UIButton()
            btn.setTitle(title, for: .normal)
            return btn
        }
    }
}

// 使用命名空间,避免冲突
MyAppNamespace.Network.getRequest(url: "https://api.example.com")
let button = MyAppNamespace.UI.createButton(title: "登录")

// 模块级命名空间:每个Swift模块(如App、Framework)本身就是一个顶级命名空间,可通过模块名.类型访问

1.2.14 typealias

重点说明

typealias用于为已有类型定义别名,简化复杂类型的书写,提高代码可读性,也可用于给协议组合、函数类型、泛型类型起别名。不会创建新的类型,只是给已有类型起了一个新名字,别名和原类型完全等价。

代码示例

import UIKit

// 1. 为基础类型起别名,适配业务场景
typealias UserID = String
typealias Price = Double

let userId: UserID = "user_654321"
let goodsPrice: Price = 99.9

// 2. 为函数类型起别名,简化闭包参数书写
typealias CompletionHandler = (Bool, String) -> Void

func fetchData(completion: CompletionHandler) {
    completion(true, "请求成功")
}

// 3. 为协议组合起别名
typealias FullStack = UITableViewDelegate & UITableViewDataSource

// 4. 为泛型类型起别名
typealias StringDictionary<T> = Dictionary<String, T>
let userDict: StringDictionary<Any> = ["name": "张三", "age": 25]

1.2.15 associatedtype

重点说明

associatedtype(关联类型)用于协议中,给协议中用到的类型定义一个占位名称,具体的类型由遵循该协议的类型来指定。实现协议时,通过typealias指定关联类型的实际类型,也可通过编译器自动推断,让协议支持泛型能力,适配不同类型的实现。

代码示例

// 定义带关联类型的协议
protocol Container {
    // 定义关联类型,占位符
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(index: Int) -> Item { get }
}

// 实现协议,指定关联类型为Int
struct IntContainer: Container {
    // 显式指定关联类型,也可省略,由编译器自动推断
    typealias Item = Int
    private var items: [Int] = []
    
    var count: Int { items.count }
    
    mutating func append(_ item: Int) {
        items.append(item)
    }
    
    subscript(index: Int) -> Int {
        items[index]
    }
}

// 泛型实现协议,自动推断关联类型
struct Stack<T>: Container {
    private var elements: [T] = []
    // 无需显式指定typealias,编译器通过append方法的参数自动推断Item为T
    
    var count: Int { elements.count }
    
    mutating func append(_ item: T) {
        elements.append(item)
    }
    
    subscript(index: Int) -> T {
        elements[index]
    }
}

// 使用
var intContainer = IntContainer()
intContainer.append(1)
intContainer.append(2)
print(intContainer[0]) // 1

var stringStack = Stack<String>()
stringStack.append("Swift")
print(stringStack[0]) // Swift

1.2.16 可变参数函数

重点说明

可变参数函数可接收零个或多个指定类型的输入值,通过在参数类型后添加...来定义。函数内部会将可变参数转换为对应类型的数组,可通过数组的方式访问。一个函数最多只能有一个可变参数,可变参数可放在参数列表的任意位置,通常放在末尾。

代码示例

// 1. 计算多个数字的平均值
func calculateAverage(_ numbers: Double...) -> Double {
    // numbers在函数内是[Double]数组
    guard !numbers.isEmpty else { return 0 }
    let sum = numbers.reduce(0, +)
    return sum / Double(numbers.count)
}

// 调用,可传入任意数量的参数
print(calculateAverage(1,2,3,4,5)) // 3.0
print(calculateAverage(10,20)) // 15.0
print(calculateAverage()) // 0

// 2. 拼接多个字符串
func joinStrings(separator: String, strings: String...) -> String {
    return strings.joined(separator: separator)
}

print(joinStrings(separator: "-", strings: "2024", "05", "20")) // 2024-05-20

1.2.17 初始化方法顺序

重点说明

Swift类的初始化有严格的安全检查顺序,分为两个阶段:第一阶段,子类必须先初始化自身的所有存储属性,再调用父类的指定初始化方法;第二阶段,父类初始化完成后,子类才能修改继承来的属性、调用实例方法。避免在属性未初始化时访问,防止崩溃。

代码示例

class Person {
    var name: String
    init(name: String) {
        self.name = name // 第一阶段:初始化自身存储属性
    }
}

class Student: Person {
    var studentID: Int
    var grade: Int
    
    init(name: String, studentID: Int, grade: Int) {
        // 第一阶段第一步:初始化子类自身的所有存储属性
        self.studentID = studentID
        self.grade = grade
        // 第一阶段第二步:调用父类的指定初始化方法
        super.init(name: name)
        
        // 第二阶段:父类初始化完成后,才能修改属性、调用方法
        self.name = "学生:\(name)" // 可修改继承的属性
    }
}

// 错误示例:先调用super.init,再初始化子类属性,会编译报错
// init(name: String, studentID: Int, grade: Int) {
//     super.init(name: name) // 错误:子类属性未初始化
//     self.studentID = studentID
//     self.grade = grade
// }

1.2.18 Designated, Convenience 和 Required

重点说明

  • Designated(指定初始化方法):类的主初始化方法,必须初始化所有未赋值的存储属性,最终必须调用父类的指定初始化方法,每个类至少有一个。
  • Convenience(便利初始化方法):辅助初始化方法,用convenience修饰,必须调用同一个类中的指定初始化方法,只能横向调用,不能直接调用父类的初始化方法,用于简化初始化流程。
  • Required(必要初始化方法):用required修饰,所有子类必须重写该初始化方法,重写时无需加override,必须加required,确保继承链上所有子类都实现该方法。

代码示例

class Person {
    var name: String
    var age: Int
    
    // 指定初始化方法
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    // 便利初始化方法
    convenience init(name: String) {
        self.init(name: name, age: 0) // 必须调用本类的指定初始化方法
    }
    
    // 必要初始化方法
    required init() {
        self.name = "未知"
        self.age = 0
    }
}

class Student: Person {
    var studentID: Int
    
    // 子类指定初始化方法
    init(name: String, age: Int, studentID: Int) {
        self.studentID = studentID
        super.init(name: name, age: age) // 调用父类指定初始化方法
    }
    
    // 必须重写父类的必要初始化方法
    required init() {
        self.studentID = 0
        super.init()
    }
    
    // 子类便利初始化方法
    convenience init(studentID: Int) {
        self.init(name: "学生", age: 18, studentID: studentID)
    }
}

// 使用
let person1 = Person(name: "张三", age: 25) // 指定初始化
let person2 = Person(name: "李四") // 便利初始化
let student1 = Student() // 必要初始化方法
let student2 = Student(studentID: 1001) // 子类便利初始化

1.2.19 初始化返回 nil

重点说明

Swift支持可失败初始化方法,在init后加?,可在初始化过程中校验条件,不满足时返回nil,中断初始化流程。可失败初始化方法可用于值类型和引用类型,常用于参数校验、资源加载失败等场景,返回的实例是可选类型,需要解包使用。

代码示例

// 1. 结构体的可失败初始化方法
struct User {
    let userID: String
    init?(userID: String) {
        // 校验条件,ID不能为空,且长度必须大于6
        guard !userID.isEmpty, userID.count > 6 else {
            return nil // 不满足条件,返回nil,初始化失败
        }
        self.userID = userID
    }
}

// 2. 类的可失败初始化方法
class Student {
    let studentID: Int
    init?(studentID: Int) {
        guard studentID > 0 else { return nil }
        self.studentID = studentID
    }
}

// 使用
let validUser = User(userID: "user_123456") // 初始化成功,User?类型
let invalidUser = User(userID: "user") // 初始化失败,返回nil

let validStudent = Student(studentID: 1001) // 成功
let invalidStudent = Student(studentID: 0) // nil

// 解包使用
if let user = validUser {
    print("用户ID:\(user.userID)")
}

1.2.20 static 和 class

重点说明

staticclass都用于定义类型级别的属性和方法,核心区别:

  • static:可用于类、结构体、枚举、协议,定义的方法/属性不能被子类重写;在协议中定义类型方法/属性时,必须用static
  • class:仅可用于类,定义的方法/计算属性可以被子类重写;不能用于存储属性,类的类型存储属性只能用static定义。

代码示例

// 1. 结构体/枚举中只能用static
struct AppConfig {
    static let appVersion = "1.0.0" // 类型存储属性
    static func printVersion() {
        print("App版本:\(appVersion)")
    }
}
AppConfig.printVersion()

// 2. 类中static和class的区别
class Person {
    // 类的类型存储属性,只能用static
    static let species = "人类"
    // static方法,不可被子类重写
    static func printSpecies() {
        print("物种:\(species)")
    }
    // class方法,可被子类重写
    class func greet() {
        print("你好,我是人类")
    }
    // class计算属性,可被子类重写
    class var averageAge: Int {
        return 75
    }
}

class Student: Person {
    // 重写class方法,不能重写static方法
    override class func greet() {
        print("你好,我是学生")
    }
    override class var averageAge: Int {
        return 18
    }
}

Person.greet() // 你好,我是人类
Student.greet() // 你好,我是学生
print(Student.averageAge) // 18

1.2.21 多类型和容器

重点说明

Swift中可通过AnyAnyObject、协议组合来实现多类型容器:

  • Any:可表示任意类型(值类型、引用类型、函数类型)。
  • AnyObject:可表示任意类类型的实例。
  • 协议组合:限定容器内的元素必须遵循指定的多个协议,比Any/AnyObject更安全,保留类型约束。

代码示例

// 1. Any类型的数组,可存储任意类型
var anyArray: [Any] = []
anyArray.append(10) // Int
anyArray.append("Swift") // String
anyArray.append((1, 2)) // 元组
anyArray.append({ print("闭包") }) // 闭包
print(anyArray)

// 2. AnyObject数组,仅能存储类实例
class Person {}
class Student {}
var objectArray: [AnyObject] = []
objectArray.append(Person())
objectArray.append(Student())

// 3. 协议组合的数组,类型安全,限定必须遵循指定协议
protocol Runnable {
    func run()
}
protocol Flyable {
    func fly()
}

class Bird: Runnable, Flyable {
    func run() { print("鸟跑") }
    func fly() { print("鸟飞") }
}
class Dog: Runnable {
    func run() { print("狗跑") }
}

// 数组元素必须同时遵循Runnable和Flyable协议
var flyAndRunArray: [Runnable & Flyable] = []
flyAndRunArray.append(Bird())
// flyAndRunArray.append(Dog()) // 编译报错,Dog不遵循Flyable

1.2.22 default 参数

重点说明

Swift支持为函数参数设置默认值,在参数定义时给参数赋值,调用时若未传入该参数,自动使用默认值。带默认值的参数可放在参数列表的任意位置,调用时可选择性省略,简化函数调用,无需为不同参数组合重载多个函数。

代码示例

import UIKit

// 带默认参数的函数
func greetUser(name: String, greeting: String = "你好", isVip: Bool = false) {
    let prefix = isVip ? "尊贵的VIP用户" : "用户"
    print("\(greeting)\(prefix)\(name)")
}

// 调用方式
greetUser(name: "张三") // 使用所有默认值
greetUser(name: "李四", greeting: "早上好") // 自定义greeting,isVip用默认值
greetUser(name: "王五", isVip: true) // 自定义isVip,greeting用默认值
greetUser(name: "赵六", greeting: "晚上好", isVip: true) // 所有参数都自定义

// 带默认参数的初始化方法
class Button {
    var title: String
    var frame: CGRect
    init(title: String, frame: CGRect = .zero) {
        self.title = title
        self.frame = frame
    }
}
let button1 = Button(title: "登录") // frame用默认值.zero
let button2 = Button(title: "注册", frame: CGRect(x: 0, y: 0, width: 100, height: 50))

1.2.23 正则表达式

重点说明

Swift中通过NSRegularExpression实现正则表达式功能,也可通过~=运算符、range(of:options:)快速匹配。Swift 5.7+引入了更简洁的正则字面量语法,支持直接用/.../定义正则。正则常用于字符串校验、匹配、替换、提取等场景,需处理初始化和匹配时的异常。

代码示例

import Foundation

// 1. 邮箱格式校验
func validateEmail(_ email: String) -> Bool {
    // 正则表达式:邮箱格式
    let emailRegex = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"
    // 匹配
    return email.range(of: emailRegex, options: .regularExpression) != nil
}

print(validateEmail("test@example.com")) // true
print(validateEmail("invalid-email")) // false

// 2. 提取字符串中的所有数字
func extractNumbers(from string: String) -> [String] {
    do {
        let regex = try NSRegularExpression(pattern: "\\d+", options: [])
        let matches = regex.matches(in: string, range: NSRange(string.startIndex..., in: string))
        return matches.map {
            String(string[Range($0.range, in: string)!])
        }
    } catch {
        print("正则初始化失败:\(error)")
        return []
    }
}

print(extractNumbers(from: "订单号12345,金额99元")) // ["12345", "99"]

// 3. Swift 5.7+ 正则字面量
let phoneRegex = /^1[3-9]\d{9}$/
let phoneNumber = "13812345678"
if phoneNumber.wholeMatch(of: phoneRegex) != nil {
    print("手机号格式正确")
}

1.2.24 模式匹配

重点说明

Swift的模式匹配是核心语法特性,核心用于switch语句,也可用于if caseguard casefor casewhile等场景。支持值匹配、区间匹配、元组匹配、可选值匹配、类型匹配、where条件过滤等,可灵活匹配复杂的条件,简化多分支判断代码。

代码示例

// 1. switch基础匹配+区间匹配
let score = 85
switch score {
case 0..<60: print("不及格")
case 60..<80: print("及格")
case 80..<90: print("良好")
case 90...100: print("优秀")
default: print("无效分数")
}

// 2. 元组匹配+值绑定
let userPoint = (x: 10, y: 0)
switch userPoint {
case (0, 0): print("原点")
case (_, 0): print("在X轴上,x值为\(userPoint.x)")
case (0, _): print("在Y轴上")
case (-10...10, -10...10): print("在10x10的范围内")
default: print("超出范围")
}

// 3. if case匹配,替代单分支switch
let age: Int? = 25
if case 20...30 = age {
    print("年龄在20-30之间")
}

// 4. for case匹配,遍历数组时过滤元素
let numbers: [Int?] = [1, nil, 3, nil, 5, 6]
for case let num? in numbers where num % 2 == 1 {
    print("奇数:\(num)") // 输出1,3,5
}

// 5. 类型匹配
let anyValue: Any = "Swift字符串"
switch anyValue {
case let str as String: print("是字符串,内容:\(str)")
case let num as Int: print("是整数:\(num)")
default: print("其他类型")
}

1.2.25 ... 和 ..<(闭区间与半开区间)

重点说明

Swift提供两种区间运算符,用于表示一个范围的值:

  • ... 闭区间运算符:a...b 表示包含a和b的所有值,范围从a到b,a不能大于b,适用于闭合范围的场景。
  • ..< 半开区间运算符:a..<b 表示包含a但不包含b的所有值,范围从a到b-1,特别适合遍历数组、集合等从0开始的索引场景,避免越界。 同时支持单侧区间,如a......b..<b,用于表示从某个值开始/结束的无限范围。

代码示例

// 1. 闭区间运算符
for number in 1...5 {
    print(number) // 输出1,2,3,4,5
}

// 2. 半开区间运算符,遍历数组
let fruits = ["苹果", "香蕉", "橙子", "葡萄"]
for index in 0..<fruits.count {
    print("索引\(index)\(fruits[index])")
} // 避免index等于fruits.count导致越界

// 3. 单侧区间
let names = ["张三", "李四", "王五", "赵六"]
// 从索引2开始到末尾
print(names[2...]) // ["王五", "赵六"]
// 从开头到索引2(不包含)
print(names[..<2]) // ["张三", "李四"]
// 从开头到索引2(包含)
print(names[...2]) // ["张三", "李四", "王五"]

// 4. 区间匹配
let temperature = 25
switch temperature {
case ..<0: print("严寒")
case 0...15: print("寒冷")
case 16...28: print("舒适")
case 29...: print("炎热")
default: break
}

1.2.26 AnyClass, 元类型和 .self

重点说明

  • 元类型:是类型的类型,用Type表示,如Int.Type就是Int的元类型,可存储类型本身,而非实例。
  • .self:两种用法,1. 实例.self返回实例本身;2. 类型.self返回该类型的元类型实例,可用于获取类型、动态创建实例、传递类型参数。
  • AnyClass:是AnyObject.Type的别名,代表任意类的元类型,可存储任意类类型。

代码示例

// 1. 类型.self获取元类型
let intType: Int.Type = Int.self
print(intType) // Int

// 2. AnyClass存储任意类的元类型
class Person {}
class Student: Person {}
var classType: AnyClass = Person.self
classType = Student.self // 可赋值为任意类的元类型

// 3. 通过元类型动态创建实例
protocol InitProtocol {
    init()
}
class Animal: InitProtocol {
    required init() {}
    func say() {
        print("动物叫")
    }
}
class Cat: Animal {
    override func say() {
        print("猫叫")
    }
}

// 接收元类型参数,动态创建实例
func createInstance(_ type: Animal.Type) -> Animal {
    return type.init()
}

let cat = createInstance(Cat.self)
cat.say() // 猫叫

// 4. 实例.self返回自身
let number = 10
print(number.self == 10) // true

1.2.27 协议和类方法中的 Self

重点说明

Self(大写S)是Swift中的动态类型,代表当前遵循/继承的类型本身,在协议、类、结构体、枚举中使用。

  • 协议中:Self表示遵循该协议的具体类型,用于约束方法的参数/返回值必须和遵循者类型一致,常用于协议的相等性判断、工厂方法等。
  • 类中:Self表示当前类的类型,在子类中会自动适配为子类的类型,可用于返回当前类的实例,确保子类调用时返回子类类型。

代码示例

// 1. 协议中使用Self
protocol Copyable {
    // 方法返回值必须是遵循该协议的类型本身
    func copy() -> Self
}

// 结构体实现协议,Self代表Person结构体
struct Person: Copyable {
    var name: String
    func copy() -> Self {
        return Person(name: self.name)
    }
}

// 类实现协议,必须用required初始化确保子类可返回Self
class Animal: Copyable {
    var name: String
    required init(name: String) {
        self.name = name
    }
    func copy() -> Self {
        return type(of: self).init(name: self.name)
    }
}

class Dog: Animal {
    var breed: String
    required init(name: String) {
        self.breed = "中华田园犬"
        super.init(name: name)
    }
}

// 使用
let person = Person(name: "张三")
let copiedPerson = person.copy()
print(type(of: copiedPerson)) // Person

let dog = Dog(name: "旺财")
let copiedDog = dog.copy()
print(type(of: copiedDog)) // Dog,返回子类类型,而非Animal

// 2. 类方法中使用Self
class BaseClass {
    class func createInstance() -> Self {
        return self.init()
    }
    required init() {}
}

class SubClass: BaseClass {
    var subProperty = 10
}

let subInstance = SubClass.createInstance()
print(type(of: subInstance)) // SubClass,返回子类类型

1.2.28 动态类型和多方法

重点说明

Swift是静态类型语言,但支持动态类型获取,通过type(of:)方法获取实例的动态运行时类型,而非静态声明类型。多方法(方法重载)是静态派发,根据参数的静态类型决定调用哪个方法;而类的继承重写是动态派发,根据实例的动态类型决定调用哪个方法。可通过type(of:)实现动态类型判断和处理。

代码示例

// 1. type(of:)获取动态类型
class Animal {
    func say() {
        print("动物叫")
    }
}
class Cat: Animal {
    override func say() {
        print("喵")
    }
}
class Dog: Animal {
    override func say() {
        print("汪")
    }
}

// 静态类型是Animal,动态类型是Cat
let animal: Animal = Cat()
print("静态类型:Animal")
print("动态类型:\(type(of: animal))") // Cat
animal.say() // 喵,动态派发,调用动态类型的方法

// 2. 多方法的静态派发
func printType(_ animal: Animal) {
    print("传入的是Animal类型")
}
func printType(_ cat: Cat) {
    print("传入的是Cat类型")
}

let cat: Animal = Cat()
printType(cat) // 输出"传入的是Animal类型",根据静态类型派发
let realCat = Cat()
printType(realCat) // 输出"传入的是Cat类型"

// 3. 根据动态类型处理
func handleAnimal(_ animal: Animal) {
    switch type(of: animal) {
    case is Cat.Type:
        print("处理猫")
    case is Dog.Type:
        print("处理狗")
    default:
        print("处理其他动物")
    }
}
handleAnimal(Dog()) // 处理狗

1.2.29 属性观察

重点说明

属性观察用于监听和响应属性值的变化,可给存储属性添加willSetdidSet两个观察器:

  • willSet:在属性值即将被设置时调用,可通过newValue获取即将设置的新值,此时旧值还未改变。
  • didSet:在属性值已经被设置后调用,可通过oldValue获取设置前的旧值,此时新值已生效,可在didSet中修改属性值(不会再次触发观察器)。 属性观察不能用于lazy存储属性、计算属性,可用于继承的属性重写时添加。

代码示例

class User {
    // 带属性观察的存储属性
    var username: String {
        willSet {
            print("即将将用户名从\(username)修改为\(newValue)")
        }
        didSet {
            print("用户名已从\(oldValue)修改为\(username)")
            // 可在didSet中修正值,不会再次触发观察器
            if username.isEmpty {
                username = "默认用户名"
            }
        }
    }
    
    var age: Int {
        didSet {
            // 限制年龄范围
            age = max(0, min(120, age))
        }
    }
    
    init(username: String, age: Int) {
        self.username = username
        self.age = age
    }
}

// 使用
let user = User(username: "张三", age: 25)
user.username = "李四"
// 输出:
// 即将将用户名从张三修改为李四
// 用户名已从张三修改为李四

user.age = 150
print(user.age) // 120,被didSet修正
user.age = -5
print(user.age) // 0

user.username = ""
print(user.username) // 默认用户名

1.2.30 final

重点说明

final修饰符用于限制类、方法、属性的继承和重写,可用于类、类的方法、属性、下标。

  • 修饰类:final class,该类不能被任何子类继承,无法作为父类,可提高编译性能,避免意外继承。
  • 修饰类的方法/属性/下标:final funcfinal var,子类不能重写该方法/属性/下标,确保父类的实现不会被子类修改,保证代码的安全性和稳定性。

代码示例

// 1. final修饰的类,不能被继承
final class FinalClass {
    func doSomething() {
        print("final类的方法")
    }
}
// 编译报错:Cannot inherit from final class 'FinalClass'
// class SubClass: FinalClass {}

// 2. final修饰方法和属性,禁止子类重写
class Person {
    final let species = "人类" // final属性,不能被子类重写
    var name: String
    init(name: String) {
        self.name = name
    }
    final func greet() { // final方法,不能被子类重写
        print("你好,我是\(name)")
    }
}

class Student: Person {
    var studentID: Int
    init(name: String, studentID: Int) {
        self.studentID = studentID
        super.init(name: name)
    }
    // 编译报错:Cannot override a final method
    // override func greet() {}
    
    // 编译报错:Cannot override a final property
    // override let species = "学生"
}

// 使用
let student = Student(name: "张三", studentID: 1001)
student.greet() // 调用父类的final方法

1.2.31 lazy 修饰符和 lazy 方法

重点说明

lazy(懒加载)用于延迟存储属性的初始化,只有当属性第一次被访问时,才会执行初始化代码并赋值,之后不会再次执行初始化。lazy属性必须用var声明,不能用let,因为初始化时机不是在实例初始化时完成。适合初始化开销大、不一定会用到的属性,非线程安全,多线程同时第一次访问可能会多次初始化。

代码示例

// 1. lazy存储属性基础使用
class DataManager {
    // 懒加载属性,第一次访问时才初始化
    lazy var bigDataArray: [String] = {
        print("正在初始化大数据数组")
        var array: [String] = []
        for i in 0..<1000 {
            array.append("数据\(i)")
        }
        return array
    }()
    
    // 懒加载属性,也可调用方法初始化
    lazy var config: [String: Any] = loadConfig()
    
    private func loadConfig() -> [String: Any] {
        print("正在加载配置")
        return ["version": "1.0.0", "env": "prod"]
    }
}

// 使用
let manager = DataManager()
// 实例初始化完成,lazy属性还未初始化,无打印输出
print("实例已创建")
// 第一次访问,执行初始化,打印"正在初始化大数据数组"
print(manager.bigDataArray.count) // 1000
// 第二次访问,直接使用已初始化的值,不会再次执行初始化
print(manager.bigDataArray.count) // 1000

// 访问config,执行loadConfig方法
print(manager.config["version"] ?? "未知")

1.2.32 Reflection 和 Mirror

重点说明

Swift通过Mirror类型实现反射(Reflection)功能,可在运行时获取实例的属性名、属性值、类型信息、继承关系等,无需提前知道类型结构。Mirror常用于调试、模型转字典、JSON解析、动态获取对象信息等场景,Swift的反射是只读的,不能通过反射修改属性值。

代码示例

// 定义模型
struct User {
    let userID: String
    var name: String
    var age: Int
    private var isVip: Bool
}

// 通过Mirror获取实例的反射信息
let user = User(userID: "user_123", name: "张三", age: 25, isVip: true)
let mirror = Mirror(reflecting: user)

// 1. 获取类型信息
print("类型:\(mirror.subjectType)") // User
print("类型分类:\(mirror.displayStyle!)") // struct

// 2. 遍历所有属性和值
print("\n属性列表:")
for case let (label?, value) in mirror.children {
    print("\(label)\(value)")
}
// 输出:
// userID:user_123
// name:张三
// age:25
// isVip:true

// 3. 模型转字典
func convertToDictionary(from object: Any) -> [String: Any] {
    let mirror = Mirror(reflecting: object)
    var dict: [String: Any] = [:]
    for case let (label?, value) in mirror.children {
        dict[label] = value
    }
    return dict
}

let userDict = convertToDictionary(from: user)
print("\n转字典结果:\(userDict)")

// 4. 递归获取继承的属性(类类型)
class Person {
    var name: String = ""
    var age: Int = 0
}
class Student: Person {
    var studentID: Int = 0
}

let student = Student()
student.name = "李四"
student.age = 20
student.studentID = 1001

func getAllProperties(_ object: Any) -> [String: Any] {
    var result: [String: Any] = [:]
    var mirror: Mirror? = Mirror(reflecting: object)
    while let currentMirror = mirror {
        for case let (label?, value) in currentMirror.children {
            result[label] = value
        }
        mirror = currentMirror.superclassMirror // 获取父类的Mirror
    }
    return result
}

print("\n学生所有属性:\(getAllProperties(student))")
// 包含studentID、name、age

1.2.33 隐式解包 Optional

重点说明

隐式解包可选类型(Implicitly Unwrapped Optionals),在类型后加!声明,是一种特殊的可选类型。声明时告诉编译器,该值在使用时一定会有值,无需手动解包,访问时会自动解包。若访问时值为nil,会直接崩溃。常用于初始化流程中无法立即赋值,但之后一定会赋值的属性(如IBOutlet),避免频繁解包,非必要不使用。

代码示例

// 1. 隐式解包可选类型声明
var username: String! = nil
// 赋值后,访问时自动解包,无需?或!
username = "张三"
print(username.count) // 2,自动解包,无需手动解包
print(type(of: username)) // Optional<String>,本质还是可选类型

// 2. 常见场景:IBOutlet
// @IBOutlet weak var titleLabel: UILabel!
// 初始化时为nil,xib/storyboard加载后会赋值,之后使用时无需解包

// 3. 风险:值为nil时访问直接崩溃
var age: Int! = nil
// print(age + 1) // 运行时崩溃,Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

// 4. 依然可以用可选绑定、可选链式调用处理
var address: String! = nil
if let address = address {
    print(address)
} else {
    print("地址为空")
}

address?.append("北京市") // 为nil时,链式调用返回nil,不会崩溃

1.2.34 多重 Optional

重点说明

多重可选类型,即可选类型的嵌套,一个可选值的包装值还是可选类型,如String??Int???等。每一层?代表一层可选包装,可能存在多层nil的情况,直接解包需要逐层处理,常用于可选链式调用、字典取值、函数返回值等场景,需注意区分不同层级的nil。

代码示例

// 1. 基础多重可选类型
let optionalString: String? = "Swift"
let doubleOptional: String?? = optionalString // 双重可选
let tripleOptional: String??? = doubleOptional // 三重可选

print(doubleOptional as Any) // Optional(Optional("Swift"))
print(tripleOptional as Any) // Optional(Optional(Optional("Swift")))

// 2. 多重可选的nil情况
var a: String? = nil
var b: String?? = a // 内层为nil,外层有包装
var c: String?? = nil // 外层直接为nil

print(a as Any) // nil
print(b as Any) // Optional(nil)
print(c as Any) // nil

// 3. 解包多重可选,需要逐层解包
if let firstLevel = b {
    if let secondLevel = firstLevel {
        print(secondLevel)
    } else {
        print("内层为nil")
    }
} else {
    print("外层为nil")
}
// 输出:内层为nil

// 4. 可选绑定一次性解包多层
if let value = tripleOptional {
    print(value)
}

// 5. 常见场景:字典中存储可选值
let dict: [String: String?] = ["name": "张三", "address": nil]
let name = dict["name"] // String??类型,双重可选
// 第一层:字典取值是否存在key,第二层:key对应的value是否为nil

if let nameValue = dict["name"], let realName = nameValue {
    print(realName) // 张三
}

1.2.35 Optional Map

重点说明

Optional的map方法,用于对可选值进行转换:当可选值有值时,执行闭包中的转换逻辑,返回包装后的新值;当可选值为nil时,直接返回nil,不会执行闭包。相比可选绑定,map可更简洁地处理可选值的转换,无需手动解包;flatMap方法用于转换后依然是可选值的场景,避免生成多重可选。

代码示例

// 1. map基础使用
let numberString: String? = "123"
// 可选值有值时,执行转换,返回Int?
let number = numberString.map { Int($0) }
print(type(of: number)) // Int??,map返回的是包装后的可选值,这里转换后还是Int?,所以变成双重可选

// 2. flatMap处理转换后依然是可选值的场景,扁平化处理
let flatNumber = numberString.flatMap { Int($0) }
print(type(of: flatNumber)) // Int?,flatMap自动解包,避免多重可选

// 3. 对比可选绑定,map更简洁
let input: Int? = 5
// 可选绑定写法
var squared: Int?
if let num = input {
    squared = num * num
}
// map写法
let squaredMap = input.map { $0 * $0 }
print(squaredMap ?? 0) // 25

// 4. nil时的处理
let nilInput: Int? = nil
let nilResult = nilInput.map { $0 * 10 }
print(nilResult as Any) // nil,闭包不会执行

// 5. 链式调用
let priceString: String? = "99.9"
let finalPrice = priceString
    .flatMap { Double($0) }
    .map { $0 * 0.8 } // 打8折
    .map { String(format: "%.2f", $0) }

print(finalPrice ?? "无效价格") // 79.92

1.2.36 Protocol Extension(协议扩展)

重点说明

协议扩展是Swift的核心特性,可给协议提供默认的方法实现、计算属性实现,遵循该协议的类型无需自己实现,自动获得默认实现;也可给协议扩展添加额外的方法,所有遵循者都可使用。可通过where子句给扩展添加约束,只给满足条件的遵循者提供实现,实现面向协议编程。

代码示例

// 定义协议
protocol Greetable {
    var name: String { get }
    func greet() // 协议方法
}

// 协议扩展,提供默认实现
extension Greetable {
    // 协议方法的默认实现
    func greet() {
        print("你好,我是\(name)")
    }
    
    // 扩展添加额外的方法,所有遵循者都可使用
    func greetWithTitle(_ title: String) {
        print("\(title) \(name),向你问好")
    }
}

// 遵循协议,无需实现greet方法,自动获得默认实现
struct Person: Greetable {
    var name: String
}

struct Student: Greetable {
    var name: String
    var studentID: Int
    // 重写协议方法,覆盖默认实现
    func greet() {
        print("你好,我是学生\(name),学号\(studentID)")
    }
}

// 使用
let person = Person(name: "张三")
person.greet() // 你好,我是张三,使用默认实现
person.greetWithTitle("先生") // 先生 张三,向你问好

let student = Student(name: "李四", studentID: 1001)
student.greet() // 你好,我是学生李四,学号1001,使用重写的实现

// 带where约束的协议扩展
extension Greetable where Self: Student {
    func greetToTeacher() {
        print("老师好,我是\(name)")
    }
}

student.greetToTeacher() // 只有Student类型可调用
// person.greetToTeacher() // 编译报错,Person不满足约束

1.2.37 where 和模式匹配

重点说明

where子句可用于模式匹配中,给匹配添加额外的约束条件,过滤不符合条件的情况,让模式匹配更灵活精准。可用于switchif casefor caseguard case、协议扩展、泛型约束等场景,支持多个条件组合,简化复杂的条件判断代码。

代码示例

// 1. switch中使用where添加约束
let number = 15
switch number {
case let n where n % 2 == 0 && n > 10:
    print("大于10的偶数")
case let n where n % 2 == 1 && n > 10:
    print("大于10的奇数")
default:
    print("小于等于10")
}
// 输出:大于10的奇数

// 2. for case + where,遍历过滤
let scores = [59, 85, 92, 76, 45, 98]
// 只遍历大于80的分数
for case let score in scores where score > 80 {
    print("高分:\(score)")
}
// 输出85、92、98

// 3. if case + where
let age: Int? = 22
if case let a? in age where a >= 18 && a <= 30 {
    print("年龄在18-30之间,符合要求")
}

// 4. 元组匹配 + where
let userInfo = (name: "张三", age: 25, isVip: true)
switch userInfo {
case let (name, age, _) where age < 18:
    print("\(name)是未成年人")
case let (name, _, isVip) where isVip:
    print("\(name)是VIP用户")
default:
    print("普通用户")
}

// 5. 泛型约束中使用where
func compare<T: Equatable>(_ a: T, _ b: T) -> Bool where T: Comparable {
    return a > b
}

1.2.38 indirect 和嵌套 enum

重点说明

  • 嵌套enum:Swift支持在枚举、结构体、类中嵌套定义枚举,将相关的枚举封装在所属的类型中,避免命名冲突,优化代码结构,提高代码的封装性。
  • indirect:用于修饰枚举的case或整个枚举,告诉编译器该枚举case包含递归的关联值(即关联值类型是枚举自身),需要添加一层间接引用,解决递归枚举的内存布局问题,常用于树形结构、链表、表达式等递归数据结构。

代码示例

// 1. 嵌套枚举
class Order {
    // 嵌套枚举,仅在Order类内部使用,也可开放访问
    enum OrderStatus {
        case pending
        case paid
        case shipped
        case completed
        case cancelled
    }
    
    enum PayType {
        case wechat
        case alipay
        case card
    }
    
    var status: OrderStatus = .pending
    var payType: PayType = .wechat
}

// 使用
let order = Order()
order.status = .paid
order.payType = .alipay
print(order.status) // paid

// 2. indirect修饰递归枚举,实现表达式计算
// 整个枚举添加indirect,所有case都支持递归
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 也可给单个case添加indirect
// enum ArithmeticExpression {
//     case number(Int)
//     indirect case addition(ArithmeticExpression, ArithmeticExpression)
//     indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
// }

// 实现表达式计算
func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}

// 计算 (5 + 3) * 2
let five = ArithmeticExpression.number(5)
let three = ArithmeticExpression.number(3)
let sum = ArithmeticExpression.addition(five, three)
let two = ArithmeticExpression.number(2)
let result = ArithmeticExpression.multiplication(sum, two)

print(evaluate(result)) // 16

从Object-c/c 到Swift

1.3.1 Selector

重点说明
  1. Swift 中使用 #selector 替代 Objective-C 的 @selector 关键字,用于将方法转换为 Selector 结构体类型,本质是 Objective-C 运行时的方法选择器。
  2. Swift 4 及以上版本默认关闭了对 NSObject 子类非私有方法的 @objc 自动推断,必须手动为需要生成 selector 的方法添加 @objc 关键字,将方法暴露给 Objective-C 运行时。
  3. 私有方法、非 NSObject 子类的方法,无法自动生成 selector,必须手动标记 @objc,否则会出现运行时 unrecognized selector 崩溃。
  4. 同一作用域内存在同名但参数不同的方法时,需通过强制类型转换为对应函数签名,消除 selector 的歧义。
代码示例
import UIKit

class MyClass: NSObject {
    // 标记@objc暴露给OC运行时
    @objc func callMe() {
        print("Hi")
    }
    
    @objc func callMeWithParam(obj: AnyObject!) {
        print("带参数的方法")
    }
    
    // 同名不同参数的方法
    @objc func commonFunc() {
        print("无参方法")
    }
    
    @objc func commonFunc(input: Int) -> Int {
        return input
    }
}

// 基础selector生成
let someMethod = #selector(MyClass.callMe)
let anotherMethod = #selector(MyClass.callMeWithParam(obj:))

// 多参数selector生成
@objc func turn(by angle: Int, speed: Float) {}
let method = #selector(turn(by:speed:))

// 消除同名方法的selector歧义
let method1 = #selector(MyClass.commonFunc as ()->())
let method2 = #selector(MyClass.commonFunc as (Int)->Int)

// 定时器中使用selector示例
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1, target: object, selector: #selector(MyClass.callMe), userInfo: nil, repeats: true)

1.3.2 实例方法的动态调用

重点说明
  1. Swift 支持通过类型名.实例方法的语法,获取该实例方法的柯里化函数,实现实例方法的动态调用。
  2. 该语法生成的柯里化函数格式为:(实例类型) -> (方法参数) -> 返回值,先传入实例对象,再传入方法参数,即可完成方法调用。
  3. 该特性仅适用于实例方法,属性的 getter/setter 无法使用此方式调用。
  4. 当类方法与实例方法重名时,可通过显式声明函数类型,区分获取的是类方法还是实例方法的柯里化函数。
代码示例
class MyClass {
    func method(number: Int) -> Int {
        return number + 1
    }
    
    // 与实例方法重名的类方法
    class func method(number: Int) -> Int {
        return number
    }
}

// 常规实例方法调用
let object = MyClass()
let result1 = object.method(number: 1)
print(result1) // 输出2

// 动态获取实例方法的柯里化函数
let methodFunc: (MyClass) -> (Int) -> Int = MyClass.method
let result2 = methodFunc(object)(1)
print(result2) // 输出2

// 区分重名的类方法与实例方法
// 获取类方法
let classMethod = MyClass.method
// 获取实例方法
let instanceMethod: (MyClass) -> (Int) -> Int = MyClass.method

1.3.3 单例

重点说明
  1. Swift 1.2 之后支持静态类变量,提供了极简的单例实现方式,底层会通过 swift_once 保证初始化的线程安全,与 GCD 的 dispatch_once 效果一致。
  2. 标准单例实现需添加私有初始化方法,防止外部通过 init() 创建新实例,保证单例的唯一性。
  3. 若需要支持子类继承,可移除私有初始化方法;若需要类似 default 形式的单例(允许外部创建实例),也可去掉私有 init
  4. Swift 1.2 之前的全局变量、嵌套结构体等实现方式已被淘汰,不推荐使用。
代码示例
// Swift 推荐的标准单例实现
class MyManager {
    // 静态常量,保证线程安全的唯一实例
    static let shared = MyManager()
    
    // 私有初始化方法,禁止外部创建实例
    private init() {}
    
    // 单例的业务方法
    func doSomething() {
        print("单例方法执行")
    }
}

// 单例使用
MyManager.shared.doSomething()

// 允许继承的单例实现
class BaseManager {
    class var shared: BaseManager {
        struct Static {
            static let instance = BaseManager()
        }
        return Static.instance
    }
    
    init() {}
}

class ChildManager: BaseManager {
    override class var shared: BaseManager {
        struct Static {
            static let instance = ChildManager()
        }
        return Static.instance
    }
}

1.3.4 条件编译

重点说明
  1. Swift 中使用 #if/#elseif/#else/#endif 实现条件编译,替代 C 系语言的宏定义条件分支。
  2. 内置了平台、架构、Swift 版本三类条件判断参数,大小写敏感,分别为:
    • 平台:os(),可选值 macOS/iOS/tvOS/watchOS/Linux
    • 架构:arch(),可选值 x86_64/arm/arm64/i386
    • Swift 版本:swift(),格式为 >= 版本号
  3. 支持自定义编译符号,需在项目 Build Settings - Other Swift Flags 中添加 -D 符号名 启用。
  4. 条件编译仅能针对编译时可确定的符号,无法使用运行时的变量进行判断。
代码示例
import UIKit

// 平台条件编译
#if os(macOS)
import AppKit
typealias Color = NSColor
#elseif os(iOS)
import UIKit
typealias Color = UIColor
#endif

// 架构条件编译
#if arch(arm64)
print("64位ARM架构")
#elseif arch(x86_64)
print("64位x86架构")
#endif

// Swift版本条件编译
#if swift(>=5.0)
print("Swift 5.0及以上版本")
#else
print("Swift 5.0以下版本")
#endif

// 自定义编译符号条件编译
#if FREE_VERSION
// 免费版功能代码
print("免费版本")
#else
// 付费版功能代码
print("付费版本")
#endif

1.3.5 编译标记

重点说明
  1. Swift 中使用 // MARK: 替代 Objective-C 的 #pragma mark,用于在 Xcode 导航栏中对代码进行分块标记,提升代码可读性。
  2. // MARK: - 会在标记位置添加分隔线,更清晰地区分代码模块。
  3. 支持 // TODO:// FIXME: 标记,分别用于标记待完成的工作和待修复的问题,会在 Xcode 导航栏中同步显示。
  4. Swift 暂不支持 Objective-C 中的 #warning 编译警告标记,无法在编译时生成自定义警告。
代码示例
import UIKit

class SimpleTimer: NSObject {
    // MARK: 公共属性
    var running: Bool = false
    var leftTime: TimeInterval = 0
    var leftTimeString: String {
        return String(format: "%.1f", leftTime)
    }
    
    // MARK: 初始化方法
    public init(timeInterval: TimeInterval) {
        self.leftTime = timeInterval
        super.init()
    }
    
    // MARK: 计时器控制方法
    public func start(updateTick: (TimeInterval) -> Void, stopHandler: () -> Void) {
        // 计时器启动逻辑
    }
    
    public func stop() {
        // 计时器停止逻辑
    }
    
    // MARK: 内部私有方法
    private func countTick() {
        // 计时逻辑
        // TODO: 优化计时精度
        // FIXME: 修复负数时间异常
    }
}

1.3.6 @UIApplicationMain

重点说明
  1. @UIApplicationMain 标记用于修饰 AppDelegate 类,替代 Objective-C 中的 main.m 入口文件,编译器会自动为其生成 main 函数,作为 iOS 应用的程序入口。
  2. 该标记的核心作用是:创建 UIApplication 单例、设置 AppDelegate 为应用的生命周期委托、启动应用的主 Runloop。
  3. 若需要自定义 UIApplication 的子类,需移除 @UIApplicationMain 标记,手动创建 main.swift 文件,在其中编写程序入口代码。
  4. main.swift 是 Swift 项目中特殊的文件,无需定义作用域,可直接书写执行代码,其内容会被作为 main 函数执行。
代码示例
// 1. 默认使用@UIApplicationMain的AppDelegate
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
}

// 2. 自定义UIApplication子类,移除@UIApplicationMain,创建main.swift
// main.swift 文件内容
import UIKit

// 自定义UIApplication子类
class MyApplication: UIApplication {
    override func sendEvent(_ event: UIEvent) {
        super.sendEvent(event)
        print("事件发送: \(event)")
    }
}

// 程序入口,等价于main函数
UIApplicationMain(
    CommandLine.argc,
    CommandLine.unsafeArgv,
    NSStringFromClass(MyApplication.self),
    NSStringFromClass(AppDelegate.self)
)

1.3.7 @objc 和 dynamic

重点说明
  1. @objc 用于将 Swift 类型、方法、属性暴露给 Objective-C 运行时,编译器会为其生成 Objective-C 可识别的符号。
  2. NSObject 的子类,Swift 不会自动为私有方法、属性添加 @objc,必须手动标记;Swift 4 及以上版本,即使是 NSObject 子类的公开方法,也需要手动添加 @objc 才能暴露给 OC。
  3. @objc(OC名称) 可重命名 Swift 类型/方法在 Objective-C 中的名称,解决中文命名、方法名冲突等问题。
  4. dynamic 用于强制开启 Swift 方法的 Objective-C 动态派发,替代 OC 运行时的消息转发机制。仅添加 @objc 不会开启动态派发,编译器仍可能将其优化为静态调用。
  5. KVO、Runtime 方法交换、动态方法解析等特性,必须依赖 dynamic 开启的动态派发才能实现。
代码示例
import UIKit

// 重命名Swift类在OC中的名称
@objc(MyClassOC)
class 我的类: NSObject {
    var num = 1
    
    // 重命名Swift方法在OC中的名称
    @objc(greeting:)
    func 打招呼(名字: String) {
        print("哈喽,\(名字)")
    }
    
    // 开启动态派发,支持KVO/方法交换
    @objc dynamic var date = Date()
}

// OC中调用方式:
// [[MyClassOC new] greeting:@"小明"];

// 仅@objc不开启动态派发,dynamic强制动态派发
class KVOObject: NSObject {
    // 必须标记@objc dynamic才能支持KVO
    @objc dynamic var value: Int = 0
}

1.3.8 可选协议和协议扩展

重点说明
  1. 原生 Swift 协议默认要求所有方法、属性必须被实现,无可选概念;有两种方式实现可选协议:
    • 方式一:为协议和可选方法添加 @objc,使用 optional 关键字标记可选方法,仅能被 class 实现,struct/enum 无法使用。
    • 方式二:通过协议扩展为协议方法提供默认实现,实现类可选择性重写,是 Swift 推荐的纯原生实现方式,支持值类型。
  2. 协议扩展中实现了协议未定义的方法时,调用规则为:
    • 实例类型被推断为协议类型时,永远调用协议扩展中的默认实现。
    • 实例类型被推断为实际实现类型时,优先调用类型中的实现,无实现则调用协议扩展默认实现。
  3. 协议中定义的方法,会通过动态派发调用类型中的重写实现,无论实例被推断为协议类型还是实际类型。
代码示例
import UIKit

// 方式一:@objc + optional 实现可选协议
@objc protocol OptionalProtocolOC {
    @objc optional func optionalMethod() // 可选
    func necessaryMethod() // 必须实现
    @objc optional func anotherOptionalMethod() // 可选
}

// 实现类
class ClassOC: NSObject, OptionalProtocolOC {
    // 仅实现必须方法,可选方法可选择不实现
    func necessaryMethod() {
        print("必须实现的方法")
    }
}

// 方式二:协议扩展实现可选方法(推荐)
protocol OptionalProtocol {
    func optionalMethod() // 可选
    func necessaryMethod() // 必须
    func anotherOptionalMethod() // 可选
}

// 协议扩展提供默认实现
extension OptionalProtocol {
    func optionalMethod() {
        print("协议扩展中的默认实现")
    }
    
    func anotherOptionalMethod() {
        print("协议扩展中的默认实现")
    }
}

// 实现类,仅需实现必须方法
class MyStruct: OptionalProtocol {
    func necessaryMethod() {
        print("结构体中实现的必须方法")
    }
    
    // 选择性重写可选方法
    func optionalMethod() {
        print("结构体中重写的可选方法")
    }
}

// 调用测试
let obj = MyStruct()
obj.necessaryMethod() // 结构体中实现的必须方法
obj.optionalMethod() // 结构体中重写的可选方法
obj.anotherOptionalMethod() // 协议扩展中的默认实现

// 类型推断为协议时的调用差异
let proto: OptionalProtocol = MyStruct()
proto.anotherOptionalMethod() // 协议扩展中的默认实现(协议未定义该方法)
proto.optionalMethod() // 结构体中重写的方法(协议中定义了该方法)

1.3.9 内存管理,weak 和 unowned

重点说明
  1. Swift 基于自动引用计数(ARC)管理内存,当对象无任何强引用时,内存会被自动回收;循环引用是 ARC 最核心的问题,需通过 weakunowned 打破循环。
  2. weak:弱引用,不会增加对象的引用计数,当引用的对象被释放后,会自动被置为 nil,因此必须声明为可选类型。适用于引用对象生命周期可能短于当前对象的场景,如 delegate、闭包中对 self 的引用。
  3. unowned:无主引用,不会增加对象的引用计数,引用的对象被释放后,不会被置为 nil,仍会保留无效的内存地址。访问已释放的 unowned 引用会直接崩溃,类似 OC 的 unsafe_unretained。适用于引用对象与当前对象生命周期一致的场景。
  4. 闭包中对 self 的引用会自动持有,若 self 直接/间接持有闭包,会形成 self->闭包->self 的循环引用,需通过闭包捕获列表 [weak self]/[unowned self] 解决。
代码示例
import UIKit

// 循环引用示例
class A: NSObject {
    var b: B!
    override init() {
        super.init()
        b = B()
        b.a = self
    }
    
    deinit {
        print("A deinit")
    }
}

class B: NSObject {
    // 用weak打破循环引用
    weak var a: A?
    deinit {
        print("B deinit")
    }
}

// 测试:无循环引用,对象正常释放
var obj: A? = A()
obj = nil
// 输出:A deinit、B deinit

// 闭包循环引用示例
class Person {
    let name: String
    lazy var printName: ()->() = {
        // 捕获列表声明weak self
        [weak self] in
        guard let self = self else { return }
        print("The name is \(self.name)")
    }
    
    init(personName: String) {
        name = personName
    }
    
    deinit {
        print("Person deinit \(self.name)")
    }
}

// 测试:闭包无循环引用,对象正常释放
var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:The name is XiaoMing、Person deinit XiaoMing

// unowned使用示例:确保引用对象生命周期一致
class Student {
    unowned let card: StudentCard
    init(card: StudentCard) {
        self.card = card
    }
}

class StudentCard {
    let student: Student
    init() {
        self.student = Student(card: self)
    }
}

1.3.10 @autoreleasepool

重点说明
  1. Swift 中可通过 autoreleasepool 函数创建自动释放池,替代 Objective-C 的 @autoreleasepool {} 语法,用于管理 autorelease 对象的生命周期。
  2. 自动释放池会在作用域结束时,对池内的 autorelease 对象统一发送 release 消息,减少内存峰值,避免循环中大量 autorelease 对象堆积导致内存溢出。
  3. Swift 中推荐使用初始化方法替代类工厂方法,减少 autorelease 对象的生成;仅在循环中生成大量临时对象时,需要手动添加自动释放池。
  4. autoreleasepool 函数接受一个无返回值的闭包,利用尾随闭包语法可简化书写。
代码示例
import UIKit

// 循环中大量生成autorelease对象,手动添加自动释放池
func loadBigData() {
    guard let path = Bundle.main.path(forResource: "big", ofType: "jpg") else {
        return
    }
    
    for _ in 1...10000 {
        // 每次循环创建自动释放池,及时释放内存
        autoreleasepool {
            let data = NSData(contentsOfFile: path)
            // 数据处理逻辑
            Thread.sleep(forTimeInterval: 0.001)
        }
    }
}

// 折衷方案:每10次循环释放一次,平衡性能与内存
func loadBigDataOptimized() {
    guard let path = Bundle.main.path(forResource: "big", ofType: "jpg") else {
        return
    }
    
    for i in 1...10000 {
        if i % 10 == 0 {
            autoreleasepool {
                let data = NSData(contentsOfFile: path)
                Thread.sleep(forTimeInterval: 0.001)
            }
        } else {
            let data = NSData(contentsOfFile: path)
            Thread.sleep(forTimeInterval: 0.001)
        }
    }
}

1.3.11 值类型和引用类型

重点说明
  1. Swift 中类型分为两大类:
    • 值类型:struct/enum,包括 Swift 所有内建类型(Int/String/Array/Dictionary 等),赋值、传参时会进行值拷贝,内存分配在栈上。
    • 引用类型:class,赋值、传参时仅传递引用指针,内存分配在堆上。
  2. Swift 对值类型的拷贝做了优化,采用**写时复制(Copy-On-Write, COW)**机制:仅当值类型的内容发生修改时,才会执行真正的内存拷贝,未修改时多个变量共享同一块内存,大幅降低拷贝开销。
  3. 值类型的优势:线程安全、无循环引用风险、栈内存分配效率远高于堆,适合轻量数据模型、集合类型。
  4. 引用类型的优势:支持继承、共享状态、适合生命周期复杂的对象;当数据量极大、频繁修改时,引用类型可避免大量拷贝开销。
代码示例
import UIKit

// 值类型:struct,赋值时拷贝
struct User {
    var name: String
    var age: Int
}

var user1 = User(name: "张三", age: 20)
var user2 = user1 // 写时复制,此时共享内存
user2.name = "李四" // 修改时才执行真正拷贝
print(user1.name) // 张三
print(user2.name) // 李四

// 数组的写时复制特性
var array1 = [1,2,3,4,5]
var array2 = array1 // 共享内存,无拷贝
print(array1.baseAddress == array2.baseAddress) // true
array2.append(6) // 修改时拷贝
print(array1.baseAddress == array2.baseAddress) // false

// 引用类型:class,赋值时仅传递引用
class Person {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

var p1 = Person(name: "张三", age: 20)
var p2 = p1 // 传递引用,指向同一块堆内存
p2.name = "李四"
print(p1.name) // 李四
print(p2.name) // 李四

1.3.12 String 还是 NSString

重点说明
  1. Swift 原生 String 与 Foundation 的 NSString 可无缝桥接转换,推荐优先使用原生 String 类型。
  2. 原生 String 的优势:
    • 是值类型,赋值、传参时遵循写时复制,线程安全。
    • 实现了 Collection 协议,支持 for...in 遍历、序列相关操作,NSString 不支持。
    • 基于 Unicode 正确处理字符,避免 NSString 按 UTF-16 编码计算长度导致的字符处理错误。
  3. NSString 的适用场景:
    • 使用部分 NSString 特有 API,如 contains(_:)(Swift 3 后 String 已原生支持)、substring(with:)、与 C 字符串交互等。
    • 与 Objective-C API 交互时,需显式桥接为 NSString
  4. Swift 中 StringNSRange 配合使用较为繁琐,需转换为 String.Index,简单场景可先桥接为 NSString 简化操作。
代码示例
import UIKit

let str = "Hello Swift"
let nsStr = str as NSString

// String原生遍历(NSString不支持for...in)
for char in str {
    print(char)
}

// String与NSRange转换
let levels = "ABCDE"
// 原生String方式
let start = levels.index(levels.startIndex, offsetBy: 1)
let end = levels.index(levels.startIndex, offsetBy: 3)
let swiftRange = start..<end
let swiftResult = levels.replacingCharacters(in: swiftRange, with: "12")
print(swiftResult) // A12DE

// 桥接为NSString简化NSRange操作
let nsRange = NSMakeRange(1, 2)
let nsResult = (levels as NSString).replacingCharacters(in: nsRange, with: "12")
print(nsResult) // A12DE

// 字符串包含判断
print(levels.contains("BC")) // true,Swift原生支持
print((levels as NSString).contains("BC")) // true,NSString API

1.3.13 UnsafePointer

重点说明
  1. Swift 中通过 UnsafePointer<T>/UnsafeMutablePointer<T> 对应 C 中的 const Type */Type * 指针,提供对 C 指针的类型安全访问。
  2. 指针类型前缀说明:
    • UnsafePointer:不可变指针,对应 C 常量指针,无法修改指向内存的内容。
    • UnsafeMutablePointer:可变指针,对应 C 普通指针,可修改指向内存的内容。
    • pointee 属性:用于访问/修改指针指向的内存内容,替代 C 中的 * 解引用操作。
  3. 传入指针参数时,在变量前添加 & 符号,与 C 语法一致,可将 Swift 变量转换为对应类型的指针。
  4. unsafeBitCast 可强制按位转换指针类型,完全绕过编译器类型检查,属于极度不安全的操作,仅在明确内存布局时使用。
  5. Swift 官方将指针类型冠以 Unsafe 前缀,明确提示其不安全性,非必要场景应避免直接操作指针。
代码示例
import UIKit

// C函数示例:void method(const int *num) { printf("%d",*num); }
// Swift中对应的函数声明
func method(_ num: UnsafePointer<CInt>) {
    print(num.pointee)
}

// 基础指针使用
var a: CInt = 123
method(&a) // 输出123

// 可变指针修改内存内容
func modifyValue(_ num: UnsafeMutablePointer<CInt>) {
    num.pointee += 100
}

var b: CInt = 50
modifyValue(&b)
print(b) // 150

// unsafeBitCast强制类型转换
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
print(str) // meow

1.3.14 C 指针内存管理

重点说明
  1. Swift 中手动创建的指针,无法被 ARC 自动管理内存,需手动申请、释放内存,遵循谁创建谁释放的原则。
  2. 指针内存操作的完整流程:
    1. allocate(capacity:):申请指定容量的内存空间。
    2. initialize(to:):初始化内存,为其赋值。
    3. 访问/修改 pointee 属性,操作内存内容。
    4. deinitialize():反初始化内存,销毁内存中的对象。
    5. deallocate(capacity:):释放内存空间,与 allocate 成对出现。
  3. 若通过 malloc/calloc 申请内存,需使用 free 释放,不可使用 deallocate
  4. 访问已释放的指针、重复释放内存,都会导致程序崩溃,与 C 语言手动内存管理的风险一致。
代码示例
import UIKit

class MyClass {
    var a = 1
    deinit {
        print("deinit")
    }
}

// 完整的指针内存管理流程
// 1. 申请内存
let pointer = UnsafeMutablePointer<MyClass>.allocate(capacity: 1)
// 2. 初始化内存
pointer.initialize(to: MyClass())
// 3. 访问内存内容
print(pointer.pointee.a) // 1
// 4. 反初始化
pointer.deinitialize(count: 1)
// 5. 释放内存
pointer.deallocate(capacity: 1)

// 错误示例:内存泄漏,未释放内存
let leakPointer = UnsafeMutablePointer<MyClass>.allocate(capacity: 1)
leakPointer.initialize(to: MyClass())
// 未调用deinitialize和deallocate,对象无法释放,内存泄漏

// malloc/free 内存管理
let mallocPointer = malloc(MemoryLayout<Int>.size)!
let intPointer = mallocPointer.bindMemory(to: Int.self, capacity: 1)
intPointer.pointee = 100
print(intPointer.pointee) // 100
free(mallocPointer) // 对应malloc使用free释放

1.3.15 COpaquePointer 和 C convention

重点说明
  1. COpaquePointer 对应 C 中的不透明指针(Opaque Pointer),用于表示 Swift 无法描述具体类型的 C 指针,即 C 中仅声明类型名、未暴露实现细节的结构体指针。
  2. COpaquePointer 可与具体的 UnsafePointer<T> 互相转换,用于不同指针类型间的强制转换,属于不安全操作。
  3. C 中的函数指针,在 Swift 中通过 @convention(c) 标记的闭包对应,可直接将 Swift 闭包传递给接受 C 函数指针的 API。
  4. @convention(c) 用于标记闭包遵循 C 函数调用约定,与 Swift 原生闭包区分,可与 C 函数指针无缝桥接。
代码示例
import UIKit
import Darwin

// COpaquePointer 与 UnsafePointer 转换
let intPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
intPtr.pointee = 100
// 转换为不透明指针
let opaquePtr = COpaquePointer(intPtr)
// 转回具体类型指针
let intPtr2 = UnsafeMutablePointer<Int>(opaquePtr)
print(intPtr2.pointee) // 100
intPtr.deallocate(capacity: 1)

// C函数指针与Swift闭包
// C函数示例:int cFunction(int (*callback)(int x, int y)) { return callback(1,2); }
// Swift中对应的函数声明
func cFunction(_ callback: @convention(c) (Int32, Int32) -> Int32) -> Int32 {
    return callback(1, 2)
}

// 定义遵循C调用约定的闭包
let callback: @convention(c) (Int32, Int32) -> Int32 = { x, y in
    return x + y
}

// 调用C函数,传入闭包
let result = cFunction(callback)
print(result) // 3

// 简化写法:直接传入尾随闭包
let result2 = cFunction { x, y in
    return x * y
}
print(result2) // 2

1.3.16 GCD 和延时调用

重点说明
  1. Swift 3 及以上版本对 GCD 进行了面向对象的重构,替代了原有的 C 语言风格 API,使用更简洁。
  2. 核心队列类型 DispatchQueue,分为主队列(main,主线程执行,用于UI更新)、串行队列、并行队列,通过 async/sync 执行任务。
  3. 延时执行通过 asyncAfter(deadline:execute:) 实现,替代 OC 中的 dispatch_after,基于 DispatchTime 控制延时时间。
  4. 可通过 DispatchWorkItem 封装任务,支持取消操作,实现可取消的延时执行。
  5. GCD 任务执行规则:UI 相关操作必须在主队列执行;耗时操作需放在后台串行/并行队列,避免阻塞主线程。
代码示例
import UIKit

// MARK: 基础GCD使用
// 创建串行队列
let workingQueue = DispatchQueue(label: "com.example.serial_queue")
// 异步执行耗时任务
workingQueue.async {
    print("后台执行耗时任务", Thread.current)
    Thread.sleep(forTimeInterval: 2) // 模拟耗时操作
    
    // 回到主线程更新UI
    DispatchQueue.main.async {
        print("回到主线程更新UI", Thread.current)
    }
}

// MARK: 基础延时执行
let delayTime: TimeInterval = 2.0
DispatchQueue.main.asyncAfter(deadline: .now() + delayTime) {
    print("2秒后执行")
}

// MARK: 可取消的延时执行
typealias DelayTask = (_ cancel: Bool) -> Void

func delay(_ time: TimeInterval, task: @escaping ()->()) -> DelayTask? {
    var closure: (()->Void)? = task
    var result: DelayTask?
    
    let delayedClosure: DelayTask = { cancel in
        if let internalClosure = closure, !cancel {
            DispatchQueue.main.async(execute: internalClosure)
        }
        closure = nil
        result = nil
    }
    
    result = delayedClosure
    DispatchQueue.main.asyncAfter(deadline: .now() + time) {
        delayedClosure(false)
    }
    return result
}

// 取消任务的方法
func cancel(_ task: DelayTask?) {
    task?(true)
}

// 使用示例
let task = delay(5) {
    print("5秒后执行的任务")
}

// 提前取消任务
cancel(task)

1.3.17 获取对象类型

重点说明
  1. Swift 中通过 type(of:) 函数获取对象的动态类型(运行时实际类型),替代 OC 中的 class 方法、Swift 旧版本的 dynamicType 属性。
  2. type(of:) 同时支持 Swift 原生类型、NSObject 子类、值类型(struct/enum),是获取类型的通用方法。
  3. 对于类继承体系,type(of:) 会返回对象的实际子类类型,而非声明的父类类型,体现动态特性。
  4. 可通过 String(describing:) 将类型转换为字符串,用于打印、日志输出;debugPrint 会输出包含模块名的完整类型名称。
代码示例
import UIKit

// 原生Swift类型
let string = "Hello World"
let stringType = type(of: string)
print(stringType) // String
debugPrint(stringType) // Swift.String

// NSObject子类
let date = NSDate()
let dateType = type(of: date)
print(dateType) // __NSDate

// 继承体系的动态类型
class Animal {
    func speak() {}
}

class Cat: Animal {
    override func speak() {
        print("Meow")
    }
}

let animal: Animal = Cat()
print(type(of: animal)) // Cat,返回实际运行时类型,而非声明的Animal类型

// 值类型
struct Person {
    var name: String
}
let person = Person(name: "小明")
print(type(of: person)) // Person

1.3.18 自省

重点说明
  1. 自省(Introspection)指在运行时判断对象是否属于某个类型/是否遵循某个协议,是面向对象编程的常用特性。
  2. Swift 中使用 is 关键字判断对象是否属于某个类型(包括子类),功能等价于 OC 中的 isKindOfClass:
  3. 使用 as?/as! 进行类型转换,as? 为可选转换,失败返回 nil;as! 为强制转换,失败直接崩溃,推荐优先使用 as? + 可选绑定。
  4. is 关键字不仅支持 class 类型,还可用于 struct、enum、protocol 等 Swift 所有类型,比 OC 的自省能力更全面。
  5. 编译器会对编译时可确定的类型判断给出警告,避免无意义的类型检查。
代码示例
import UIKit

// 类型继承体系
protocol Runnable {
    func run()
}

class Animal {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Dog: Animal, Runnable {
    func run() {
        print("\(name) 跑起来了")
    }
}

class Cat: Animal {
    func speak() {
        print("Meow")
    }
}

// 基础类型判断
let animal: Animal = Dog(name: "旺财")
print(animal is Dog) // true,属于Dog类
print(animal is Animal) // true,属于Animal父类
print(animal is Cat) // false,不属于Cat类

// 协议判断
print(animal is Runnable) // true,遵循Runnable协议

// 类型转换+可选绑定,安全使用子类方法
if let dog = animal as? Dog {
    dog.run() // 旺财 跑起来了
}

// 强制类型转换,确定类型时使用
let dog = animal as! Dog
dog.run()

// 多类型分支判断
func checkAnimalType(_ animal: Animal) {
    switch animal {
    case let dog as Dog:
        print("这是狗:\(dog.name)")
    case let cat as Cat:
        print("这是猫:\(cat.name)")
    default:
        print("未知动物")
    }
}

checkAnimalType(Dog(name: "大黄")) // 这是狗:大黄
checkAnimalType(Cat(name: "咪酱")) // 这是猫:咪酱

1.3.19 KeyPath 和 KVO

重点说明
  1. KVO(Key-Value Observing)是 Objective-C 运行时的键值观察特性,用于监听对象属性的变化,Swift 中仅支持 NSObject 子类的 KVO。
  2. Swift 4 引入了原生 KeyPath 语法,格式为 \类型名.属性名,替代了 OC 中的字符串键路径,提供编译时类型安全,避免拼写错误。
  3. 被监听的属性必须标记 @objc dynamic,强制开启 Objective-C 动态派发,否则无法触发 KVO 回调。
  4. Swift 中提供了基于闭包的 KVO 监听 API,替代了 OC 中重写 observeValue(forKeyPath:of:change:context:) 的方式,代码更简洁、回调更集中。
  5. 监听结果会通过 NSKeyValueObservation 实例持有,实例释放后监听自动失效,无需手动移除,避免了 OC 中忘记移除监听导致的崩溃。
代码示例
import UIKit

// 支持KVO的类,必须继承自NSObject
class MyClass: NSObject {
    // 必须标记@objc dynamic才能支持KVO
    @objc dynamic var date = Date()
    @objc dynamic var count = 0
}

// MARK: Swift 4 原生KeyPath+闭包KVO(推荐)
class ObserverClass: NSObject {
    var observedObject: MyClass!
    // 持有监听实例,释放后自动停止监听
    var observation: NSKeyValueObservation?
    
    override init() {
        super.init()
        observedObject = MyClass()
        print("初始化,当前日期:\(observedObject.date)")
        
        // 基于KeyPath添加监听
        observation = observedObject.observe(\MyClass.date, options: [.new, .old]) { _, change in
            if let newDate = change.newValue {
                print("日期发生变化:\(newDate)")
            }
            if let oldDate = change.oldValue {
                print("变化前日期:\(oldDate)")
            }
        }
        
        // 1秒后修改属性,触发KVO回调
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.observedObject.date = Date()
        }
    }
}

// 启动监听
let observer = ObserverClass()

// MARK: 传统OC风格KVO(兼容)
class TraditionalObserver: NSObject {
    var myObject: MyClass!
    private var myContext = 0
    
    override init() {
        super.init()
        myObject = MyClass()
        // 添加监听
        myObject.addObserver(self, forKeyPath: "count", options: .new, context: &myContext)
    }
    
    // 重写监听回调方法
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard context == &myContext, keyPath == "count" else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
            return
        }
        if let newCount = change?[.newKey] as? Int {
            print("count 变化为:\(newCount)")
        }
    }
    
    // 销毁时移除监听
    deinit {
        myObject.removeObserver(self, forKeyPath: "count", context: &myContext)
    }
}

1.3.20 局部 scope

重点说明
  1. 局部 scope 用于将代码按功能分块,限制临时变量的作用域,避免命名污染,同时让超出作用域的变量尽早释放,优化内存使用。
  2. Swift 中无法直接使用 {} 创建局部作用域(与闭包语法冲突),有三种实现方式:
    • 方式一:使用 do {} 代码块,Swift 2.0 及以上支持,是官方推荐的原生方式。
    • 方式二:定义全局 local 函数,接受闭包并执行,模拟局部作用域。
    • 方式三:使用立即执行的匿名闭包,常用于属性初始化。
  3. 局部作用域的优势:让代码逻辑更清晰,临时变量仅在块内可见,减少代码维护成本;同时让 ARC 能及时回收块内的临时对象,降低内存峰值。
代码示例
import UIKit

// MARK: 方式一:do 代码块(推荐)
func setupUI() {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
    view.backgroundColor = .white
    
    // 局部作用域:配置标题标签
    do {
        let titleLabel = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
        titleLabel.textColor = .red
        titleLabel.text = "Title"
        view.addSubview(titleLabel)
    }
    
    // 局部作用域:配置内容标签
    do {
        let textLabel = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
        textLabel.textColor = .black
        textLabel.text = "Text"
        view.addSubview(textLabel)
    }
    
    // 此处无法访问 titleLabel 和 textLabel,避免命名冲突
}

// MARK: 方式二:local 函数实现
func local(_ closure: () -> ()) {
    closure()
}

func setupUIV2() {
    let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
    
    local {
        let titleLabel = UILabel()
        // 配置逻辑
        view.addSubview(titleLabel)
    }
    
    local {
        let button = UIButton()
        // 配置逻辑
        view.addSubview(button)
    }
}

// MARK: 方式三:立即执行闭包,常用于属性初始化
class ViewController: UIViewController {
    // 立即执行闭包,初始化属性,代码集中
    let titleLabel: UILabel = {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
        label.textColor = .red
        label.font = .systemFont(ofSize: 16)
        label.textAlignment = .center
        return label
    }()
}

1.3.21 判等

重点说明
  1. Swift中内容判等统一使用==操作符,替代Objective-C的isEqual:系列方法;!=由标准库自动基于==取反实现,无需单独定义。
  2. ==Equatable协议的核心方法,自定义类型需实现该协议才能使用判等操作,判等逻辑由业务需求决定(如通过唯一ID判断对象相等)。
  3. NSObject子类的特殊规则:
    • 若未重载==,Swift会自动调用该类的-isEqual:方法。
    • 若重写了-isEqual:必须同步重写-hash方法,保证"相等对象哈希值相同",否则作为字典key时会出现异常。
  4. Swift中使用===判断引用相等(两个变量是否指向同一个对象实例),对应Objective-C的指针比较==
代码示例
// 1. 基础类型判等
let str1 = "快乐的字符串"
let str2 = "快乐的字符串"
print(str1 == str2) // true

// 2. 自定义类型实现Equatable
class TodoItem {
    let uuid: String
    var title: String
    
    init(uuid: String, title: String) {
        self.uuid = uuid
        self.title = title
    }
}

extension TodoItem: Equatable {
    static func == (lhs: TodoItem, rhs: TodoItem) -> Bool {
        return lhs.uuid == rhs.uuid // 仅通过唯一ID判等
    }
}

// 3. 引用相等判断
let item1 = TodoItem(uuid: "123", title: "写代码")
let item2 = TodoItem(uuid: "123", title: "写代码")
let item3 = item1
print(item1 == item2)  // true(内容相等)
print(item1 === item2) // false(不同实例)
print(item1 === item3) // true(同一实例)

1.3.22 哈希

重点说明
  1. Swift字典的key必须实现Hashable协议,该协议继承自Equatable,要求提供hashValue属性。
  2. 核心规则:判等结果为true的两个对象,必须拥有相同的哈希值;但哈希值相同不代表对象一定相等(哈希冲突)。
  3. NSObject子类的hashValue对应Objective-C的-hash方法,重写-isEqual:时必须同步重写-hash
  4. 哈希值是单向且不稳定的:不可依赖哈希值做持久化存储或全局唯一性判断,其实现可能随系统版本、平台变化。
代码示例
// 1. 基础类型的哈希值
let num = 19
print(num.hashValue) // 19(Int的哈希值为自身)

// 2. 自定义类型实现Hashable(结合判等)
class TodoItem: Equatable, Hashable {
    let uuid: String
    var title: String
    
    init(uuid: String, title: String) {
        self.uuid = uuid
        self.title = title
    }
    
    static func == (lhs: TodoItem, rhs: TodoItem) -> Bool {
        return lhs.uuid == rhs.uuid
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(uuid) // 仅用唯一ID生成哈希
    }
}

// 3. 作为字典key使用
var todoDict: [TodoItem: String] = [:]
let item = TodoItem(uuid: "123", title: "写代码")
todoDict[item] = "待完成"
print(todoDict[item]!) // 待完成

1.3.23 类簇

重点说明
  1. 类簇是Cocoa核心设计模式:用统一的公共父类暴露接口,底层由多个私有子类实现具体逻辑,隐藏实现细节并简化接口。
  2. Objective-C中通过在init方法中替换self实现类簇;但Swift的init是真正的初始化方法,只能返回当前类实例,因此Swift中通过工厂方法实现类簇
  3. 工厂方法根据输入参数返回对应私有子类的实例,对外只暴露公共父类的接口。
代码示例
import UIKit

// 公共父类
class Drinking {
    typealias LiquidColor = UIColor
    var color: LiquidColor {
        return .clear
    }
    
    // 工厂方法:类簇的核心
    class func drinking(name: String) -> Drinking {
        switch name {
        case "Coke":
            return Coke()
        case "Beer":
            return Beer()
        default:
            return Drinking()
        }
    }
}

// 私有子类:可乐
class Coke: Drinking {
    override var color: LiquidColor {
        return .black
    }
}

// 私有子类:啤酒
class Beer: Drinking {
    override var color: LiquidColor {
        return .yellow
    }
}

// 使用
let coke = Drinking.drinking(name: "Coke")
print(coke.color) // black
let beer = Drinking.drinking(name: "Beer")
print(beer.color) // yellow

1.3.24 调用C动态库

重点说明
  1. Swift无法直接导入C头文件,需通过Objective-C桥接文件{product-module-name}-Bridging-Header.h)导入C库的头文件。
  2. 系统内置C动态库(如libzCommonCrypto)无需额外链接,仅需在桥接文件中导入对应头文件即可调用。
  3. 也可使用纯Swift重写C库功能,但推荐优先使用经过长期验证的系统C库,以保证稳定性和减小安装包体积。
代码示例
// 桥接文件:TargetName-Bridging-Header.h
#import <CommonCrypto/CommonCrypto.h>
// Swift代码:实现String的MD5扩展
import Foundation

extension String {
    var MD5: String {
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        if let data = data(using: .utf8) {
            data.withUnsafeBytes { bytes in
                CC_MD5(bytes.baseAddress, CC_LONG(data.count), &digest)
            }
        }
        
        var digestHex = ""
        for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
            digestHex += String(format: "%02x", digest[index])
        }
        return digestHex
    }
}

// 测试
print("swifter.tips".MD5) // dff88de99ff03d109de22fed4f71a273

1.3.25 输出格式化

重点说明
  1. Swift的print支持字符串插值,无需记忆C风格的类型占位符,但不支持直接在插值中指定格式(如小数位数、对齐方式)。
  2. 格式化输出需使用String(format:)方法,语法与C的printf完全兼容。
  3. 可通过扩展常用类型(如DoubleInt)封装格式化方法,简化重复的格式化调用。
代码示例
let a = 3
let b = 1.234567

// 1. 基础格式化
let format = String(format: "int:%d double:%.2f", a, b)
print(format) // int:3 double:1.23

// 2. 扩展Double封装格式化
extension Double {
    func format(_ f: String) -> String {
        return String(format: "%\(f)f", self)
    }
}

print("double:\(b.format(".2"))") // double:1.23
print("double:\(b.format(".4"))") // double:1.2346

1.3.26 Options

重点说明
  1. Swift中没有Objective-C的NS_OPTIONS宏,使用**实现OptionSet协议的struct**替代。
  2. OptionSet遵循SetAlgebra协议,支持集合运算(并集、交集、差集),多个选项使用数组字面量组合。
  3. 无选项时使用**空集合[]**表示,对应Objective-C的kNilOptions
代码示例
import UIKit

// 1. 系统API的Options使用
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseIn, .allowUserInteraction], animations: {
    // 动画代码
}, completion: nil)

// 2. 自定义Options
struct YourOption: OptionSet {
    let rawValue: UInt
    
    static let none = YourOption(rawValue: 0)
    static let option1 = YourOption(rawValue: 1 << 0)
    static let option2 = YourOption(rawValue: 1 << 1)
    static let option3 = YourOption(rawValue: 1 << 2)
}

// 使用自定义Options
let options: YourOption = [.option1, .option2]
if options.contains(.option1) {
    print("包含option1")
}

1.3.27 数组enumerate

重点说明
  1. Objective-C的enumerateObjectsUsingBlock:在Swift中仍可使用,但依赖NSArray且性能不佳,不推荐使用。
  2. Swift原生推荐使用数组的enumerated()方法,返回包含(下标, 元素)的多元组,类型安全且效率更高。
  3. enumerated()配合for-in循环可同时获取索引和元素,支持break/continue控制循环流程。
代码示例
// 1. 不推荐:NSArray的enumerateObjects
let arr: NSArray = [1,2,3,4,5]
var result = 0
arr.enumerateObjects { (num, idx, stop) in
    result += num as! Int
    if idx == 2 {
        stop.pointee = true // 中断循环
    }
}
print(result) // 6

// 2. 推荐:Swift原生enumerated()
result = 0
for (idx, num) in [1,2,3,4,5].enumerated() {
    result += num
    if idx == 2 {
        break // 中断循环
    }
}
print(result) // 6

1.3.28 类型编码@encode

重点说明
  1. Objective-C的@encode用于获取类型对应的C字符串编码,Swift中无对应关键字
  2. Swift中可通过NSValueobjcType属性获取值的类型编码指针,再转换为字符串。
  3. 类型编码常用于运行时类型信息传递,如NSValuevalueWithBytes:objcType:方法。
代码示例
import UIKit

// 1. 基础数字类型的编码
let int: Int = 0
let float: Float = 0.0
let double: Double = 0.0

let intType = String(validatingUTF8: (int as NSNumber).objcType)
let floatType = String(validatingUTF8: (float as NSNumber).objcType)
let doubleType = String(validatingUTF8: (double as NSNumber).objcType)

print(intType!)    // q(64位Int)
print(floatType!)  // f
print(doubleType!) // d

// 2. 结构体类型的编码
let point = NSValue(cgPoint: CGPoint(x: 3, y: 3))
let pointType = String(validatingUTF8: point.objcType)
print(pointType!) // {CGPoint=dd}

let transform = NSValue(cgAffineTransform: .identity)
let transformType = String(validatingUTF8: transform.objcType)
print(transformType!) // {CGAffineTransform=dddddd}

1.3.29 C代码调用和@asmname

重点说明
  1. 调用自定义C代码的标准方式:将C头文件导入桥接文件,Swift中直接调用C函数。
  2. @asmname(现已更名为@_silgen_name)可直接映射C函数到Swift,无需桥接文件;还可重命名函数,解决命名冲突。
  3. 系统C库(如Darwin)的函数无需额外导入,Swift中可直接调用。
代码示例
// test.h
int test(int a);

// test.c
int test(int a) {
    return a + 1;
}
// 桥接文件导入(标准方式)
#import "test.h"
// 1. 标准方式调用
func testSwift(input: Int32) {
    let result = test(input)
    print(result)
}
testSwift(input: 1) // 2

// 2. 使用@asmname直接映射(无需桥接文件)
@_silgen_name("test") func c_test(a: Int32) -> Int32

func testSwift2(input: Int32) {
    let result = c_test(input)
    print(result)
}
testSwift2(input: 2) // 3

1.3.30 delegate

重点说明
  1. Swift中协议默认可被classstructenum实现,但weak只能修饰引用类型,因此直接声明weak var delegate: MyProtocol?会编译错误。
  2. 需将协议限制为仅能由class实现,有两种方式:
    • 添加@objc修饰符(兼容Objective-C,但会引入不必要的运行时开销)。
    • 在协议名后加: class(Swift原生方式,推荐使用)。
代码示例
// ❌ 错误:weak不能修饰非class类型的协议
protocol MyClassDelegate {
    func method()
}
class MyClass {
    weak var delegate: MyClassDelegate? // 编译错误
}

// ✅ 正确:限制协议仅能由class实现
protocol MyClassDelegate: class {
    func method()
}

class MyClass {
    weak var delegate: MyClassDelegate?
}

// 使用
class ViewController: UIViewController, MyClassDelegate {
    var someInstance: MyClass!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        someInstance = MyClass()
        someInstance.delegate = self
    }
    
    func method() {
        print("Do something")
    }
}

1.3.31 Associated Object

重点说明
  1. Swift中无法通过extension给类添加存储属性,需借助Objective-C运行时的关联对象实现。
  2. 核心API:
    • objc_getAssociatedObject:读取关联值
    • objc_setAssociatedObject:设置关联值,需指定关联策略(如OBJC_ASSOCIATION_RETAIN_NONATOMIC
  3. 关联对象的key通常声明为private Void?类型,通过&取地址作为全局唯一标识。
代码示例
import ObjectiveC

class MyClass {}

// 关联对象的key
private var titleKey: Void?

extension MyClass {
    var title: String? {
        get {
            return objc_getAssociatedObject(self, &titleKey) as? String
        }
        set {
            objc_setAssociatedObject(
                self,
                &titleKey,
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}

// 使用
let a = MyClass()
print(a.title ?? "没有设置") // 没有设置
a.title = "Swifter.tips"
print(a.title!) // Swifter.tips

1.3.32 Lock

重点说明
  1. Swift中没有Objective-C的@synchronized关键字,可通过objc_sync_enterobjc_sync_exit实现相同的互斥锁功能。
  2. 可封装全局synchronized方法,配合Swift的尾随闭包特性,简化使用语法。
  3. 加锁会带来性能开销,应仅在多线程共享资源访问时使用,避免过度加锁导致性能下降。
代码示例
import ObjectiveC

// 封装全局synchronized方法
func synchronized(_ lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

// 1. 基础使用
func myMethodLocked(anObj: AnyObject) {
    synchronized(anObj) {
        // 临界区:持有anObj的锁
    }
}

// 2. 线程安全的setter示例
class ThreadSafeObj {
    private var _str = "123"
    var str: String {
        get {
            return _str
        }
        set {
            synchronized(self) {
                _str = newValue
            }
        }
    }
}

1.3.33 Toll-Free Bridging 和 Unmanaged

重点说明
  1. Toll-Free Bridging(免费桥接)是 Core Foundation 与 Foundation 类型之间的无缝转换机制,如 NSStringCFStringNSURLCFURL 等,二者内存结构一致,可直接桥接转换。
  2. Swift 中对系统 Core Foundation API 进行了内存管理自动桥接,遵循 CF 命名规则的 API(含 Create/Copy/Retain)会自动纳入 ARC 管理,无需手动调用 CFRelease
  3. 非系统、未标注内存管理规则的第三方 CF API,返回的对象会被包装为 Unmanaged<T> 类型,需手动管理内存。
  4. Unmanaged 类型的内存管理方法:
    • takeUnretainedValue():不改变引用计数,获取对象值,适用于非 Create/Copy 方法返回的对象。
    • takeRetainedValue():引用计数+1,获取对象值,使用完后需手动调用 release() 释放,适用于 Create/Copy 方法返回的对象。
代码示例
import UIKit
import CoreFoundation

// MARK: 系统CF类型与Foundation类型的免费桥接,自动内存管理
// NSString 与 CFString 无缝转换
let nsString: NSString = "Hello Swift"
let cfString: CFString = nsString as CFString
// 反向转换
let nsString2 = cfString as NSString
print(nsString2) // Hello Swift

// NSURL 与 CFURL 转换
let nsUrl = NSURL(string: "https://www.apple.com")!
let cfUrl = nsUrl as CFURL
let nsUrl2 = cfUrl as NSURL

// 系统CF API,自动内存管理,无需手动CFRelease
let font = CTFontCreateWithName("PingFangSC-Regular" as CFString, 16, nil)
// 无需调用CFRelease(font),ARC自动管理

// MARK: Unmanaged 手动内存管理
// 模拟第三方CF API,未标注内存规则,返回Unmanaged类型
// 示例:非Create方法,不转移所有权
func unmanagedGetObject() -> Unmanaged<CFString> {
    let str = "Unmanaged String" as CFString
    return Unmanaged.passUnretained(str)
}

// 示例:Create方法,转移所有权,需手动释放
func unmanagedCreateObject() -> Unmanaged<CFString> {
    let str = CFStringCreateWithCString(kCFAllocatorDefault, "Created String", kCFStringEncodingUTF8)
    return Unmanaged.passRetained(str!)
}

// 1. 非Create方法,使用takeUnretainedValue
let unmanagedStr1 = unmanagedGetObject()
let str1 = unmanagedStr1.takeUnretainedValue()
print(str1) // Unmanaged String
// 无需手动释放

// 2. Create方法,使用takeRetainedValue,手动释放
let unmanagedStr2 = unmanagedCreateObject()
let str2 = unmanagedStr2.takeRetainedValue()
print(str2) // Created String
// 使用完后手动释放
unmanagedStr2.release()

Swift 与开发环境及一些实践

1.4.1 Swift命令行工具

重点说明

  1. 支持REPL交互式环境,输入语句即时编译执行,适合快速验证语言特性
  2. 可直接运行.swift文件,添加#!/usr/bin/env swift并修改权限后可作为可执行脚本
  3. 使用swiftc编译多个.swift文件,生成独立的二进制可执行文件
  4. 支持生成汇编级代码,用于分析编译器优化行为和底层实现

代码示例

// 1. 脚本方式运行(hello.swift)
#!/usr/bin/env swift
print("hello")
// 终端执行:chmod 755 hello.swift && ./hello.swift

// 2. 编译生成可执行文件
// MyClass.swift
class MyClass {
    let name = "XiaoMing"
    func hello() { print("Hello \(name)") }
}
// main.swift
let object = MyClass()
object.hello()
// 终端编译:swiftc MyClass.swift main.swift
// 运行:./main

// 3. 生成汇编代码
// swiftc -O hello.swift -o hello.asm

1.4.2 随机数生成

重点说明

  1. arc4random()返回UInt32,32位平台转Int可能溢出,不推荐直接使用
  2. 推荐使用arc4random_uniform(n),返回0..<nUInt32,避免溢出和模运算偏差
  3. 可封装Range的随机数生成方法,提升代码复用性

代码示例

// ❌ 错误用法:32位平台可能溢出崩溃
let diceFaceCount = 6
let randomRoll = Int(arc4random()) % diceFaceCount + 1

// ✅ 正确用法
let randomRoll = Int(arc4random_uniform(UInt32(diceFaceCount))) + 1

// 封装Range随机数方法
func random(in range: Range<Int>) -> Int {
    let count = UInt32(range.endIndex - range.startIndex)
    return Int(arc4random_uniform(count)) + range.startIndex
}
// 调用
for _ in 0...10 {
    print(random(in: 1..<7))
}

1.4.3 print 和 debugPrint

重点说明

  1. 普通class对象print默认只输出类型名,struct会自动输出所有成员名和值
  2. 实现CustomStringConvertible协议可自定义print的输出格式
  3. 实现CustomDebugStringConvertible协议可自定义调试时(如po命令)的输出
  4. 推荐通过extension实现协议,保持核心代码整洁

代码示例

struct Meeting {
    var date: Date
    var place: String
    var attendeeName: String
}

// 自定义输出格式
extension Meeting: CustomStringConvertible {
    var description: String {
        return "于 \(self.date)\(self.place)\(self.attendeeName) 进行会议"
    }
}

// 调用
let meeting = Meeting(date: Date(), place: "会议室B1", attendeeName: "小明")
print(meeting)
// 输出:于 2026-04-11 08:00:00 +0000 在 会议室B1 与 小明 进行会议

1.4.4 错误和异常处理

重点说明

  1. Swift异常本质是错误处理,仅适合同步方法,异步API仍使用Error回调
  2. throws标记可抛出错误的方法,调用时必须使用try/do-catch
  3. 三种try用法:
    • try:正常调用,需配合do-catch捕获错误
    • try!:强制执行,出错直接崩溃
    • try?:返回Optional,出错返回nil
  4. rethrows用于高阶函数,表示仅当参数闭包抛出错误时,自身才会抛出
  5. 推荐用泛型Result枚举处理异步错误,统一成功/失败状态

代码示例

// 定义错误类型
enum LoginError: Error {
    case UserNotFound
    case UserPasswordNotMatch
}

// 标记可抛出错误的方法
func login(user: String, password: String) throws {
    let users = ["onevcat": "123"]
    guard users.keys.contains(user) else { throw LoginError.UserNotFound }
    guard users[user] == password else { throw LoginError.UserPasswordNotMatch }
    print("Login successfully.")
}

// 调用
do {
    try login(user: "onevcat", password: "123")
} catch LoginError.UserNotFound {
    print("用户不存在")
} catch LoginError.UserPasswordNotMatch {
    print("密码错误")
}

// 泛型Result处理异步错误
enum Result<T> {
    case Success(T)
    case Failure(Error)
}

1.4.5 断言

重点说明

  1. 仅在Debug编译时生效,Release版本自动禁用,不影响运行时性能
  2. 用于检查输入参数的边界条件,提前暴露开发阶段的逻辑错误
  3. 可通过编译标记强制控制断言:-assert-config Debug强制启用,-assert-config Release强制禁用
  4. Objective-C的NSAssert已被Swift彻底移除

代码示例

let absoluteZeroInCelsius = -273.15

func convertToKelvin(_ celsius: Double) -> Double {
    // 断言:输入温度不能低于绝对零度
    assert(celsius > absoluteZeroInCelsius, "输入的摄氏温度不能低于绝对零度")
    return celsius - absoluteZeroInCelsius
}

let roomTemperature = convertToKelvin(27) // 正常运行
let tooCold = convertToKelvin(-300) // Debug模式崩溃,Release模式继续执行

1.4.6 fatalError

重点说明

  1. 无论Debug/Release版本都会执行,执行后程序立即强制终止
  2. 用于处理绝对不应该发生的情况,如抽象方法未实现、不支持的初始化方法
  3. 可模拟Objective-C的抽象函数,强制子类重写父类方法
  4. 常用在init(coder:)等必须实现但项目中不会用到的初始化方法

代码示例

// 1. 模拟抽象方法
class MyClass {
    func methodMustBeImplementedInSubclass() {
        fatalError("这个方法必须在子类中被重写")
    }
}

class YourClass: MyClass {
    override func methodMustBeImplementedInSubclass() {
        print("YourClass 实现了该方法")
    }
}

// 2. 不支持的初始化方法
class ViewController: UIViewController {
    required init?(coder: NSCoder) {
        fatalError("NSCoding not supported")
    }
}

1.4.7 代码组织和Framework

重点说明

  1. iOS不允许第三方动态框架,自制Cocoa Touch Framework本质是静态库,需嵌入app bundle
  2. Swift框架推荐以源码依赖形式使用,避免Swift运行时版本不兼容
  3. 框架中需要对外暴露的类、方法、属性必须添加public修饰符
  4. 需用lipo命令合并模拟器和真机架构的二进制文件,生成通用Framework

代码示例

// 框架中的代码(HelloKit.framework)
public class Hello {
    public class func sayHello() {
        print("Hello Kit")
    }
}

// app中调用
import HelloKit
Hello.sayHello() // 输出:Hello Kit

1.4.8 安全的资源组织方式

重点说明

  1. 直接用字符串指定资源名存在拼写错误、改名后漏改的运行时风险
  2. rawValueStringenum封装资源名,结合extension提供类型安全的API
  3. 推荐使用R.swiftSwiftGen等工具自动生成资源类型,避免手动维护

代码示例

// 定义资源枚举
enum ImageName: String {
    case myImage = "my_image"
}

enum SegueName: String {
    case mySegue = "my_segue"
}

// 扩展系统类提供类型安全的API
extension UIImage {
    convenience init!(imageName: ImageName) {
        self.init(named: imageName.rawValue)
    }
}

extension UIViewController {
    func performSegue(withName segueName: SegueName, sender: Any?) {
        performSegue(withIdentifier: segueName.rawValue, sender: sender)
    }
}

// 调用
let image = UIImage(imageName: .myImage)
performSegue(withName: .mySegue, sender: self)

1.4.9 Playground延时运行

重点说明

  1. Playground默认执行完所有顶层语句后立即退出,异步代码无法正常执行
  2. 导入PlaygroundSupport框架,设置needsIndefiniteExecution = true开启延时运行
  3. 默认最长运行30秒,可在辅助编辑器右下角修改超时时间
  4. 适用于测试网络请求、定时器、动画等异步逻辑

代码示例

import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

// 1. 定时器测试
class MyClass {
    @objc func callMe() { print("Hi") }
}
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1, target: object, selector: #selector(MyClass.callMe), userInfo: nil, repeats: true)

// 2. 网络请求测试
let url = URL(string: "http://httpbin.org/get")!
URLSession.shared.dataTask(with: url) { data, _, error in
    if let data = data, let json = try? JSONSerialization.jsonObject(with: data) {
        print(json)
    }
}.resume()

1.4.10 Playground与项目协作

重点说明

  1. 单独的Playground无法使用项目代码,必须将Playground加入项目中
  2. 想要复用的项目代码必须组织为Cocoa Touch Framework的独立target
  3. 框架需针对64位模拟器编译,且Xcode的Derived Data保持默认位置
  4. 在Playground中导入框架的module名,即可使用项目中的代码

代码示例

无单独代码,核心是项目配置步骤。

1.4.11 Playground可视化开发

重点说明

  1. 支持直接显示UIViewUIViewController,通过PlaygroundPage.current.liveView赋值
  2. 打开Assistant Editor(Alt+Shift+Cmd+Return)可查看实时UI并进行交互
  3. 适用于快速原型开发、UI组件调试,无需每次重启模拟器

代码示例

import UIKit
import PlaygroundSupport

// 显示UILabel
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 200))
label.backgroundColor = .white
label.font = UIFont.systemFont(ofSize: 32)
label.textAlignment = .center
label.text = "Hello World"
PlaygroundPage.current.liveView = label

// 显示可交互的UITableViewController
class ViewController: UITableViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .cyan
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = String(indexPath.row)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Select: \(indexPath.row)")
    }
}

PlaygroundPage.current.liveView = ViewController()

1.4.12 数学和数字

重点说明

  1. 导入Darwin可使用所有C标准数学函数(sincosM_PI等)
  2. Double.infinity表示无穷大,超过Double最大值的数会被判定为无穷
  3. NaN(Not a Number)表示未定义/错误运算结果,不能用==判断,需用isNaN属性

代码示例

import Darwin

// 圆周率计算
func circlePerimeter(radius: Double) -> Double {
    return 2 * Double.pi * radius
}

// 无穷大判断
print(1.797693134862315e+308 < Double.infinity) // true
print(1.797693134862316e+308 < Double.infinity) // false

// NaN判断
let a = 0.0 / 0.0
print(a == a) // false
print(a.isNaN) // true

1.4.13 JSON和Codable

重点说明

  1. Swift 4引入Codable协议,替代手动JSON解析,自动处理序列化/反序列化
  2. 所有成员都实现Codable的类型,可自动满足Codable要求
  3. CodingKeys枚举映射JSON键名与Swift属性名(如下划线转驼峰)
  4. JSONDecoder/JSONEncoder处理Data与对象的转换,错误时抛出异常

代码示例

// JSON结构
// {"menu": { "id": "file", "value": "File", "popup": { "menuitem": [{"value": "New", "onclick": "CreateNewDoc()"}] }}}

// 定义Codable类型
struct Obj: Codable {
    let menu: Menu
    
    struct Menu: Codable {
        let id: String
        let value: String
        let popup: Popup
    }
    
    struct Popup: Codable {
        let menuItem: [MenuItem]
        enum CodingKeys: String, CodingKey {
            case menuItem = "menuitem"
        }
    }
    
    struct MenuItem: Codable {
        let value: String
        let onClick: String
        enum CodingKeys: String, CodingKey {
            case value
            case onClick = "onclick"
        }
    }
}

// 解析JSON
let jsonString = "..." // 上述JSON字符串
let data = jsonString.data(using: .utf8)!
do {
    let obj = try JSONDecoder().decode(Obj.self, from: data)
    print(obj.menu.popup.menuItem[0].value) // 输出:New
} catch {
    print("解析错误:\(error)")
}

1.4.14 NSNull

重点说明

  1. 用于表示集合中的null值,因为Swift集合只能存储对象类型
  2. Swift的类型安全特性避免了向NSNull发送消息导致的崩溃
  3. 通过Optional Binding可自动将NSNull转换为nil,安全处理JSON中的null

代码示例

// 假设从JSON中取出的NSNull值
let jsonValue: AnyObject = NSNull()

if let string = jsonValue as? String {
    print(string.hasPrefix("a"))
} else {
    print("不能解析") // 输出:不能解析
}

1.4.15 文档注释

重点说明

  1. 使用/**...*/块注释或///行注释编写文档,Xcode可自动提取为Quick Help
  2. - parameter说明参数,- returns说明返回值
  3. Xcode 8+支持快捷键Alt+Cmd+/自动生成文档注释模板
  4. jazzy是Swift官方推荐的文档生成工具,可生成HTML格式文档

代码示例

/// 一个演示方法
/// - parameter input: 一个整数输入
/// - returns: 表示输入数字的字符串
func method(input: Int) -> String {
    return String(input)
}

/// 人的名字
struct Person {
    /// 姓名
    var name: String
}

1.4.16 性能考虑

重点说明

  1. Swift方法调用默认使用虚函数表派发,比Objective-C的动态派发快得多
  2. final标记的方法/类可被编译器优化为inline调用,进一步提升性能
  3. 性能敏感代码优先使用纯Swift类型,避免NSObject子类和Objective-C运行时
  4. Swift基本算法性能接近C语言,远优于Objective-C

代码示例

无单独代码,核心是性能优化原则。

1.4.17 Log输出

重点说明

  1. 利用#file#line#function等编译符号自动添加日志上下文(文件名、行号、方法名)
  2. 封装统一的日志方法,通过条件编译在Release版本中禁用日志
  3. Release版本中空方法会被编译器完全优化,实现零成本日志

代码示例

func printLog<T>(_ message: T,
                 file: String = #file,
                 method: String = #function,
                 line: Int = #line) {
    #if DEBUG
    let fileName = (file as NSString).lastPathComponent
    print("\(fileName)[\(line)], \(method): \(message)")
    #endif
}

// 调用
func testLog() {
    printLog("这是一条测试日志")
}
// 输出:Test.swift[12], testLog(): 这是一条测试日志

1.4.18 溢出

重点说明

  1. Swift默认数值溢出会导致程序崩溃,而非截断高位,提升了类型安全性
  2. Int在32位平台是Int32,64位平台是Int64,跨平台开发需注意数值范围
  3. 使用&+&-&*&/&%溢出运算符,可实现高位截断的溢出行为

代码示例

// ❌ 默认溢出崩溃
var max = Int.max
max = max + 1 // 崩溃

// ✅ 溢出运算符:高位截断
var max2 = Int.max
max2 = max2 &+ 1
// 64位系统输出:-9223372036854775808
print(max2)

1.4.19 宏定义define

重点说明

  1. Swift彻底移除了宏定义,避免了宏的安全性和可维护性问题
  2. 常量宏用let或计算属性替代,函数宏用全局方法替代
  3. 条件编译用#if配合编译标记实现,替代#ifdef
  4. 无法实现改变代码结构的宏(如类定义宏)

代码示例

// 替代常量宏
let M_PI = Double.pi

// 替代函数宏:NSLocalizedString
func NSLocalizedString(_ key: String, comment: String) -> String {
    return Bundle.main.localizedString(forKey: key, value: "", table: nil)
}

// 条件编译
#if os(iOS)
print("iOS平台")
#elseif os(macOS)
print("macOS平台")
#endif

1.4.20 属性访问控制

重点说明

  1. Swift提供五级访问控制(权限从低到高):private < fileprivate < internal(默认) < public < open
  2. private仅当前作用域/同文件同类型可访问,fileprivate仅当前文件可访问
  3. public允许外部module访问但不能继承/重写,open允许继承和重写
  4. private(set)可分离读写权限,实现只读公开、可写私有

代码示例

// 分离读写权限
class MyClass {
    public private(set) var name: String?
}

// 外部module只能读取name,不能修改
let obj = MyClass()
print(obj.name) // 正常
obj.name = "test" // 编译错误

1.4.21 Swift中的测试

重点说明

  1. 使用XCTest框架进行测试,测试target与app target相互独立
  2. Swift 2.0引入@testable import,可在测试中访问app的internal成员
  3. 无需为了测试将方法标记为public,遵循最小权限原则
  4. 库开发时public方法即为测试接口,app开发用@testable访问内部方法

代码示例

// app中的业务代码(internal权限)
func methodToTest() -> Int {
    return 123
}

// 测试代码
@testable import MyApp
import XCTest

class MyAppTests: XCTestCase {
    func testMethod() {
        XCTAssertEqual(methodToTest(), 123)
    }
}

1.4.22 Core Data

重点说明

  1. Swift中用@NSManaged替代Objective-C的@dynamic,标记Core Data动态属性
  2. @NSManaged告诉编译器属性的getter/setter由Core Data在运行时生成
  3. 创建Entity时需指定module名,否则类型转换会失败
  4. @NSManaged仅限NSManagedObject子类使用,不推荐在其他类中使用

代码示例

import CoreData

class MyModel: NSManagedObject {
    @NSManaged var title: String
    @NSManaged var createTime: Date
}

1.4.23 闭包歧义

重点说明

  1. 闭包省略参数类型时,编译器可能推断错误导致调用歧义
  2. Swift 1.2+会对歧义调用抛出编译错误,避免运行时异常
  3. 显式声明闭包参数类型可彻底解决歧义,同时提升代码可读性
  4. Void本质是空元组(),无参数闭包实际是接受空元组的闭包

代码示例

// 存在歧义的扩展
extension Int {
    func times(f: (Int) -> ()) {
        for i in 1...self { f(i) }
    }
    
    func times(f: () -> ()) {
        for _ in 1...self { f() }
    }
}

// ❌ 歧义调用:编译器无法推断
3.times { print($0) }

// ✅ 显式指定参数类型解决歧义
3.times { (i: Int) in print(i) }
3.times { () in print("Hello") }

1.4.24 泛型扩展

重点说明

  1. 泛型类型的扩展可直接使用原类型定义的泛型符号,无需重复声明
  2. 扩展中不能添加整个类型可用的新泛型符号,但单个方法可定义自己的泛型
  3. 可通过扩展为泛型类型添加通用方法,无需修改原类型定义

代码示例

// 为Array添加随机取元素的属性
extension Array {
    var random: Element? {
        return self.count != 0 ? self[Int(arc4random_uniform(UInt32(self.count)))] : nil
    }
    
    // 方法级别的泛型
    func appendRandomDescription<U: CustomStringConvertible>(_ input: U) -> String {
        if let element = self.random {
            return "\(element) " + input.description
        } else {
            return "empty array"
        }
    }
}

// 调用
let languages = ["Swift", "ObjC", "C++"]
print(languages.random!) // 随机输出一个语言
print(languages.appendRandomDescription(123)) // 输出类似:Swift 123

1.4.25 兼容性

重点说明

  1. Swift app会打包自带Swift运行时库,保证不同系统版本行为一致
  2. 支持向下兼容到iOS 7和OS X 10.9,但会增加app尺寸约4-5MB
  3. 第三方Swift代码推荐以源码形式依赖,避免二进制版本不兼容
  4. 主app未用Swift但扩展用了Swift时,需开启Embedded Content Contains Swift Code

代码示例

无单独代码,核心是项目配置和依赖原则。

1.4.26 列举enum类型

重点说明

  1. Swift原生不支持直接枚举enum的所有case,因为enum可包含关联值
  2. 可通过协议+static属性手动实现allValues,枚举无关联值的enum
  3. 适用于固定case集合的enum,如扑克牌花色、等级等

代码示例

// 定义可枚举协议
protocol EnumeratableEnum {
    static var allValues: [Self] { get }
}

// 实现协议
enum Suit: String, EnumeratableEnum {
    case spades = "黑桃"
    case hearts = "红桃"
    case clubs = "草花"
    case diamonds = "方片"
    
    static var allValues: [Suit] {
        return [.spades, .hearts, .clubs, .diamonds]
    }
}

// 遍历所有case
for suit in Suit.allValues {
    print(suit.rawValue)
}

1.4.27 尾递归

重点说明

  1. 普通递归会不断创建栈帧,深度过大时导致栈溢出
  2. 尾递归让函数最后一个动作是函数调用,编译器可优化为复用栈帧
  3. SwiftRelease模式下支持尾递归优化,Debug模式不支持
  4. 嵌套函数可用于实现尾递归的内部辅助方法

代码示例

// ❌ 普通递归:深度过大栈溢出
func sum(_ n: UInt) -> UInt {
    if n == 0 { return 0 }
    return n + sum(n - 1)
}
sum(1000000) // 崩溃

// ✅ 尾递归:Release模式下无栈溢出
func tailSum(_ n: UInt) -> UInt {
    func sumInternal(_ n: UInt, current: UInt) -> UInt {
        if n == 0 {
            return current
        } else {
            return sumInternal(n - 1, current: current + n)
        }
    }
    return sumInternal(n, current: 0)
}
tailSum(1000000) // Release模式正常运行