Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain

第7章:协议在跨平台开发中的应用

7.4 实战案例:跨平台待办事项应用

在前几节中,我们探讨了协议在跨平台开发中的作用,包括业务逻辑和 UI 层的协议化实现。本节将通过一个实战案例,整合这些概念,设计并实现一个跨平台的待办事项(To-Do)应用。该应用将支持 iOS 和 macOS(基于 SwiftUI),展示如何利用协议化编程(POP)实现逻辑复用、平台适配和模块化设计。

案例背景

我们要开发一个待办事项应用,支持以下功能:

  • 添加和删除任务。
  • 持久化存储任务数据。
  • 显示任务列表并支持跨平台 UI。 目标是:
  • 业务逻辑跨平台复用。
  • UI 适配 iOS 和 macOS。
  • 可测试且模块化。

系统模块划分

  1. 任务管理模块:处理任务的增删逻辑。
  2. 存储模块:管理任务数据的持久化。
  3. 视图模块:渲染任务列表和用户交互。
  4. 协调模块:整合各模块,驱动应用。

第一步:定义协议

// 任务实体
struct TaskItem: Identifiable, Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
}

// 任务管理协议
protocol TaskService {
    func addTask(title: String)
    func deleteTask(id: UUID)
    func allTasks() -> [TaskItem]
}

// 存储协议
protocol TaskStorage {
    func saveTasks(_ tasks: [TaskItem])
    func loadTasks() -> [TaskItem]
}

// 视图协议
protocol TaskView {
    func updateTasks(_ tasks: [TaskItem])
    func showError(_ message: String)
}
  • 协议定义了模块职责,保持跨平台通用性。

第二步:实现跨平台业务逻辑

class TaskManager: TaskService {
    private let storage: TaskStorage
    private let view: TaskView
    
    init(storage: TaskStorage, view: TaskView) {
        self.storage = storage
        self.view = view
        refresh()
    }
    
    func addTask(title: String) {
        var tasks = storage.loadTasks()
        let newTask = TaskItem(id: UUID(), title: title, isCompleted: false)
        tasks.append(newTask)
        storage.saveTasks(tasks)
        refresh()
    }
    
    func deleteTask(id: UUID) {
        var tasks = storage.loadTasks()
        if let index = tasks.firstIndex(where: { $0.id == id }) {
            tasks.remove(at: index)
            storage.saveTasks(tasks)
            refresh()
        } else {
            view.showError("Task not found")
        }
    }
    
    func allTasks() -> [TaskItem] {
        storage.loadTasks()
    }
    
    private func refresh() {
        view.updateTasks(allTasks())
    }
}
  • TaskManager 是跨平台逻辑,依赖注入存储和视图。

第三步:实现存储模块

为 iOS/macOS 提供文件存储实现:

struct FileTaskStorage: TaskStorage {
    private let fileURL: URL
    
    init(fileName: String) {
        let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        fileURL = documents.appendingPathComponent(fileName)
    }
    
    func saveTasks(_ tasks: [TaskItem]) {
        do {
            let data = try JSONEncoder().encode(tasks)
            try data.write(to: fileURL)
        } catch {
            print("Save failed: \(error)")
        }
    }
    
    func loadTasks() -> [TaskItem] {
        do {
            let data = try Data(contentsOf: fileURL)
            return try JSONDecoder().decode([TaskItem].self, from: data)
        } catch {
            return [] // 返回空列表作为默认值
        }
    }
}
  • FileTaskStorage 使用文件系统存储,跨 iOS/macOS 通用。

第四步:实现跨平台 UI

使用 SwiftUI 为 iOS 和 macOS 提供视图:

import SwiftUI

struct TaskListView: TaskView {
    @ObservedObject private var viewModel: TaskManager
    @State private var tasks: [TaskItem] = []
    @State private var errorMessage: String?
    
    init(viewModel: TaskManager) {
        self.viewModel = viewModel
    }
    
    func updateTasks(_ tasks: [TaskItem]) {
        self.tasks = tasks
    }
    
    func showError(_ message: String) {
        errorMessage = message
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(tasks) { task in
                    HStack {
                        Text(task.title)
                        Spacer()
                        if task.isCompleted {
                            Image(systemName: "checkmark")
                        }
                    }
                }
                .onDelete { indexSet in
                    indexSet.forEach { viewModel.deleteTask(id: tasks[$0].id) }
                }
            }
            .navigationTitle("To-Do List")
            .toolbar {
                Button(action: { viewModel.addTask(title: "New Task") }) {
                    Image(systemName: "plus")
                }
            }
            .alert(isPresented: Binding(get: { errorMessage != nil }, set: { if !$0 { errorMessage = nil } })) {
                Alert(title: Text("Error"), message: Text(errorMessage ?? ""))
            }
        }
        .onAppear { viewModel.refresh() }
    }
}
  • TaskListView 使用 SwiftUI,跨 iOS 和 macOS 渲染。

第五步:集成应用

let storage = FileTaskStorage(fileName: "tasks.json")
let view = TaskListView(viewModel: TaskManager(storage: storage, view: TaskListView(viewModel: nil)))
let manager = TaskManager(storage: storage, view: view)

// 在 SwiftUI 应用中:
import SwiftUI

@main
struct TodoApp: App {
    var body: some Scene {
        WindowGroup {
            TaskListView(viewModel: manager)
        }
    }
}
  • TaskManager 协调存储和视图,驱动应用。

第六步:测试跨平台逻辑

为 TaskManager 添加单元测试:

struct MockTaskStorage: TaskStorage {
    private var tasks: [TaskItem] = []
    
    mutating func saveTasks(_ tasks: [TaskItem]) {
        self.tasks = tasks
    }
    
    func loadTasks() -> [TaskItem] {
        tasks
    }
}

struct MockTaskView: TaskView {
    var updatedTasks: [TaskItem] = []
    var errors: [String] = []
    
    mutating func updateTasks(_ tasks: [TaskItem]) {
        updatedTasks = tasks
    }
    
    mutating func showError(_ message: String) {
        errors.append(message)
    }
}

import XCTest
class TaskManagerTests: XCTestCase {
    func testAddAndDeleteTask() {
        var storage = MockTaskStorage()
        var view = MockTaskView()
        let manager = TaskManager(storage: storage, view: view)
        
        manager.addTask(title: "Test Task")
        XCTAssertEqual(manager.allTasks().count, 1)
        XCTAssertEqual(view.updatedTasks.first?.title, "Test Task")
        
        let taskId = manager.allTasks().first!.id
        manager.deleteTask(id: taskId)
        XCTAssertEqual(manager.allTasks().count, 0)
        XCTAssertEqual(view.updatedTasks.count, 0)
    }
}
  • 测试验证了任务增删逻辑和视图更新。

案例分析

  1. 跨平台复用:
    • TaskManager 和协议跨 iOS/macOS 共享。
  2. UI 适配:
    • SwiftUI 自动适配平台风格(iOS 列表、macOS 窗口)。
  3. 模块化:
    • 存储、视图和管理模块独立,可替换。
  4. 测试性:
    • Mock 对象隔离依赖,测试高效。

扩展性

添加云存储支持:

struct CloudTaskStorage: TaskStorage {
    func saveTasks(_ tasks: [TaskItem]) {
        print("Saved to cloud: \(tasks.map { $0.title })")
    }
    
    func loadTasks() -> [TaskItem] {
        [TaskItem(id: UUID(), title: "Cloud Task", isCompleted: false)]
    }
}

let cloudManager = TaskManager(storage: CloudTaskStorage(), view: TaskListView(viewModel: nil))
  • 只需替换存储模块,逻辑无需调整。

注意事项

  • SwiftUI 限制:目前仅支持 Apple 平台,Android 需其他 UI 方案。
  • 存储路径:实际应用需处理文件路径兼容性。
  • 异步优化:可为存储添加 async 支持。

小结

通过跨平台待办事项应用,我们展示了协议如何实现业务逻辑复用、UI 适配和模块化设计。这一案例整合了跨平台开发的完整流程,为开发者提供了实用参考。下一章将探讨协议的高级用法,进一步提升编程能力。


Last Updated:: 3/11/25, 11:48 AM