新手 Swift 开发者常见错误:从 A 到 Z

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

新手 Swift 开发者常见错误:从 A 到 Z

\n\n

Swift 是 Apple 为 iOS、macOS、watchOS 和 tvOS 开发而创建的一种现代、强大且直观的语言。然而,其独特的功能(可选类型、强类型、ARC 和协议)常常成为初学者的绊脚石。在本文中,我们将分析新手 Swift 开发者最常见的错误——从 "A"(Optionals)到 "Z"(ARC)。您将学习如何避免这些错误,并编写干净、安全的代码。

\n\n

1. Optionals(可选类型)使用不当

\n\n

最常见的错误是忽略 nil 和可选类型的概念。新手常常在未确认值存在的情况下就尝试使用它。

\n\n

错误:强制解包(Force Unwrapping)

\n
let optionalString: String? = nil\nlet unwrapped = optionalString! // 崩溃!致命错误
\n\n

正确解决方案:安全解包

\n
if let safeString = optionalString {\n    print(safeString)\n} else {\n    print("值不存在")\n}\n\n// 或者使用 guard\nguard let safeString = optionalString else {\n    print("结束执行")\n    return\n}\nprint(safeString)
\n\n

错误:使用隐式解包可选类型但未保证值存在

\n
var name: String! = nil\nprint(name.count) // 崩溃
\n\n

建议: 使用 ? 声明可选类型,并在使用前始终检查值。除非 100% 确定在首次访问前值会被设置,否则避免在声明中使用 !。

\n\n

2. 内存管理(ARC)错误与强引用循环

\n\n

Swift 使用自动引用计数(ARC)来管理内存。新手的主要错误是创建强引用循环(retain cycles),这会导致内存泄漏。

\n\n

问题:捕获 self 的闭包

\n
class ViewController: UIViewController {\n    var closure: (() -> Void)?\n\n    func setupClosure() {\n        closure = {\n            self.view.backgroundColor = .red // 对 self 的强引用\n        }\n    }\n}
\n

由于 closure 持有对 self 的强引用,而 self(通过属性 closure)持有对闭包的强引用,因此形成了循环。

\n\n

解决方案:捕获列表(Capture Lists)

\n
closure = { [weak self] in\n    guard let self = self else { return }\n    self.view.backgroundColor = .red\n}\n\n// 或者如果确定 self 不会变为 nil,则使用 unowned\nclosure = { [unowned self] in\n    self.view.backgroundColor = .red\n}
\n\n

问题:对象之间的循环

\n
class Parent {\n    var child: Child?\n}\n\nclass Child {\n    var parent: Parent? // 对 Parent 的强引用\n}
\n\n

解决方案:其中一方使用弱引用

\n
class Child {\n    weak var parent: Parent? // 现在是弱引用\n}
\n\n

3. 忽略值语义(Value Semantics)与引用语义(Reference Semantics)

\n\n

新手常常混淆类(引用类型)和结构体(值类型)。这会导致意外行为,尤其是在处理集合时。

\n\n

错误:在集合内部修改类的实例

\n
class User {\n    var name: String\n    init(name: String) { self.name = name }\n}\n\nvar users = [User(name: "Alice")]\nlet firstUser = users[0]\nfirstUser.name = "Bob" // 修改集合内部\nprint(users[0].name) // "Bob" - 集合已更改
\n

这既可能是优点,也可能是错误的根源,如果开发者期望的是复制行为的话。

\n\n

正确方法:对数据使用结构体

\n
struct User {\n    var name: String\n}\n\nvar users = [User(name: "Alice")]\nvar firstUser = users[0] // 这是副本\nfirstUser.nam                

博客

书籍推荐