Swift 从入门到精通-终篇

0 阅读4分钟

第14章:SwiftUI 进阶

14.1 动画

SwiftUI 动画非常简单,只需添加 .animation


struct AnimationView: View {

    @State private var isExpanded = false

    

    var body: some View {

        VStack {

            RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)

                .fill(isExpanded ? Color.blue : Color.red)

                .frame(

                    width: isExpanded ? 300 : 100,

                    height: isExpanded ? 300 : 100

                )

                .animation(.spring(response: 0.3, dampingFraction: 0.5), value: isExpanded)

            

            Button("切换") {

                isExpanded.toggle()

            }

        }

    }

}

**动画类型: **

  • .default - 默认动画

  • .linear - 线性

  • .easeIn / .easeOut / .easeInOut - 缓动

  • .spring() - 弹性动画

  • .interactiveSpring() - 交互式弹性

14.2 手势


struct GestureView: View {

    @State private var offset: CGSize = .zero

    @State private var scale: CGFloat = 1.0

    @State private var rotation: Angle = .zero

    

    var body: some View {

        Image(systemName: "star.fill")

            .font(.system(size: 100))

            .foregroundColor(.yellow)

            .offset(offset)

            .scaleEffect(scale)

            .rotationEffect(rotation)

            .gesture(

                DragGesture()

                    .onChanged { gesture in

                        offset = gesture.translation

                    }

                    .onEnded { _ in

                        withAnimation {

                            offset = .zero

                        }

                    }

            )

            .gesture(

                MagnificationGesture()

                    .onChanged { scale = $0 }

            )

            .gesture(

                RotationGesture()

                    .onChanged { rotation = $0 }

            )

    }

}

14.3 数据持久化


import SwiftUI

  


struct TodoItem: Identifiable, Codable {

    let id: UUID

    var title: String

    var isCompleted: Bool

    var createdAt: Date

}

  


class TodoStore: ObservableObject {

    @Published var todos: [TodoItem] = []

    

    private let saveKey = "todos"

    

    init() {

        load()

    }

    

    func add(_ title: String) {

        let todo = TodoItem(

            id: UUID(),

            title: title,

            isCompleted: false,

            createdAt: Date()

        )

        todos.append(todo)

        save()

    }

    

    func toggle(_ todo: TodoItem) {

        if let index = todos.firstIndex(where: { $0.id == todo.id }) {

            todos[index].isCompleted.toggle()

            save()

        }

    }

    

    func delete(at offsets: IndexSet) {

        todos.remove(atOffsets: offsets)

        save()

    }

    

    // UserDefaults 存储

    private func save() {

        if let encoded = try? JSONEncoder().encode(todos) {

            UserDefaults.standard.set(encoded, forKey: saveKey)

        }

    }

    

    private func load() {

        if let data = UserDefaults.standard.data(forKey: saveKey),

           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {

            todos = decoded

        }

    }

}


第15章:并发编程 - async/await

15.1 为什么需要并发?

想象一个场景:

  • 用户点击按钮

  • App 需要从网络加载图片

  • 如果用同步方式,界面会卡住,直到图片加载完成

  • 用户看到"假死",体验极差

**并发让 App 能同时做多件事: **

  • 主线程:响应用户操作、更新界面

  • 后台线程:网络请求、文件读写、复杂计算

15.2 旧的方式 - GCD


// 旧的写法(仍然有效,但不推荐新代码使用)

DispatchQueue.global().async {

    // 后台线程执行耗时操作

    let data = try! Data(contentsOf: url)

    

    DispatchQueue.main.async {

        // 主线程更新 UI

        imageView.image = UIImage(data: data)

    }

}

**问题: **

  • 嵌套层级深(回调地狱)

  • 错误处理麻烦

  • 代码难以阅读和维护

15.3 async/await - 新的方式


// 定义异步函数

func fetchImage(from url: URL) async throws -> UIImage {

    let (data, _) = try await URLSession.shared.data(from: url)

    guard let image = UIImage(data: data) else {

        throw ImageError.invalidData

    }

    return image

}

  


// 调用异步函数

func loadImage() async {

    do {

        let image = try await fetchImage(from: imageURL)

        imageView.image = image

    } catch {

        print("加载失败: \(error)")

    }

}

**代码看起来像同步的,实际上是异步执行的! **

15.4 在 SwiftUI 中使用


struct AsyncImageView: View {

    @State private var image: UIImage?

    @State private var isLoading = false

    

    let url: URL

    

    var body: some View {

        Group {

            if let image = image {

                Image(uiImage: image)

                    .resizable()

                    .aspectRatio(contentMode: .fit)

            } else if isLoading {

                ProgressView()

            } else {

                Image(systemName: "photo")

                    .font(.largeTitle)

                    .foregroundColor(.gray)

            }

        }

        .onAppear {

            loadImage()

        }

    }

    

    private func loadImage() {

        isLoading = true

        

        Task {

            do {

                let (data, _) = try await URLSession.shared.data(from: url)

                await MainActor.run {

                    self.image = UIImage(data: data)

                    self.isLoading = false

                }

            } catch {

                await MainActor.run {

                    self.isLoading = false

                }

            }

        }

    }

}

**Task: **创建异步任务的环境

**MainActor: **确保代码在主线程执行(用于 UI 更新)

15.5 结构化并发


// 顺序执行(慢)

func fetchSequentially() async throws -> [User] {

    let user1 = try await fetchUser(id: 1)

    let user2 = try await fetchUser(id: 2)

    let user3 = try await fetchUser(id: 3)

    return [user1, user2, user3]

}

  


// 并行执行(快!)

func fetchConcurrently() async throws -> [User] {

    async let user1 = fetchUser(id: 1)

    async let user2 = fetchUser(id: 2)

    async let user3 = fetchUser(id: 3)

    return try await [user1, user2, user3]

}

  


// 使用 TaskGroup 处理动态数量的任务

func fetchUsers(ids: [Int]) async throws -> [User] {

    try await withThrowingTaskGroup(of: User.self) { group in

        for id in ids {

            group.addTask {

                try await fetchUser(id: id)

            }

        }

        

        var users: [User] = []

        for try await user in group {

            users.append(user)

        }

        return users

    }

}

**async let: **启动并行任务

**withTaskGroup: **管理一组动态任务

15.6 异步序列


// 异步序列

let stream = AsyncStream<Int> { continuation in

    Task {

        for i in 1...5 {

            try? await Task.sleep(for: .seconds(1))

            continuation.yield(i)

        }

        continuation.finish()

    }

}

  


// 遍历

for await number in stream {

    print("收到: \(number)")

}


第16章:Actor 与数据竞争防护

16.1 数据竞争问题


class UnsafeCounter {

    var count = 0

    

    func increment() {

        count += 1  // 非线程安全!

    }

}

  


let counter = UnsafeCounter()

  


// 从多个线程同时增加

DispatchQueue.concurrentPerform(iterations: 1000) { _ in

    counter.increment()

}

  


// 结果可能小于 1000!

print(counter.count)

原因: count += 1 实际上分三步:

  1. 读取 count 的值

  2. 加 1

  3. 写回 count

如果两个线程同时执行,可能都读到 5,都变成 6,结果只增加了 1。

16.2 Actor - 隔离状态


actor SafeCounter {

    private var count = 0

    

    func increment() {

        count += 1  // 线程安全!

    }

    

    func getCount() -> Int {

        return count

    }

}

  


let counter = SafeCounter()

  


await withTaskGroup(of: Void.self) { group in

    for _ in 0..<1000 {

        group.addTask {

            await counter.increment()

        }

    }

}

  


let finalCount = await counter.getCount()

print(finalCount)  // 1000

**Actor 的特点: **

  • 同一时间只有一个线程能访问 actor 内部

  • 自动防止数据竞争

  • 访问 actor 的属性和方法需要 await

16.3 MainActor - 主线程 Actor


@MainActor

class ViewModel: ObservableObject {

    @Published var items: [Item] = []

    

    func load() async {

        items = await fetchItems()  // await 切到后台

        // 自动回到主线程

    }

}

  


// 或者只标记某个方法

class ViewModel2: ObservableObject {

    @Published var items: [Item] = []

    

    @MainActor

    func updateUI() {

        // 确保在主线程执行

        items.append(newItem)

    }

}

16.4 Sendable 协议


// 安全的值类型,可以跨 actor 传递

struct UserData: Sendable {

    let id: UUID

    let name: String

}

  


// 类也可以遵循 Sendable,但需要是 final 且属性都是 Sendable

final class SafeClass: Sendable {

    let value: Int

    init(value: Int) {

        self.value = value

    }

}

**Swift 6 会强制检查: **如果类型不是 Sendable,不能跨 actor 传递。


第一次做分享,期待更优秀的你~