
- 在Package.swift中注释掉旧的swift-composable-architecture依赖,并添加swift-case-paths依赖。 - 在Podfile中将iOS平台版本更新至16.0,并移除QCloudCOSXML/Transfer依赖,改为使用QCloudCOSXML。 - 更新Podfile.lock以反映依赖变更,确保项目依赖的准确性。 - 新增架构分析需求文档,明确项目架构评估和改进建议。 - 在多个文件中实现async/await语法,提升异步操作的可读性和性能。 - 更新日志输出方法,确保在调试模式下提供一致的调试信息。 - 优化多个视图组件,提升用户体验和代码可维护性。
138 lines
4.6 KiB
Swift
138 lines
4.6 KiB
Swift
import Foundation
|
||
import SwiftUI
|
||
import Combine
|
||
|
||
// MARK: - API Loading Manager
|
||
|
||
/// 全局 API 加载状态管理器
|
||
///
|
||
/// 该管理器负责:
|
||
/// - 跟踪多个并发的 API 调用状态
|
||
/// - 管理 loading 和错误信息的显示
|
||
/// - 自动清理过期的错误信息
|
||
/// - 提供线程安全的状态更新
|
||
@MainActor
|
||
class APILoadingManager: ObservableObject {
|
||
// MARK: - Properties
|
||
/// 单例实例
|
||
static let shared = APILoadingManager()
|
||
/// 当前活动的加载项
|
||
@Published private(set) var loadingItems: [APILoadingItem] = []
|
||
/// 错误清理任务
|
||
private var errorCleanupTasks: [UUID: DispatchWorkItem] = [:]
|
||
/// 私有初始化器,确保单例
|
||
private init() {}
|
||
// MARK: - Public Methods
|
||
/// 开始显示 loading
|
||
/// - Parameters:
|
||
/// - shouldShowLoading: 是否显示 loading 动画
|
||
/// - shouldShowError: 是否显示错误信息
|
||
/// - Returns: 唯一的加载 ID,用于后续更新状态
|
||
func startLoading(shouldShowLoading: Bool = true, shouldShowError: Bool = true) -> UUID {
|
||
let loadingId = UUID()
|
||
let loadingItem = APILoadingItem(
|
||
id: loadingId,
|
||
state: .loading,
|
||
shouldShowError: shouldShowError,
|
||
shouldShowLoading: shouldShowLoading
|
||
)
|
||
loadingItems.append(loadingItem)
|
||
return loadingId
|
||
}
|
||
/// 更新 loading 状态为成功
|
||
/// - Parameter id: 加载 ID
|
||
func finishLoading(_ id: UUID) {
|
||
removeLoading(id)
|
||
}
|
||
/// 更新 loading 状态为错误
|
||
/// - Parameters:
|
||
/// - id: 加载 ID
|
||
/// - errorMessage: 错误信息
|
||
func setError(_ id: UUID, errorMessage: String) {
|
||
guard let index = loadingItems.firstIndex(where: { $0.id == id }) else { return }
|
||
let currentItem = loadingItems[index]
|
||
if currentItem.shouldShowError {
|
||
let errorItem = APILoadingItem(
|
||
id: id,
|
||
state: .error(message: errorMessage),
|
||
shouldShowError: true,
|
||
shouldShowLoading: currentItem.shouldShowLoading
|
||
)
|
||
loadingItems[index] = errorItem
|
||
setupErrorCleanup(for: id)
|
||
} else {
|
||
loadingItems.removeAll { $0.id == id }
|
||
}
|
||
}
|
||
/// 手动移除特定的加载项
|
||
/// - Parameter id: 加载 ID
|
||
private func removeLoading(_ id: UUID) {
|
||
cancelErrorCleanup(for: id)
|
||
loadingItems.removeAll { $0.id == id }
|
||
}
|
||
/// 清空所有加载项(用于应急情况)
|
||
func clearAll() {
|
||
errorCleanupTasks.values.forEach { $0.cancel() }
|
||
errorCleanupTasks.removeAll()
|
||
loadingItems.removeAll()
|
||
}
|
||
// MARK: - Computed Properties
|
||
/// 是否有正在显示的 loading
|
||
var hasActiveLoading: Bool {
|
||
loadingItems.contains { $0.state == .loading && $0.shouldDisplay }
|
||
}
|
||
/// 是否有正在显示的错误
|
||
var hasActiveError: Bool {
|
||
loadingItems.contains { $0.isError && $0.shouldDisplay }
|
||
}
|
||
// MARK: - Private Methods
|
||
/// 设置错误信息自动清理
|
||
/// - Parameter id: 加载 ID
|
||
private func setupErrorCleanup(for id: UUID) {
|
||
let workItem = DispatchWorkItem { [weak self] in
|
||
self?.removeLoading(id)
|
||
}
|
||
errorCleanupTasks[id] = workItem
|
||
DispatchQueue.main.asyncAfter(
|
||
deadline: .now() + APILoadingConfiguration.errorDisplayDuration,
|
||
execute: workItem
|
||
)
|
||
}
|
||
/// 取消错误清理任务
|
||
/// - Parameter id: 加载 ID
|
||
private func cancelErrorCleanup(for id: UUID) {
|
||
errorCleanupTasks[id]?.cancel()
|
||
errorCleanupTasks.removeValue(forKey: id)
|
||
}
|
||
}
|
||
|
||
// MARK: - Convenience Extensions
|
||
|
||
extension APILoadingManager {
|
||
/// 便捷方法:执行带 loading 的异步操作
|
||
/// - Parameters:
|
||
/// - shouldShowLoading: 是否显示 loading
|
||
/// - shouldShowError: 是否显示错误
|
||
/// - operation: 异步操作
|
||
/// - Returns: 操作结果
|
||
@MainActor
|
||
func withLoading<T: Sendable>(
|
||
shouldShowLoading: Bool = true,
|
||
shouldShowError: Bool = true,
|
||
operation: @escaping () async throws -> T
|
||
) async -> Result<T, Error> {
|
||
let loadingId = startLoading(
|
||
shouldShowLoading: shouldShowLoading,
|
||
shouldShowError: shouldShowError
|
||
)
|
||
do {
|
||
let result = try await operation()
|
||
finishLoading(loadingId)
|
||
return .success(result)
|
||
} catch {
|
||
setError(loadingId, errorMessage: error.localizedDescription)
|
||
return .failure(error)
|
||
}
|
||
}
|
||
}
|