
- 在Package.swift中注释掉旧的swift-composable-architecture依赖,并添加swift-case-paths依赖。 - 在Podfile中将iOS平台版本更新至16.0,并移除QCloudCOSXML/Transfer依赖,改为使用QCloudCOSXML。 - 更新Podfile.lock以反映依赖变更,确保项目依赖的准确性。 - 新增架构分析需求文档,明确项目架构评估和改进建议。 - 在多个文件中实现async/await语法,提升异步操作的可读性和性能。 - 更新日志输出方法,确保在调试模式下提供一致的调试信息。 - 优化多个视图组件,提升用户体验和代码可维护性。
241 lines
9.1 KiB
Swift
241 lines
9.1 KiB
Swift
import Foundation
|
||
|
||
// MARK: - API Logger
|
||
@MainActor
|
||
class APILogger {
|
||
enum LogLevel {
|
||
case none
|
||
case basic
|
||
case detailed
|
||
}
|
||
|
||
#if DEBUG
|
||
static var logLevel: LogLevel = .detailed
|
||
#else
|
||
static var logLevel: LogLevel = .none
|
||
#endif
|
||
|
||
private static let dateFormatter: DateFormatter = {
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = "HH:mm:ss.SSS"
|
||
return formatter
|
||
}()
|
||
|
||
// MARK: - Request Logging
|
||
@MainActor static func logRequest<T: APIRequestProtocol>(
|
||
_ request: T,
|
||
url: URL,
|
||
body: Data?,
|
||
finalHeaders: [String: String]? = nil
|
||
) {
|
||
#if DEBUG
|
||
guard logLevel != .none else { return }
|
||
#else
|
||
return
|
||
#endif
|
||
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
|
||
print("\n🚀 [API Request] [\(timestamp)] ==================")
|
||
print("📍 Endpoint: \(request.endpoint)")
|
||
print("🔗 Full URL: \(url.absoluteString)")
|
||
print("📝 Method: \(request.method.rawValue)")
|
||
print("⏰ Timeout: \(request.timeout)s")
|
||
|
||
// 显示最终的完整 headers(包括默认 headers 和自定义 headers)
|
||
if let headers = finalHeaders, !headers.isEmpty {
|
||
if logLevel == .detailed {
|
||
print("📋 Final Headers (包括默认 + 自定义):")
|
||
for (key, value) in headers.sorted(by: { $0.key < $1.key }) {
|
||
print(" \(key): \(value)")
|
||
}
|
||
} else if logLevel == .basic {
|
||
print("📋 Headers: \(headers.count) 个 headers")
|
||
// 只显示重要的 headers
|
||
let importantHeaders = ["Content-Type", "Accept", "User-Agent", "Authorization"]
|
||
for key in importantHeaders {
|
||
if let value = headers[key] {
|
||
print(" \(key): \(value)")
|
||
}
|
||
}
|
||
}
|
||
} else if let customHeaders = request.headers, !customHeaders.isEmpty {
|
||
print("📋 Custom Headers:")
|
||
for (key, value) in customHeaders.sorted(by: { $0.key < $1.key }) {
|
||
print(" \(key): \(value)")
|
||
}
|
||
} else {
|
||
print("📋 Headers: 使用默认 headers")
|
||
}
|
||
|
||
if let queryParams = request.queryParameters, !queryParams.isEmpty {
|
||
print("🔍 Query Parameters:")
|
||
for (key, value) in queryParams.sorted(by: { $0.key < $1.key }) {
|
||
print(" \(key): \(value)")
|
||
}
|
||
}
|
||
|
||
if logLevel == .detailed {
|
||
if let body = body {
|
||
print("📦 Request Body (\(body.count) bytes):")
|
||
if let jsonObject = try? JSONSerialization.jsonObject(with: body, options: []),
|
||
let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
|
||
let prettyString = String(data: prettyData, encoding: .utf8) {
|
||
print(prettyString)
|
||
} else if let rawString = String(data: body, encoding: .utf8) {
|
||
print(rawString)
|
||
} else {
|
||
print("Binary data")
|
||
}
|
||
} else {
|
||
print("📦 Request Body: No body")
|
||
}
|
||
|
||
// 显示基础参数信息(仅详细模式)
|
||
if request.includeBaseParameters {
|
||
print("📱 Base Parameters: 自动注入设备和应用信息")
|
||
let baseParams = BaseRequest()
|
||
print(" Device: \(baseParams.model), OS: \(baseParams.os) \(baseParams.osVersion)")
|
||
print(" App: \(baseParams.app) v\(baseParams.appVersion)")
|
||
print(" Language: \(baseParams.acceptLanguage)")
|
||
}
|
||
} else if logLevel == .basic {
|
||
if let body = body {
|
||
print("📦 Request Body: \(formatBytes(body.count))")
|
||
} else {
|
||
print("📦 Request Body: No body")
|
||
}
|
||
|
||
// 基础模式也显示是否包含基础参数
|
||
if request.includeBaseParameters {
|
||
print("📱 Base Parameters: 已自动注入")
|
||
}
|
||
}
|
||
|
||
print("=====================================")
|
||
}
|
||
|
||
// MARK: - Response Logging
|
||
static func logResponse(data: Data, response: HTTPURLResponse, duration: TimeInterval) {
|
||
#if DEBUG
|
||
guard logLevel != .none else { return }
|
||
#else
|
||
return
|
||
#endif
|
||
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
let statusEmoji = response.statusCode < 400 ? "✅" : "❌"
|
||
|
||
print("\n\(statusEmoji) [API Response] [\(timestamp)] ===================")
|
||
print("⏱️ Duration: \(String(format: "%.3f", duration))s")
|
||
print("📊 Status Code: \(response.statusCode)")
|
||
print("🔗 URL: \(response.url?.absoluteString ?? "Unknown")")
|
||
print("📏 Data Size: \(formatBytes(data.count))")
|
||
|
||
if logLevel == .detailed {
|
||
print("📋 Response Headers:")
|
||
for (key, value) in response.allHeaderFields.sorted(by: { "\($0.key)" < "\($1.key)" }) {
|
||
print(" \(key): \(value)")
|
||
}
|
||
|
||
print("📦 Response Data:")
|
||
if data.isEmpty {
|
||
print(" Empty response")
|
||
} else if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
|
||
let prettyData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
|
||
let prettyString = String(data: prettyData, encoding: .utf8) {
|
||
print(prettyString)
|
||
} else if let rawString = String(data: data, encoding: .utf8) {
|
||
print(rawString)
|
||
} else {
|
||
print(" Binary data (\(data.count) bytes)")
|
||
}
|
||
}
|
||
|
||
print("=====================================")
|
||
}
|
||
|
||
// MARK: - Error Logging
|
||
static func logError(_ error: Error, url: URL?, duration: TimeInterval) {
|
||
#if DEBUG
|
||
guard logLevel != .none else { return }
|
||
#else
|
||
return
|
||
#endif
|
||
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
|
||
print("\n❌ [API Error] [\(timestamp)] ======================")
|
||
print("⏱️ Duration: \(String(format: "%.3f", duration))s")
|
||
if let url = url {
|
||
print("🔗 URL: \(url.absoluteString)")
|
||
}
|
||
|
||
if let apiError = error as? APIError {
|
||
print("🚨 API Error: \(apiError.localizedDescription)")
|
||
} else {
|
||
print("🚨 System Error: \(error.localizedDescription)")
|
||
}
|
||
|
||
if logLevel == .detailed {
|
||
if let urlError = error as? URLError {
|
||
print("🔍 URLError Code: \(urlError.code.rawValue)")
|
||
print("🔍 URLError Localized: \(urlError.localizedDescription)")
|
||
|
||
// 详细的网络错误分析
|
||
switch urlError.code {
|
||
case .timedOut:
|
||
print("💡 建议:检查网络连接或增加超时时间")
|
||
case .notConnectedToInternet:
|
||
print("💡 建议:检查网络连接")
|
||
case .cannotConnectToHost:
|
||
print("💡 建议:检查服务器地址和端口")
|
||
case .resourceUnavailable:
|
||
print("💡 建议:检查 API 端点是否正确")
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
print("🔍 Full Error: \(error)")
|
||
}
|
||
|
||
print("=====================================\n")
|
||
}
|
||
|
||
// MARK: - Decoded Response Logging
|
||
static func logDecodedResponse<T>(_ response: T, type: T.Type) {
|
||
#if DEBUG
|
||
guard logLevel == .detailed else { return }
|
||
#else
|
||
return
|
||
#endif
|
||
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
print("🎯 [Decoded Response] [\(timestamp)] Type: \(type)")
|
||
print("=====================================\n")
|
||
}
|
||
|
||
// MARK: - Helper Methods
|
||
private static func formatBytes(_ bytes: Int) -> String {
|
||
let formatter = ByteCountFormatter()
|
||
formatter.allowedUnits = [.useKB, .useMB]
|
||
formatter.countStyle = .file
|
||
return formatter.string(fromByteCount: Int64(bytes))
|
||
}
|
||
|
||
// MARK: - Performance Logging
|
||
static func logPerformanceWarning(duration: TimeInterval, threshold: TimeInterval = 5.0) {
|
||
#if DEBUG
|
||
guard logLevel != .none && duration > threshold else { return }
|
||
#else
|
||
return
|
||
#endif
|
||
|
||
let timestamp = dateFormatter.string(from: Date())
|
||
print("\n⚠️ [Performance Warning] [\(timestamp)] ============")
|
||
print("🐌 Request took \(String(format: "%.3f", duration))s (threshold: \(threshold)s)")
|
||
print("💡 建议:检查网络条件或优化 API 响应")
|
||
print("================================================\n")
|
||
}
|
||
}
|