Jacky's blog
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)
  • iOS 开发完整指南
  • Swift 语法与最佳实践
    • 语法基础
      • 参数标签
      • in-out 关键字
      • & 符号的常见用途
    • 一、闭包捕获与循环引用
      • 1.1 闭包默认强引用
      • 1.2 捕获列表:[weak self] vs [unowned self]
      • [weak self] 示例
      • [unowned self] 示例
      • 1.3 什么时候必须用 [weak self]?
      • 1.4 循环引用检测工具
    • 二、GCD 与异步编程
      • 2.1 常用队列
      • 2.2 异步派发
      • 2.3 async 为什么能解决视图更新警告?
    • 三、SwiftUI 属性包装器
      • 3.1 常用属性包装器对比
      • 3.2 @StateObject vs @ObservedObject
      • 3.3 @AppStorage 在 ObservableObject 中的坑
    • 四、SwiftUI 视图更新机制
      • 4.1 视图更新触发条件
      • 4.2 视图更新过程
      • 4.3 为什么不能在视图更新过程中发布变化?
      • 4.4 onAppear vs onChange vs task
    • 五、内存管理最佳实践
      • 5.1 单例的使用
      • 5.2 避免强引用循环
      • 5.3 deinit 调试技巧
    • 六、错误处理最佳实践
      • 6.1 try? vs try! vs do-catch
      • 6.2 自定义错误类型
    • 七、线程安全最佳实践
      • 7.1 共享资源保护
      • 7.2 主线程更新 UI
    • 八、代码组织最佳实践
      • 8.1 目录结构
      • 8.2 日志工具
      • 8.3 扩展文件命名
    • 九、性能优化最佳实践
      • 9.1 避免不必要的视图更新
      • 9.2 懒加载
      • 9.3 避免在 body 中做耗时操作
    • 十、调试技巧
      • 10.1 打印调用栈
      • 10.2 测量执行时间
      • 10.3 条件编译
    • 参考资料
  • oc语法
  • iOS 运行时
  • xcode的使用
  • xcrun 与 Xcode 命令行工具
  • pod
  • xcodebuild 命令行构建
  • Xcode 工程文件说明
  • ios
Jacky
2026-09-21
目录

Swift 语法与最佳实践

# Swift 语法与最佳实践

本文档整合 Swift 语法基础与开发最佳实践,供日常查阅与团队参考。 最后更新:2026-09-21


# 语法基础

# 参数标签

// 在这个函数中, than 是参数标签, 它用于在函数调用时提供更多的上下文和可读性。number 是参数名称, 它是函数内部用来引用传递给函数的整数值的名称。Int 表示参数的类型, 即函数接受一个整数作为参数
func oneMore(than number: Int) -> Int {
    return number + 1
}


var myNumber = 1

// than 是参数标签, myNumber 是传递给函数的参数名称。这有助于使代码更易读懂, 尤其是在函数有多个参数时
myNumber = oneMore(than: myNumber)
print(myNumber)
1
2
3
4
5
6
7
8
9
10
11

如果您愿意, 也可以省略参数标签, 函数调用会变得更加简洁, 但通常会失去一些可读性

# in-out 关键字

in-out 参数是 Swift 中的一种参数类型, 它允许函数修改调用者传递给该函数的参数的值。通常, 函数内的参数是常量, 这意味着您不能在函数内修改它们的值。但是, 如果将参数声明为 in-out, 函数可以修改该参数的值, 并且这些更改将在函数调用后保持有效

要声明 in-out 参数, 您需要在参数类型前面加上 inout 关键字。这是一个示例:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

var x = 1
var y = 2

swapTwoInts(&x, &y)

print("x is now \(x), and y is now \(y)")
1
2
3
4
5
6
7
8
9
10
11
12

在这个示例中, swapTwoInts 函数接受两个 in-out 整数参数 a 和 b。通过使用 & 符号, 我们将 x 和 y 变量传递给函数, 使函数能够修改它们的值。函数内部实现了一个经典的交换两个整数值的算法

需要注意的是, in-out 参数必须具有可变的存储属性, 例如变量, 而不是常量。此外, in-out 参数不能有默认值, 它们通常不能是可选类型, 并且它们不能用于不逃逸的闭包

使用 in-out 参数时要小心, 因为它们会更改原始值, 可能会导致复杂的代码行为。通常情况下, 最好避免使用 in-out 参数, 而是使用返回值来传递修改后的值

# & 符号的常见用途

在 Swift 中, & 符号用于多种不同的上下文, 它的含义取决于它的使用方式。以下是 & 符号的一些常见用途:

  1. 引用参数(In-Out Parameters): 在函数或方法中, 你可以将参数标记为 in-out, 以允许函数修改传递给它的参数。这时, 你需要在参数前面加上 & 符号, 表示你要传递参数的引用。例如:
func increment(_ number: inout Int) {
    number += 1
}

var myNumber = 5
increment(&myNumber) // 这里需要使用 & 符号传递参数的引用
1
2
3
4
5
6
  1. 取地址(Address Of): 在某些情况下, 你可能需要获取一个变量或常量的内存地址。你可以使用 & 符号来获得它。例如:
var someValue = 42
let pointerToValue = &someValue // 获取 someValue 的内存地址
1
2
  1. 位操作(Bitwise Operations): 在位操作中, & 表示按位与运算, 用于比较两个二进制值的每一位, 只有当两者都为 1 时结果才为 1。例如:
let a: UInt8 = 0b00101010
let b: UInt8 = 0b10110000
let result = a & b // result 的值为 0b00100000
1
2
3
  1. 逻辑与(Logical AND): & 也可以用作逻辑与运算符, 用于组合布尔表达式中的条件。例如:
if condition1 && condition2 {
// 如果 condition1 和 condition2 都为 true, 则执行此块
}
1
2
3

这些是 & 符号在 Swift 中的一些常见用途, 其含义会根据上下文的不同而变化


# 一、闭包捕获与循环引用

# 1.1 闭包默认强引用

Swift 闭包默认会强引用捕获的对象。如果闭包被对象持有,同时闭包又强引用该对象,就会形成循环引用(Reference Cycle),导致内存泄漏。

// ❌ 循环引用示例
class MyClass {
    var closure: (() -> Void)?
    
    func setup() {
        closure = {
            self.doSomething()  // 闭包强引用 self
        }
    }
    // MyClass 强引用 closure,closure 强引用 MyClass → 循环引用
}
1
2
3
4
5
6
7
8
9
10
11

# 1.2 捕获列表:[weak self] vs [unowned self]

特性 [weak self] [unowned self]
引用类型 弱引用 无主引用
是否可选 是(self?) 否(self,直接访问)
self 被释放后 安全(nil,不执行) 💥 崩溃(悬垂指针)
适用场景 self 可能被释放 self 一定不会被释放,且闭包生命周期不超过 self

# [weak self] 示例

// ✅ 使用 [weak self] 避免循环引用
DispatchQueue.main.async { [weak self] in
    self?.objectWillChange.send()  // self? 为 nil 时安全跳过
}
1
2
3
4

# [unowned self] 示例

// ✅ 确定 self 不会被释放时使用 [unowned self]
UIView.animate(withDuration: 0.3) { [unowned self] in
    self.view.layoutIfNeeded()  // self 一定存在,直接访问
}
1
2
3
4

# 1.3 什么时候必须用 [weak self]?

场景 是否需要 [weak self] 原因
DispatchQueue.main.async ✅ 推荐 闭包被 GCD 持有,可能形成循环引用
Timer.scheduledTimer ✅ 必须 Timer 强引用 target,容易形成循环引用
UIView.animate ⚠️ 可选 闭包执行完毕后释放,通常不会泄漏,但用 [weak self] 更安全
即时执行的闭包(如 map、filter) ❌ 不需要 闭包即时执行,不会被持有
@escaping 闭包 ✅ 推荐 逃逸闭包可能被持有,容易形成循环引用
单例对象的闭包 ⚠️ 可选 单例不会被释放,但用 [weak self] 是最佳实践

# 1.4 循环引用检测工具

  • Xcode Memory Graph Debugger:运行时查看对象引用关系
  • Instruments - Leaks:检测内存泄漏
  • deinit 打印日志:在 deinit 中打印日志,确认对象是否被释放
deinit {
    print("\(type(of: self)) deinit")  // 如果没打印,说明可能有循环引用
}
1
2
3

# 二、GCD 与异步编程

# 2.1 常用队列

队列 说明 使用场景
DispatchQueue.main 主线程串行队列 UI 更新、用户交互
DispatchQueue.global() 全局并发队列 耗时操作(网络、计算、IO)
DispatchQueue(label:) 自定义串行队列 保护共享资源、顺序执行任务
DispatchQueue(label:, attributes: .concurrent) 自定义并发队列 并发执行任务

# 2.2 异步派发

// 主线程异步执行(下一个 runloop)
DispatchQueue.main.async {
    // UI 更新
}

// 后台线程异步执行
DispatchQueue.global().async {
    // 耗时操作
    DispatchQueue.main.async {
        // 回到主线程更新 UI
    }
}

// 延迟执行
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
    // 1 秒后执行
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.3 async 为什么能解决视图更新警告?

问题:在视图更新过程中调用 objectWillChange.send() 会触发警告:

Publishing changes from within view updates is not allowed
1

原因:SwiftUI 的视图更新在当前 runloop 中同步执行,如果此时发布变化,会导致未定义行为。

解决方案:用 DispatchQueue.main.async 将发布变化推迟到下一个 runloop:

DispatchQueue.main.async { [weak self] in
    self?.objectWillChange.send()  // 下一个 runloop 执行,当前视图更新已完成
}
1
2
3

关键点:不是"判定"是否在视图更新过程中,而是"确保"当前事务完成后再执行。


# 三、SwiftUI 属性包装器

# 3.1 常用属性包装器对比

属性包装器 用途 存储位置 触发视图更新
@State 视图内部私有状态 视图内存 ✅
@Binding 引用父视图的 @State 不存储,引用 ✅
@ObservedObject 观察外部对象 外部对象 ✅(对象的 objectWillChange)
@StateObject 视图拥有的可观察对象 视图内存 ✅
@EnvironmentObject 全局共享对象 环境 ✅
@Environment 系统环境值(如 colorScheme) 系统环境 ✅
@AppStorage UserDefaults 持久化 UserDefaults ✅(在视图中)
@Published 可观察对象的属性 对象内存 ✅(触发 objectWillChange)

# 3.2 @StateObject vs @ObservedObject

特性 @StateObject @ObservedObject
所有权 视图拥有 外部拥有,视图观察
生命周期 与视图相同 不拥有,可能被释放
适用场景 视图创建并拥有的对象 外部传入的对象
注意事项 只会初始化一次 每次视图重建都可能重新初始化
// ✅ 视图拥有的对象用 @StateObject
struct MyView: View {
    @StateObject var viewModel = ViewModel()  // 视图拥有,生命周期与视图相同
}

// ✅ 外部传入的对象用 @ObservedObject
struct MyView: View {
    @ObservedObject var viewModel: ViewModel  // 外部拥有,视图只观察
}
1
2
3
4
5
6
7
8
9

# 3.3 @AppStorage 在 ObservableObject 中的坑

问题:@AppStorage 实现了 DynamicProperty 协议,它的 update() 方法只有在 SwiftUI 视图中才会被自动调用。在 ObservableObject 中,update() 不会被自动调用,所以 @AppStorage 的缓存值可能不会更新。

现象:在设置页面修改了 @AppStorage 属性,但在 ObservableObject 中读取到的还是旧值。

解决方案:在 ObservableObject 的 init 中监听 UserDefaults.didChangeNotification,主动调用所有 @AppStorage 属性的 update() 方法:

final class AppSettings: ObservableObject {
    static let shared = AppSettings()
    
    @AppStorage("showOverlayOnBackground") var showOverlayOnBackground: Bool = true
    // ... 其他 @AppStorage 属性
    
    private init() {
        NotificationCenter.default.addObserver(
            forName: UserDefaults.didChangeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.refreshFromUserDefaults()
        }
    }
    
    private func refreshFromUserDefaults() {
        _showOverlayOnBackground.update()
        // ... 其他属性的 update()
        
        // 异步派发,避免在视图更新过程中调用
        DispatchQueue.main.async { [weak self] in
            self?.objectWillChange.send()
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 四、SwiftUI 视图更新机制

# 4.1 视图更新触发条件

SwiftUI 视图在以下情况会重新计算 body:

  1. @State 属性变化
  2. @ObservedObject / @StateObject / @EnvironmentObject 的 objectWillChange 触发
  3. @Environment 值变化(如 colorScheme)
  4. 父视图重新渲染(子视图可能重新渲染)
  5. @AppStorage 值变化(在视图中)

# 4.2 视图更新过程

当前 runloop
├── 状态变化(@State / objectWillChange)
├── SwiftUI 标记视图为"需要更新"
├── runloop 结束时,批量执行视图更新
│   ├── 计算 body
│   ├── diff(比较新旧视图)
│   └── 渲染
└── runloop 结束
1
2
3
4
5
6
7
8

# 4.3 为什么不能在视图更新过程中发布变化?

如果在视图更新过程中(比如 body 计算、onAppear)调用 objectWillChange.send():

  1. SwiftUI 正在更新视图,此时又收到"对象将要变化"的通知
  2. 可能导致无限循环、重复更新、或未定义行为
  3. SwiftUI 会输出警告:Publishing changes from within view updates is not allowed

解决方案:用 DispatchQueue.main.async 推迟到下一个 runloop。

# 4.4 onAppear vs onChange vs task

修饰符 触发时机 适用场景
.onAppear 视图出现时 初始化、加载数据
.onDisappear 视图消失时 清理、取消任务
.onChange(of:) 某个值变化时 响应状态变化
.task 视图出现时,异步任务 异步加载数据(自动取消)

# 五、内存管理最佳实践

# 5.1 单例的使用

// ✅ 推荐:线程安全的单例
final class MyManager {
    static let shared = MyManager()  // let 是线程安全的
    private init() {}  // 禁止外部创建
}

// ❌ 不推荐:非线程安全的单例
final class MyManager {
    static var shared: MyManager!
    private init() {}
}
1
2
3
4
5
6
7
8
9
10
11

# 5.2 避免强引用循环

场景 解决方案
闭包捕获 self [weak self] 或 [unowned self]
delegate 模式 delegate 用 weak
Timer 用 [weak self],或在 deinit 中 invalidate()
通知中心 在 deinit 中移除观察者,或使用 [weak self]
异步任务 用 [weak self],任务完成后检查 self 是否存在

# 5.3 deinit 调试技巧

deinit {
    print("✅ \(type(of: self)) deinit")  // 确认对象被释放
}
1
2
3

如果 deinit 没有被调用,说明可能有循环引用。


# 六、错误处理最佳实践

# 6.1 try? vs try! vs do-catch

方式 说明 适用场景
try? 错误返回 nil 错误不重要,可以忽略
try! 错误直接崩溃 确定不会出错(如资源文件加载)
do-catch 捕获并处理错误 需要处理错误的场景
// ✅ 推荐:do-catch 处理错误
do {
    let data = try Data(contentsOf: url)
} catch {
    print("加载失败: \(error)")
}

// ✅ 可以忽略错误时用 try?
let data = try? Data(contentsOf: url)

// ❌ 避免:除非确定不会出错
let data = try! Data(contentsOf: url)  // 出错会崩溃
1
2
3
4
5
6
7
8
9
10
11
12

# 6.2 自定义错误类型

enum AppError: Error, LocalizedError {
    case invalidPassword
    case decryptionFailed
    case fileNotFound(String)
    
    var errorDescription: String? {
        switch self {
        case .invalidPassword:
            return "密码错误"
        case .decryptionFailed:
            return "解密失败"
        case .fileNotFound(let path):
            return "文件不存在: \(path)"
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 七、线程安全最佳实践

# 7.1 共享资源保护

// ✅ 推荐:用串行队列保护共享资源
final class ThreadSafeStore {
    private let queue = DispatchQueue(label: "com.example.store")
    private var _items: [Item] = []
    
    var items: [Item] {
        queue.sync { _items }  // 读用 sync
    }
    
    func add(_ item: Item) {
        queue.async { self._items.append(item) }  // 写用 async
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 7.2 主线程更新 UI

// ✅ 确保在主线程更新 UI
DispatchQueue.main.async {
    self.label.text = "新内容"
}

// ✅ 检查是否在主线程
func updateUI() {
    if Thread.isMainThread {
        label.text = "新内容"
    } else {
        DispatchQueue.main.async { self.label.text = "新内容" }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 八、代码组织最佳实践

# 8.1 目录结构

CipherNest/
├── App/                    # App 入口、配置
├── Views/                  # SwiftUI 视图
│   ├── Components/         # 可复用组件
│   └── Modifiers/          # 自定义 ViewModifier
├── ViewModels/             # 视图模型
├── Models/                 # 数据模型
├── Services/               # 业务逻辑服务
├── Extensions/             # 扩展
├── Utilities/              # 工具类
├── Debug/                  # 调试相关(#if DEBUG)
└── Resources/              # 资源文件
1
2
3
4
5
6
7
8
9
10
11
12

# 8.2 日志工具

// ✅ 推荐:每个文件有自己的 log 实例,tag 为文件名
struct MyView: View {
    private let log = LogHelper(tag: "MyView")
    
    var body: some View {
        Text("Hello")
            .onAppear {
                log.info("onAppear")
            }
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 8.3 扩展文件命名

Extensions/
├── String+Extension.swift
├── Date+Extension.swift
├── Color+Extension.swift
└── View+Extension.swift
1
2
3
4
5

# 九、性能优化最佳实践

# 9.1 避免不必要的视图更新

// ✅ 使用 Equatable 避免不必要的更新
struct Item: Identifiable, Equatable {
    let id: UUID
    var title: String
}

// ✅ 使用 .id() 强制视图重建
MyView()
    .id(item.id)  // id 变化时强制重建
1
2
3
4
5
6
7
8
9

# 9.2 懒加载

// ✅ 大列表用 LazyVStack / LazyHStack
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            ItemRow(item: item)
        }
    }
}
1
2
3
4
5
6
7
8

# 9.3 避免在 body 中做耗时操作

// ❌ 不推荐:在 body 中做耗时操作
var body: some View {
    let data = loadFromDisk()  // 每次 body 计算都执行
    Text(data)
}

// ✅ 推荐:在 onAppear 或 task 中加载
@State private var data: String = ""

var body: some View {
    Text(data)
        .task {
            data = await loadFromDisk()
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 十、调试技巧

# 10.1 打印调用栈

// 打印当前调用栈,用于排查问题
func debugCallStack() {
    print("📞 Call stack:")
    for symbol in Thread.callStackSymbols.prefix(10) {
        print("  \(symbol)")
    }
}
1
2
3
4
5
6
7

# 10.2 测量执行时间

func measure<T>(_ label: String, _ block: () -> T) -> T {
    let start = CFAbsoluteTimeGetCurrent()
    let result = block()
    let elapsed = (CFAbsoluteTimeGetCurrent() - start) * 1000
    print("⏱️ \(label): \(String(format: "%.1f", elapsed))ms")
    return result
}

// 使用
measure("加载数据") {
    loadData()
}
1
2
3
4
5
6
7
8
9
10
11
12

# 10.3 条件编译

#if DEBUG
// 调试代码,Release 不会打包
debugPrint("调试信息")
#endif

#if targetEnvironment(simulator)
// 模拟器专用代码
#endif
1
2
3
4
5
6
7
8

# 参考资料

  • Swift 官方文档 (opens new window)
  • SwiftUI 官方文档 (opens new window)
  • Swift 内存管理 (opens new window)
  • Grand Central Dispatch (opens new window)
#iOS#Swift#best-practice
上次更新: 2026/10/18, 22:14:48
iOS 开发完整指南
oc语法

← iOS 开发完整指南 oc语法→

最近更新
01
Codex 使用与会话管理指南
10-17
02
Tailscale 入门:让自己的设备跨网络互联
10-15
03
GitHub CLI(gh)工作流与 CI 排查
10-08
更多文章>
Theme by Vdoing | Copyright © 2019-2026 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式