wake-ios/specs/refactor_spec.md

12 KiB
Raw Blame History

Wake iOS 重构与性能优化规格说明

版本: v0.1 创建时间: 2025-09-08 15:41 +08

背景与目标

  • 现状问题:代码组织结构一般、页面间切换卡顿(特别是在重动画/媒体加载/网络日志时)。
  • 目标:
    • 提升导航一致性与可维护性(仅保留顶层 NavigationStack + Router
    • 降低页面切换卡顿(减少主线程压力、控制刷新频率、优化媒体与动画负载、收敛网络日志)。
    • 推动 Feature-Oriented 结构与 MVVM降低视图体量与重绘范围。

架构调整总览

  • 统一导航:顶层 NavigationStack(path: $router.path)(见 wake/WakeApp.swift),子页面不再嵌套 NavigationView;使用 Router.shared.navigate/pop/popToRoot
  • MVVM优先对 BlindBoxView 引入 BlindBoxViewModel,将轮询、计时器、媒体预处理、会员信息等迁至 VM。
  • 并发与取消:轮询改 AsyncSequence/Task 可取消;倒计时改 Combine/AsyncTimer统一在 onDisappear/路由变化处取消。
  • 媒体与动画GIF 优先替换为 Lottie 或仅在可见态播放;模糊与缩放动画范围与时机控制;媒体元数据后台计算。
  • 网络日志Debug 可控、限流Release 关闭大段打印;使用 os_log/Logger 分类。
  • 工程结构Feature-OrientedCore/Features/*SharedUI/);延续 Theme/Typography/Spacing 设计系统。

导航设计规范

  • 顶层:WakeApp 中唯一 NavigationStack;其它页面不使用 NavigationView
  • 路由:统一通过 Router.shared.navigate(to:)Router.shared.pop()Router.shared.popToRoot()
  • 返回按钮:子页面通过 Router.shared.pop() 而非 presentationMode.dismiss()

BlindBox 模块重构要点

  • BlindBoxViewModel@MainActor
    • 状态:盲盒列表/单盒数据、会员信息、计时与轮询状态、媒体 URL/尺寸/播放器句柄。
    • 行为:loadBlindBox()start/stopPolling()startCountdown()prepareVideo/Image()、资源清理。
  • 视图拆分:
    • BlindBoxHeaderBlindBoxAnimationAreaLoading/Ready/Opening/NoneBlindBoxActionButtonBlindBoxScalingOverlay
    • 视图仅订阅少量 @Published,降低 body 重绘。

并发与轮询规范

  • 轮询:使用 Task { for await ... in pollSequence } + task.cancel(),严禁无 cancel 的 while + Task.sleep
  • 倒计时:优先 0.25s0.5s 频率;必要时“毫秒展示”不落地 state严格在主线程更新 UI 状态。

媒体与动画规范

  • GIF -> Lottie 优先;若保留 GIF仅在可见态播放避免与大范围模糊+缩放并发。
  • 媒体预热与尺寸探测走后台,回主线程赋值。
  • 播放器生命周期集中管理,页面切换前暂停并释放。

网络日志策略

  • Debug按需与限长打印错误优先可通过开关关闭详细日志。
  • Release关闭大段请求/响应体打印。

工程结构规划(建议)

  • Core/Utils/Network/Auth/Router/Theme/Typography/
  • Features/BlindBox/View/ViewModel/Models/API/Components/
  • Features/Subscribe/:含 CreditsInfoCardPlanCompare
  • Features/Memories/
  • SharedUI/Buttons、LottieView、SVGImage、SheetModal 等

实施计划(分阶段)

  • 第一阶段12 天,先解卡顿):
    1. 统一导航:移除子页面 NavigationView,使用顶层 NavigationStack + Router
    2. 计时器降频0.250.5s;如非必要移除毫秒级显示。
    3. GIF 限制播放或替换为 Lottie关/收敛网络大日志。
  • 第二阶段24 天): 4) 为 BlindBox 引入 ViewModel迁移副作用与状态。 5) 轮询改为可取消的异步序列;媒体预热与尺寸探测后台化。 6) 视图拆分与体量控制。
  • 第三阶段(持续): 7) 目录重组ViewModel 标注 @MainActor;保留 os_signpost 监测关键路径。

验收标准DoD

  • 导航:仅顶层 NavigationStack;子页面无 NavigationView
  • 性能:转场掉帧率明显下降;主界面进入/退出动画流畅。
  • 结构:BlindBoxView < 300 行,主要状态/副作用位于 ViewModel。
  • 资源GIF 仅在可见时播放或替换为 Lottie网络日志按需输出。

任务清单(同步 todo

  • nav-1 统一导航(移除子页面 NavigationViewRouter 返回)
  • mvvm-1 BlindBox 引入 ViewModel迁移逻辑
  • timer-1 计时器降频与取消
  • polling-1 轮询可取消化
  • media-1 媒体与动画优化GIF->Lottie/可见播放)
  • concurrency-1 @MainActor 与线程安全
  • netlog-1 网络日志开关与限流
  • structure-1 目录重组(进行中)
  • perf-1 性能埋点与基线(进行中)

进度记录(每次执行后更新)

  • 2025-09-08 15:41 +08: 创建 specs 目录与本说明v0.1)。

  • 2025-09-08 15:41 +08: 完成 nav-1第一步移除 wake/View/Blind/BlindOutCome.swiftwake/View/Memories/MemoriesView.swift 中的 NavigationView,改为使用 Router.shared.pop() 返回;依赖顶层 NavigationStack

  • 2025-09-08 15:51 +08: 继续完成 nav-1

    • 移除 wake/View/Credits/CreditsDetailView.swiftwake/View/Welcome/SplashView.swiftwake/View/Owner/SettingsView.swiftwake/View/Components/Upload/MediaUpload.swift(示例 MediaUploadExample)、wake/View/Examples/MediaDemo.swift 中的 NavigationView
    • wake/View/Feedback.swiftFeedbackViewFeedbackDetailView 的返回行为从 dismiss() 统一为 Router.shared.pop()
    • 保留预览Preview中的 NavigationView,运行时代码已全部依赖顶层 NavigationStack + Router
  • 2025-09-08 16:10 +08: 完成 timer-1 与 netlog-1

    • 倒计时更新频率由 0.1s 改为 1s移除毫秒级显示初始值设为 (36, 50, 0),减少 UI 重绘(wake/View/Blind/ContentView.swift)。
    • 网络日志:将详细请求/成功响应日志置于 #if DEBUG,错误响应体截断至约 300 字符401 刷新 Token 相关提示仅在 Debug 下打印(wake/Utils/NetworkService.swift)。
  • 2025-09-08 16:28 +08: 完成 polling-1

    • 新增 wake/View/Blind/BlindBoxPolling.swift,提供 AsyncThrowingStream 序列:singleBox(boxId:)firstUnopened(),内部可取消(Task.isCancelled)并使用统一的间隔控制。
    • wake/View/Blind/ContentView.swift 轮询改为 for try await ... in 形式;通过 pollingTask 管理任务,在 stopPolling()onDisappear 中取消,避免视图中出现 while + Task.sleep
  • 2025-09-08 18:05 +08: 推进 mvvm-1 与 media-1

    • ViewModelBlindBoxViewModel)新增 applyStatusSideEffects(),将状态联动的副作用集中处理:Preparing 开始 1s 倒计时(countdownText),其它状态停止倒计时;在 bootstrapInitialState()startPolling() 的数据落地后调用。
    • 倒计时迁移至 VM新增 startCountdown/stopCountdowncountdownText,视图使用 viewModel.countdownText 展示;ContentView 移除本地倒计时与定时器。
    • 首帧无闪烁:ContentView 初始 animationPhase = .none,监听 viewModel.didBootstrap 后按初始状态(大小写不敏感)切换 loading/ready;操作按钮在 didBootstrap 前隐藏。
    • 媒体优化起步:将 loading 阶段的 GIF 替换为 LottieLottieView(name: "loading"),资源位于 wake/Assets/Lottie/loading.json)。
  • 2025-09-08 19:19 +08: 完成 media-1

    • Loading/Ready/Opening 全部替换为 Lottieloading.json/data.json/open.json),并使用 isPlaying 仅在可见时播放Opening 使用 .playOnce
    • 引入 Perf 工具类并在关键路径打点Appear、状态切换、开盒点击、开启动画以支持 perf-1。
  • 2025-09-08 19:19 +08: 启动 structure-1进行中

    • 创建目录骨架:Core/SharedUI/Features/BlindBox/Features/Subscribe/ 等,仅添加 README不改变构建后续在 Xcode 内移动文件以保持引用正确。
  • 2025-09-09 11:26 +08: 推进 structure-1

    • 完成盲盒批次文件迁移View/ViewModel/API/Models/Components
    • 完成 SharedUI 与 Core 的第 13 步迁移:
      • SharedUILottieView.swiftGIFView.swiftSVGImage.swiftSheetModal.swift 已迁移至 wake/SharedUI/...(当前均放在 Animation/ 分组,后续可按需细分 Media/Modals/)。
      • CoreRouter.swift 已迁至 Core/Navigation/NetworkService.swift 已迁至 Core/Network/Theme.swiftTypography.swift 已迁至 Core/DesignSystem/
    • 待迁移:Performance.swiftCore/Diagnostics/APIConfig.swiftCore/Network/

structure-1 目录重构计划与迁移步骤

目标结构

  • wake/Core/
    • DesignSystem/Theme.swiftTypography.swift
    • Navigation/Router.swift
    • Diagnostics/Performance.swift
    • Network/NetworkService.swiftAPIConfig.swift、通用 ApiClient/*
  • wake/SharedUI/
    • Animation/LottieView.swift
    • Media/GIFView.swiftSVGImage.swift/SVGImageHtml.swift
    • Modals/SheetModal.swift 等)
    • Controls/(通用按钮、输入框等)
    • Graphics/(共享图形资源包装)
  • wake/Features/BlindBox/
    • View/ContentView.swift -> 建议更名 BlindBoxView.swift
    • ViewModel/BlindBoxViewModel.swift
    • API/BlindBoxApi.swiftBlindBoxPolling.swift
    • Models/BlindModels.swift
    • Components/(与盲盒强相关的子视图)
  • wake/Features/Subscribe/
    • Components/CreditsInfoCard.swiftPlanCompare.swift 等)
    • View/SubscribeView.swift 及其子视图)

迁移建议

  1. 在 Xcode 的 Project Navigator 中先创建“Group without folder”形式的虚拟分组稳定编译再选择是否同步到磁盘。
  2. 若需要调整磁盘目录:
    • 建议在 Xcode 中使用拖拽将文件移动到对应 Group同时勾选“Move files”与“Add to targets”避免红色引用。
    • 一次迁移一个 Feature迁移后立即编译验证。
  3. 资源文件Lottie JSON/SVG 等)保持在 wake/Assets/ 下;仅调整其使用方的源文件位置。
  4. 网络与工具类尽量放入 Core/Feature 专用 API 可放在 Features/<Feature>/API/

第一批建议迁移(盲盒)

  • wake/View/Blind/ContentView.swiftwake/Features/BlindBox/View/BlindBoxView.swift(建议改名)
  • wake/View/Blind/BlindBoxViewModel.swiftwake/Features/BlindBox/ViewModel/BlindBoxViewModel.swift
  • wake/View/Blind/BlindBoxPolling.swiftwake/Features/BlindBox/API/BlindBoxPolling.swift
  • wake/Utils/ApiClient/BlindBoxApi.swiftwake/Features/BlindBox/API/BlindBoxApi.swift
  • wake/Models/BlindModels.swiftwake/Features/BlindBox/Models/BlindModels.swift

公共组件迁移

  • wake/Components/Lottie/LottieView.swiftwake/SharedUI/Animation/LottieView.swift
  • wake/Utils/GIFView.swiftwake/SharedUI/Media/GIFView.swift
  • wake/Utils/SVGImage.swiftwake/Utils/SVGImageHtml.swiftwake/SharedUI/Media/
  • wake/View/Components/SheetModal.swiftwake/SharedUI/Modals/SheetModal.swift
  • wake/View/Components/Button.swift/Buttons/wake/SharedUI/Controls/

核心模块迁移

  • wake/Utils/Router.swiftwake/Core/Navigation/Router.swift
  • wake/Utils/Performance.swiftwake/Core/Diagnostics/Performance.swift
  • wake/Utils/NetworkService.swiftwake/Utils/APIConfig.swiftwake/Core/Network/
  • wake/Theme.swiftwake/Typography.swiftwake/Core/DesignSystem/

决策记录

  • 采用顶层 NavigationStack + Router,子页面取消 NavigationView
  • BlindBox 优先落地 MVVM 重构,其它模块随后跟进。