第 19 课:移动端开发 — Swift / SwiftUI / Dart / Flutter

12 阅读8分钟

所属阶段:第四阶段「语言与框架」(第 17-22 课) 前置条件:第 17 课(后端语言) 本课收获:了解移动端 Skill 体系,能分析 Anti-Patterns


一、本课概述

移动端开发有自己独特的世界 — UI 线程安全、设备资源限制、平台审核规范、离线支持。ECC 为 Swift/SwiftUI 和 Dart/Flutter 两大生态提供了深度 Skill 支持,甚至覆盖了最前沿的设备端 LLM 集成。

本课回答三个问题:

  1. Swift 生态有哪些 Skill? — 从 SwiftUI 到 Swift 6.2 并发模型
  2. Flutter/Dart 生态有哪些 Skill? — 跨平台架构与代码审查
  3. 移动端 Skill 的 Anti-Patterns 是什么? — 从反面学习最佳实践

二、Swift 生态 Skill 全景

2.1 Skill 清单

Swift 是 ECC 中移动端 Skill 最丰富的语言,共 5 个专用 Skill:

Skill定位核心主题
swiftui-patternsSwiftUI 架构@Observable 状态管理、视图组合、导航
swift-concurrency-6-2Swift 6.2 并发单线程默认、@concurrent、actor 隔离
swift-actor-persistenceActor 持久化Actor 线程安全持久化、CoreData/SwiftData
swift-protocol-di-testing协议与测试协议 DI、可测试性设计、Mock 策略
foundation-models-on-device设备端 AI设备端 LLM、@Generable 宏

2.2 Skill 关系图

                 swiftui-patterns
              (UI 层:视图 + 状态 + 导航)
                    │
         ┌──────────┼──────────┐
         │          │          │
         ▼          ▼          ▼
  swift-concurrency  swift-actor   swift-protocol
      -6-2          -persistence    -di-testing
   (并发模型)     (持久化层)    (可测试性)
                    │
                    ▼
         foundation-models
            -on-device
          (设备端 AI)

这些 Skill 形成了一条从 UI 到底层的完整链:SwiftUI 视图 → 并发安全 → 数据持久化 → 协议抽象 → 设备端 AI。


三、swiftui-patterns 深入

3.1 @Observable 状态管理

Swift 5.9 引入了 @Observable 宏,取代了 ObservableObject 协议。swiftui-patterns Skill 强调新模式:

// 旧模式(Swift 5.8 及之前)— 不再推荐
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var email: String = ""
}

// 新模式(Swift 5.9+)— 推荐
@Observable
class UserViewModel {
    var name: String = ""
    var email: String = ""
}

关键区别@Observable 实现了属性级别的变更追踪,而不是整个对象级别。这意味着当 name 变化时,只有用到 name 的视图会重新渲染,用到 email 的视图不受影响。

3.2 视图组合原则

swiftui-patterns 推荐的视图组织方式:

视图层级:
  Screen(屏幕)     → 顶层容器,处理导航和数据获取
    Section(区块)   → 逻辑分组
      Component(组件)→ 可复用的 UI 单元
        Element(元素)→ 最小 UI 原语

Anti-Pattern:巨型视图

// WRONG — 一个视图做了太多事情(God View)
struct UserProfileScreen: View {
    var body: some View {
        ScrollView {
            // 头像区域 ... 50 行
            // 个人信息 ... 80 行
            // 设置列表 ... 100 行
            // 底部操作 ... 30 行
        }
    }
}

// CORRECT — 拆分为子组件
struct UserProfileScreen: View {
    var body: some View {
        ScrollView {
            AvatarSection(user: user)
            InfoSection(user: user)
            SettingsSection(settings: settings)
            ActionBar(onLogout: handleLogout)
        }
    }
}

3.3 导航模式

SwiftUI 的导航经历了多次演进。swiftui-patterns 推荐 NavigationStack 模式:

// 推荐:NavigationStack + NavigationPath
@Observable
class Router {
    var path = NavigationPath()

    func push(_ destination: Destination) {
        path.append(destination)
    }

    func pop() {
        path.removeLast()
    }

    func popToRoot() {
        path.removeLast(path.count)
    }
}

四、Swift 6.2 并发模型

4.1 swift-concurrency-6-2 核心变化

Swift 6.2 带来了并发模型的重大变化。swift-concurrency-6-2 Skill 是理解这些变化的关键:

核心变化:默认单线程

Swift 6.1 及之前:
  nonisolated 函数 → 可能在任意线程执行

Swift 6.2:
  nonisolated 函数 → 默认在 caller 的 actor 上执行
  @concurrent 标注  → 显式声明"可以在其他线程执行"

为什么这样设计?

大多数代码不需要并发执行。默认单线程减少了数据竞争的风险,需要并发时显式标注 @concurrent

4.2 Actor 隔离

actor DatabaseManager {
    private var cache: [String: Data] = [:]

    func fetch(key: String) -> Data? {
        // 这里是 actor 隔离的 — 线程安全
        return cache[key]
    }

    func store(key: String, value: Data) {
        // 同一时刻只有一个任务能执行
        cache[key] = value
    }
}

4.3 swift-actor-persistence

swift-actor-persistence Skill 处理一个棘手问题:如何在 Actor 隔离的约束下进行数据持久化。

问题:
  CoreData/SwiftData 的 context 不是线程安全的
  Actor 保证了内部状态的线程安全
  如何让两者协作?

解决方案:
  ModelActor 宏 — 创建一个绑定到特定 context 的 Actor
@ModelActor
actor PersistenceActor {
    func createUser(name: String) throws -> User {
        let user = User(name: name)
        modelContext.insert(user)
        try modelContext.save()
        return user
    }
}

五、设备端 LLM 集成

5.1 foundation-models-on-device

这是 ECC 中最前沿的 Skill 之一。Apple 在 iOS 26 / macOS 26 中引入了 Foundation Models 框架,允许在设备上运行 LLM。

@Generable 宏

@Generable
struct RecipeSuggestion {
    var title: String
    var ingredients: [String]
    var steps: [String]
    var estimatedTime: Int
}

// 使用
let session = LanguageModelSession()
let suggestion: RecipeSuggestion = try await session.respond(
    to: "Suggest a quick pasta recipe",
    generating: RecipeSuggestion.self
)

设备端 vs 云端 LLM

维度设备端云端
隐私数据不离开设备数据上传到服务器
延迟无网络延迟受网络影响
能力受限于设备算力几乎无限
离线可用不可用
成本免费按 token 计费

六、Flutter / Dart 生态

6.1 Skill 清单

Skill定位核心主题
dart-flutter-patternsDart + Flutter 架构空安全、状态管理、Widget 架构、GoRouter
flutter-dart-code-review代码审查Widget 最佳实践、性能陷阱
compose-multiplatform-patternsKMP 共享 UIKotlin Multiplatform 共享 UI 层
android-clean-architectureAndroid 架构Clean Architecture、KMP 模块划分

6.2 dart-flutter-patterns 核心

空安全(Null Safety)

Dart 的空安全系统是类型系统的一部分,dart-flutter-patterns 强调:

// 类型系统保证
String name;           // 不可空 — 必须有值
String? nickname;      // 可空 — 可以是 null
String title = '';     // 不可空 + 有默认值

// 空安全操作符
nickname?.toUpperCase()      // 空时返回 null
nickname ?? 'Anonymous'      // 空时用默认值
nickname!.toUpperCase()      // 断言非空(谨慎使用)

状态管理方案对比

方案复杂度适用场景ECC 推荐度
setState局部状态仅限简单场景
Provider中型应用推荐
Riverpod中高中大型应用强烈推荐
BLoC大型应用企业级推荐

Widget 架构

// Anti-Pattern: 深层嵌套
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Container(
              decoration: BoxDecoration(...),
              child: Row(
                children: [
                  // 已经 6 层嵌套了...
                ],
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

// 正确做法:提取子 Widget
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: _ContentColumn(),
      ),
    ),
  );
}

6.3 GoRouter 导航

dart-flutter-patterns 推荐 GoRouter 作为导航方案:

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'users/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return UserDetailScreen(userId: id);
          },
        ),
      ],
    ),
  ],
);

七、跨平台方案对比

7.1 Flutter vs Compose Multiplatform

ECC 同时提供了两种跨平台方案的 Skill:

维度Flutter (dart-flutter-patterns)KMP (compose-multiplatform-patterns)
语言DartKotlin
UI 引擎自绘引擎(Skia/Impeller)平台原生 + Compose
代码共享UI + 逻辑全共享逻辑共享,UI 可选共享
平台iOS, Android, Web, DesktopiOS, Android, Desktop, Web
学习曲线中等已有 Kotlin 经验则较低
ECC Agentflutter-reviewer, dart-build-resolverkotlin-reviewer, kotlin-build-resolver

7.2 android-clean-architecture

android-clean-architecture Skill 定义了 Android 项目的分层架构:

┌─────────────────────────┐
│  Presentation Layer     │  ← UI + ViewModel
│  (Android/Compose)      │
├─────────────────────────┤
│  Domain Layer           │  ← Use Cases + Entities
│  (Pure Kotlin)          │     无框架依赖
├─────────────────────────┤
│  Data Layer             │  ← Repository 实现
│  (Room/Retrofit/etc.)   │     + Data Sources
└─────────────────────────┘

关键原则:Domain Layer 是纯 Kotlin,不依赖任何 Android 框架。这使得业务逻辑可以在 KMP 项目中跨平台共享。


八、移动端 Anti-Patterns 分析

8.1 SwiftUI Anti-Patterns

Anti-Pattern问题正确做法
God View一个视图超过 300 行拆分为 Screen → Section → Component
过度使用 @State所有状态都放在视图里提取到 @Observable ViewModel
忽略 Actor 隔离在主线程做耗时操作使用 Actor 或 Task.detached
强制解包到处使用 !使用 guard letif let
忽略生命周期不清理 Task使用 .task modifier 自动管理

8.2 Flutter Anti-Patterns

Anti-Pattern问题正确做法
深层嵌套Widget 嵌套超过 5 层提取子 Widget 或自定义 Widget
setState 滥用在大型应用中用 setState使用 Riverpod 或 BLoC
阻塞 UI 线程在 build 方法中做计算使用 compute() 或 Isolate
不使用 const每次 build 创建新 Widget尽可能使用 const 构造器
忽略 Key列表不使用 Key为列表项提供唯一 Key

九、本课练习

练习 1:查看移动端 Skill(10 分钟)

# Swift 生态
ls skills/swiftui-patterns/
ls skills/swift-concurrency-6-2/

# Flutter 生态
ls skills/dart-flutter-patterns/

回答问题:

  • swiftui-patterns 中关于 @Observable 的章节在哪里?
  • dart-flutter-patterns 推荐了哪些状态管理方案?

练习 2:分析 Anti-Patterns(20 分钟)

这是本课最重要的练习。

选择一个移动端 Skill(如 swiftui-patternsdart-flutter-patterns),找到其中描述的 Anti-Patterns 部分。

对每个 Anti-Pattern:

  1. 用自己的话解释为什么它是问题
  2. 写出正确做法的伪代码
  3. 思考在你的项目中是否存在类似问题

练习 3:对比导航方案(15 分钟)

对比 SwiftUI 的 NavigationStack 和 Flutter 的 GoRouter:

  • 它们的路由定义方式有什么相似之处?
  • 深层链接(Deep Link)的处理方式有什么差异?

练习 4(选做):思考题

设备端 LLM(foundation-models-on-device)适合哪些移动应用场景?不适合哪些?限制因素是什么?


十、本课小结

你应该记住的内容
Swift 生态5 个 Skill,从 SwiftUI 到设备端 LLM
核心变化Swift 6.2 默认单线程,@concurrent 显式并发
Flutter 生态4 个 Skill,含跨平台 KMP 方案
Anti-PatternsSwiftUI 的 God View,Flutter 的深层嵌套
跨平台选择Flutter 全共享 vs KMP 逻辑共享

十一、下节预告

第 20 课:数据库模式 — 设计、迁移与优化

下节课我们将进入数据层。你将学习 PostgreSQL 查询优化、零停机数据库迁移、以及 ECC 的 database-reviewer Agent 如何帮你避免 N+1 查询和不安全的 Schema 变更。

预习建议:提前浏览 skills/postgres-patternsskills/database-migrations 目录。