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( shouldShowLoading: Bool = true, shouldShowError: Bool = true, operation: @escaping () async throws -> T ) async -> Result { 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) } } }