引言

Swift 是苹果公司于2014年推出的现代编程语言,专为 iOS、macOS、watchOS 和 tvOS 开发而设计。它结合了 C 和 Objective-C 的优点,同时引入了更安全、更简洁的语法。对于初学者来说,Swift 的学习曲线相对平缓,但要真正精通并避免常见陷阱,需要大量的实战经验。本文将从零开始,逐步深入,分享 Swift 编程的核心技巧和常见陷阱,帮助你从新手成长为专家。

第一部分:Swift 基础入门

1.1 环境搭建与第一个程序

在开始编程之前,你需要安装 Xcode,这是苹果官方的集成开发环境(IDE)。Xcode 包含了 Swift 编译器、调试器和界面构建工具。

步骤:

  1. 打开 Mac App Store,搜索并安装 Xcode。
  2. 打开 Xcode,选择“Create a new Xcode project”。
  3. 选择“App”模板,点击“Next”。
  4. 填写项目名称(例如:HelloSwift),选择语言为 Swift,点击“Next”并选择保存位置。
  5. 在项目中,打开 ViewController.swift 文件,在 viewDidLoad 方法中添加以下代码:
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 打印 Hello, World!
        print("Hello, World!")
        
        // 在控制台输出
        print("欢迎来到 Swift 编程世界!")
    }
}

运行程序:

  • 点击 Xcode 左上角的“运行”按钮(▶️),或按 Cmd + R
  • 在模拟器或真机上运行,查看控制台输出。

1.2 变量与常量

Swift 使用 let 声明常量(不可变),var 声明变量(可变)。

// 常量
let name = "Alice"
// name = "Bob" // 错误:常量不能修改

// 变量
var age = 25
age = 26 // 正确:变量可以修改

// 类型推断
let city: String = "Beijing" // 显式指定类型
let temperature = 25.5 // 类型推断为 Double

常见陷阱:

  • 尽量使用 let 声明常量,除非需要修改。这有助于提高代码的安全性和可读性。
  • 避免使用隐式解包可选值(!),除非你确定值一定存在。

1.3 控制流

Swift 提供了 ifguardswitch 等控制流语句。

// if 语句
let score = 85
if score >= 90 {
    print("优秀")
} else if score >= 60 {
    print("及格")
} else {
    print("不及格")
}

// guard 语句(提前退出)
func processUser(name: String?) {
    guard let unwrappedName = name else {
        print("名字不能为空")
        return
    }
    print("欢迎,\(unwrappedName)")
}

// switch 语句(支持模式匹配)
let number = 5
switch number {
case 0:
    print("零")
case 1...10:
    print("1到10之间")
case 11, 13, 15:
    print("奇数")
default:
    print("其他")
}

技巧:

  • 使用 guard 语句可以减少嵌套,使代码更清晰。
  • switch 语句必须覆盖所有情况,否则需要添加 default

1.4 函数与闭包

函数是 Swift 的基本构建块,闭包是匿名函数。

// 函数定义
func greet(name: String) -> String {
    return "Hello, \(name)!"
}

let greeting = greet(name: "Bob")
print(greeting) // 输出: Hello, Bob!

// 闭包
let numbers = [1, 2, 3, 4]
let doubled = numbers.map { $0 * 2 }
print(doubled) // 输出: [2, 4, 6, 8]

// 闭包作为参数
func calculate(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let sum = calculate(a: 5, b: 3) { $0 + $1 }
print(sum) // 输出: 8

常见陷阱:

  • 闭包中使用 self 时,注意循环引用。使用 [weak self][unowned self] 避免内存泄漏。
  • 函数参数默认是常量,不能修改。如果需要修改,使用 inout 关键字。

第二部分:面向对象编程

2.1 类与结构体

Swift 支持类(引用类型)和结构体(值类型)。

// 结构体(值类型)
struct Person {
    var name: String
    var age: Int
    
    func describe() -> String {
        return "\(name) is \(age) years old."
    }
}

var person1 = Person(name: "Alice", age: 30)
var person2 = person1 // 值拷贝
person2.name = "Bob"
print(person1.name) // 输出: Alice(person1 不变)
print(person2.name) // 输出: Bob

// 类(引用类型)
class Employee {
    var name: String
    var salary: Double
    
    init(name: String, salary: Double) {
        self.name = name
        self.salary = salary
    }
    
    func raise(by percentage: Double) {
        salary *= (1 + percentage/100)
    }
}

let emp1 = Employee(name: "Charlie", salary: 50000)
let emp2 = emp1 // 引用拷贝
emp2.name = "David"
print(emp1.name) // 输出: David(emp1 被修改)

技巧:

  • 优先使用结构体,除非需要引用语义或继承。
  • 类必须有初始化器(init),而结构体有默认的成员初始化器。

2.2 属性与方法

Swift 支持计算属性、属性观察器和类型方法。

class Circle {
    var radius: Double = 0.0 {
        willSet {
            print("半径将从 \(radius) 变为 \(newValue)")
        }
        didSet {
            print("半径已从 \(oldValue) 变为 \(radius)")
        }
    }
    
    // 计算属性
    var area: Double {
        get {
            return Double.pi * radius * radius
        }
        set {
            radius = sqrt(newValue / Double.pi)
        }
    }
    
    // 类型方法
    static func describe() -> String {
        return "这是一个圆"
    }
}

let circle = Circle()
circle.radius = 5.0 // 触发属性观察器
circle.area = 78.5 // 设置计算属性,自动更新 radius

常见陷阱:

  • 计算属性的 set 方法中,不要直接修改其他属性,以免引起无限循环。
  • 属性观察器在初始化时不会被调用。

2.3 继承与多态

Swift 支持单继承,但可以通过协议实现多态。

// 基类
class Animal {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func makeSound() {
        print("动物发出声音")
    }
}

// 子类
class Dog: Animal {
    override func makeSound() {
        print("汪汪!")
    }
}

// 多态
let animals: [Animal] = [Dog(name: "Buddy"), Animal(name: "Tiger")]
for animal in animals {
    animal.makeSound() // 输出: 汪汪! 和 动物发出声音
}

技巧:

  • 使用 final 关键字防止类或方法被继承或重写。
  • 避免深层继承,优先使用组合而非继承。

第三部分:协议与扩展

3.1 协议(Protocol)

协议定义了一组方法、属性和要求,类、结构体或枚举可以遵循协议。

// 定义协议
protocol Identifiable {
    var id: String { get }
    func identify()
}

// 遵循协议
struct User: Identifiable {
    var id: String
    var name: String
    
    func identify() {
        print("用户 ID: \(id), 姓名: \(name)")
    }
}

// 协议扩展
extension Identifiable {
    func identify() {
        print("ID: \(id)")
    }
}

let user = User(id: "123", name: "Eve")
user.identify() // 输出: ID: 123(因为扩展提供了默认实现)

常见陷阱:

  • 协议扩展中的默认实现可能被覆盖,但要注意优先级。
  • 协议不能有存储属性,只能有计算属性。

3.2 扩展(Extension)

扩展可以为现有类、结构体、枚举或协议添加新功能。

// 为 Int 添加扩展
extension Int {
    func squared() -> Int {
        return self * self
    }
    
    var isEven: Bool {
        return self % 2 == 0
    }
}

let num = 5
print(num.squared()) // 输出: 25
print(num.isEven) // 输出: false

// 为 String 添加扩展
extension String {
    func isPalindrome() -> Bool {
        let cleaned = self.lowercased().filter { $0.isLetter }
        return cleaned == String(cleaned.reversed())
    }
}

print("A man, a plan, a canal: Panama".isPalindrome()) // 输出: true

技巧:

  • 扩展可以添加方法、计算属性、下标,但不能添加存储属性或初始化器。
  • 使用扩展来组织代码,例如将相关功能分组。

第四部分:错误处理与调试

4.1 错误处理

Swift 使用 throwdo-catchtry 处理错误。

// 定义错误类型
enum FileError: Error {
    case notFound
    case permissionDenied
    case readFailed
}

// 可能抛出错误的函数
func readFile(path: String) throws -> String {
    if !FileManager.default.fileExists(atPath: path) {
        throw FileError.notFound
    }
    // 模拟读取文件
    return "文件内容"
}

// 使用 do-catch
do {
    let content = try readFile(path: "/path/to/file.txt")
    print(content)
} catch FileError.notFound {
    print("文件未找到")
} catch {
    print("其他错误: \(error)")
}

// 可选型 try? 和 try!
let content = try? readFile(path: "/path/to/file.txt") // 返回可选值
// let content = try! readFile(path: "/path/to/file.txt") // 强制解包,可能崩溃

常见陷阱:

  • 避免使用 try!,除非你确定不会抛出错误。
  • 使用 guard letif let 处理可选值,避免强制解包。

4.2 调试技巧

  • 使用断点:在 Xcode 中点击行号左侧设置断点,程序会在断点处暂停。
  • 打印调试:使用 printNSLog 输出变量值。
  • LLDB 命令:在调试控制台中使用 LLDB 命令,如 po variable 打印对象描述。
  • 视图调试:使用 Xcode 的视图调试器检查 UI 层次结构。

示例:

// 在代码中添加断点
func debugExample() {
    let array = [1, 2, 3]
    for item in array {
        print(item) // 在这里设置断点
    }
}

第五部分:常见陷阱与最佳实践

5.1 内存管理

Swift 使用自动引用计数(ARC)管理内存,但需注意循环引用。

// 循环引用示例
class Person {
    var apartment: Apartment?
}

class Apartment {
    var tenant: Person?
}

var alice: Person? = Person()
var apt: Apartment? = Apartment()
alice?.apartment = apt
apt?.tenant = alice // 循环引用

// 解决:使用弱引用或无主引用
class Apartment2 {
    weak var tenant: Person? // 弱引用
}

// 或使用无主引用(当对象生命周期确定时)
class Person2 {
    var apartment: Apartment2?
}

class Apartment2 {
    unowned var tenant: Person2?
}

技巧:

  • 在闭包中捕获 self 时,使用 [weak self][unowned self]
  • 使用 deinit 检查对象是否被正确释放。

5.2 线程安全

Swift 本身不提供线程安全机制,需使用 GCD(Grand Central Dispatch)或 OperationQueue。

import Foundation

// 使用 GCD 保证线程安全
class Counter {
    private var value = 0
    private let queue = DispatchQueue(label: "com.example.counter")
    
    func increment() {
        queue.sync {
            value += 1
        }
    }
    
    func getValue() -> Int {
        return queue.sync { value }
    }
}

// 使用示例
let counter = Counter()
DispatchQueue.global().async {
    for _ in 0..<1000 {
        counter.increment()
    }
}
DispatchQueue.global().async {
    for _ in 0..<1000 {
        counter.increment()
    }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("最终计数: \(counter.getValue())") // 应为 2000
}

常见陷阱:

  • 避免在主线程执行耗时操作,否则会导致 UI 卡顿。
  • 使用 @MainActorMainActor.run 确保 UI 更新在主线程。

5.3 性能优化

  • 避免不必要的对象创建:使用值类型(结构体)代替引用类型(类)以减少内存开销。
  • 使用懒加载:对于昂贵的资源,使用 lazy 关键字延迟初始化。
  • 优化循环:避免在循环中进行复杂计算,使用 for-in 而非 while 以提高可读性。
// 懒加载示例
class ImageProcessor {
    lazy var expensiveImage: UIImage = {
        // 模拟耗时操作
        print("加载图片...")
        return UIImage(named: "large_image")!
    }()
    
    func process() {
        // 只有在访问 expensiveImage 时才会加载
        print(expensiveImage.size)
    }
}

第六部分:实战项目示例

6.1 简单计算器

以下是一个简单的命令行计算器,演示了函数、错误处理和用户输入。

import Foundation

// 定义错误类型
enum CalculatorError: Error {
    case invalidInput
    case divisionByZero
}

// 计算器函数
func calculate(_ expression: String) throws -> Double {
    let parts = expression.split(separator: " ")
    guard parts.count == 3 else {
        throw CalculatorError.invalidInput
    }
    
    let num1 = Double(parts[0])
    let num2 = Double(parts[2])
    let operatorChar = parts[1]
    
    guard let a = num1, let b = num2 else {
        throw CalculatorError.invalidInput
    }
    
    switch operatorChar {
    case "+":
        return a + b
    case "-":
        return a - b
    case "*":
        return a * b
    case "/":
        if b == 0 {
            throw CalculatorError.divisionByZero
        }
        return a / b
    default:
        throw CalculatorError.invalidInput
    }
}

// 主程序
print("请输入表达式(例如:5 + 3):")
if let input = readLine() {
    do {
        let result = try calculate(input)
        print("结果: \(result)")
    } catch CalculatorError.invalidInput {
        print("错误:无效输入,请使用格式 '数字 运算符 数字'")
    } catch CalculatorError.divisionByZero {
        print("错误:除数不能为零")
    } catch {
        print("未知错误: \(error)")
    }
}

运行步骤:

  1. 在 Xcode 中创建一个命令行工具项目。
  2. 将上述代码复制到 main.swift 文件中。
  3. 运行项目,输入表达式并查看结果。

6.2 iOS 简单待办事项应用

以下是一个简单的 iOS 待办事项应用,使用 UIKit 和 Core Data。

步骤:

  1. 创建新项目,选择“App”模板,使用 Swift 和 UIKit。
  2. ViewController.swift 中添加以下代码:
import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    var tasks: [NSManagedObject] = []
    let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        fetchTasks()
    }
    
    func setupUI() {
        title = "待办事项"
        view.backgroundColor = .white
        
        // 添加按钮
        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTask))
        
        // 设置表格视图
        tableView.frame = view.bounds
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        view.addSubview(tableView)
    }
    
    @objc func addTask() {
        let alert = UIAlertController(title: "新任务", message: nil, preferredStyle: .alert)
        alert.addTextField { textField in
            textField.placeholder = "输入任务"
        }
        alert.addAction(UIAlertAction(title: "取消", style: .cancel))
        alert.addAction(UIAlertAction(title: "保存", style: .default) { [weak self] _ in
            guard let textField = alert.textFields?.first, let text = textField.text, !text.isEmpty else { return }
            self?.saveTask(text)
        })
        present(alert, animated: true)
    }
    
    func saveTask(_ text: String) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Task", in: managedContext)!
        let task = NSManagedObject(entity: entity, insertInto: managedContext)
        task.setValue(text, forKey: "title")
        
        do {
            try managedContext.save()
            tasks.append(task)
            tableView.reloadData()
        } catch {
            print("保存失败: \(error)")
        }
    }
    
    func fetchTasks() {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
        let managedContext = appDelegate.persistentContainer.viewContext
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Task")
        
        do {
            tasks = try managedContext.fetch(fetchRequest)
        } catch {
            print("获取失败: \(error)")
        }
    }
    
    // MARK: - UITableViewDataSource
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let task = tasks[indexPath.row]
        cell.textLabel?.text = task.value(forKey: "title") as? String
        return cell
    }
    
    // MARK: - UITableViewDelegate
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
            let managedContext = appDelegate.persistentContainer.viewContext
            managedContext.delete(tasks[indexPath.row])
            
            do {
                try managedContext.save()
                tasks.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: .automatic)
            } catch {
                print("删除失败: \(error)")
            }
        }
    }
}

配置 Core Data:

  1. 在项目导航器中,点击 .xcdatamodeld 文件。
  2. 添加一个实体 Task,属性为 title(String 类型)。
  3. AppDelegate.swift 中,确保 Core Data 栈已设置(Xcode 通常自动生成)。

运行:

  • 运行应用,点击“+”添加任务,滑动删除任务。

第七部分:进阶技巧

7.1 泛型

泛型允许编写灵活、可重用的代码。

// 泛型函数
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 5
var y = 10
swapValues(&x, &y)
print(x, y) // 输出: 10 5

// 泛型结构体
struct Stack<Element> {
    var items = [Element]()
    
    mutating func push(_ item: Element) {
        items.append(item)
    }
    
    mutating func pop() -> Element? {
        return items.popLast()
    }
}

var stack = Stack<Int>()
stack.push(1)
stack.push(2)
print(stack.pop()) // 输出: Optional(2)

7.2 并发编程(Swift 5.5+)

Swift 5.5 引入了 async/await,简化异步代码。

import Foundation

// 模拟网络请求
func fetchData() async throws -> String {
    // 模拟延迟
    try await Task.sleep(nanoseconds: 1_000_000_000)
    return "数据"
}

// 使用 async/await
func process() async {
    do {
        let data = try await fetchData()
        print(data)
    } catch {
        print("错误: \(error)")
    }
}

// 在主线程调用
Task {
    await process()
}

技巧:

  • 使用 Task 创建异步任务。
  • 避免在异步代码中阻塞主线程。

第八部分:总结与资源

8.1 总结

Swift 是一门强大而优雅的语言,从基础到高级,每一步都需要实践。记住以下关键点:

  • 始终使用 let 除非需要修改。
  • 避免强制解包和隐式解包可选值。
  • 使用协议和扩展来组织代码。
  • 注意内存管理和线程安全。
  • 不断练习,构建实际项目。

8.2 推荐资源

  • 官方文档Swift.org
  • 书籍:《Swift Programming: The Big Nerd Ranch Guide》、《Swift in Depth》
  • 在线课程:Stanford CS193p(免费)、Ray Wenderlich 教程
  • 社区:Stack Overflow、Swift Forums、Reddit 的 r/swift

通过持续学习和实践,你将能够掌握 Swift 的核心技巧,避免常见陷阱,成为一名优秀的 Swift 开发者。祝你编程愉快!