feat: 重写 SDK 支持 iOS 12.2,移除 async/await 和 Combine

- 所有公开 API 改为 completion handler (Result<T, Error>)
- 状态通知改为 DuooomiBleSDKDelegate 协议
- 移除 Sendable、@MainActor、AsyncStream、CheckedContinuation
- 网络请求改为 URLSession.dataTask 回调
- BLE 通信改为 delegate + 闭包模式
- deployment target 降至 iOS 12.2

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-04-16 16:59:39 +08:00
parent 79af43cdef
commit 1a7bbdc625
26 changed files with 3211 additions and 944 deletions

View File

@@ -10,7 +10,7 @@ final class AniConverter {
private let config: DuooomiBleConfig
private var endpoint: URL {
config.apiHost.appendingPathComponent("api/auth/loomart/file/convert-to-ani")
URL(string: "\(config.apiHost)/api/auth/loomart/file/convert-to-ani")!
}
private let session: URLSession = {
@@ -26,15 +26,23 @@ final class AniConverter {
}
/// ANI ani CDN URL
func convert(fileUrl: String) async throws -> String {
do {
return try await performConvert(fileUrl: fileUrl)
} catch let error as NSError where error.code == NSURLErrorNetworkConnectionLost {
return try await performConvert(fileUrl: fileUrl)
func convert(fileUrl: String, completion: @escaping (Result<String, Error>) -> Void) {
performConvert(fileUrl: fileUrl) { result in
switch result {
case .success:
completion(result)
case .failure(let error):
// Retry once on connection lost
if (error as NSError).code == NSURLErrorNetworkConnectionLost {
self.performConvert(fileUrl: fileUrl, completion: completion)
} else {
completion(.failure(error))
}
}
}
}
private func performConvert(fileUrl: String) async throws -> String {
private func performConvert(fileUrl: String, completion: @escaping (Result<String, Error>) -> Void) {
var request = URLRequest(url: endpoint)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "content-type")
@@ -47,32 +55,49 @@ final class AniConverter {
"height": config.aniHeight,
"fps": config.aniFps,
]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, response) = try await session.data(for: request)
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
throw AniConverterError(message: "HTTP \(http.statusCode)")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
} catch {
completion(.failure(error))
return
}
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw AniConverterError(message: "Invalid JSON response")
}
session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard (json["success"] as? Bool) == true,
let outer = json["data"] as? [String: Any] else {
throw AniConverterError(message: "ANI API reported failure")
}
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
completion(.failure(AniConverterError(message: "HTTP \(http.statusCode)")))
return
}
if (outer["status"] as? Bool) != true {
let msg = (outer["msg"] as? String) ?? "ANI conversion failed"
throw AniConverterError(message: msg)
}
guard let data = data,
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
completion(.failure(AniConverterError(message: "Invalid JSON response")))
return
}
guard let aniUrl = outer["data"] as? String, !aniUrl.isEmpty else {
throw AniConverterError(message: "Missing ani url in response")
}
guard (json["success"] as? Bool) == true,
let outer = json["data"] as? [String: Any] else {
completion(.failure(AniConverterError(message: "ANI API reported failure")))
return
}
return aniUrl
if (outer["status"] as? Bool) != true {
let msg = (outer["msg"] as? String) ?? "ANI conversion failed"
completion(.failure(AniConverterError(message: msg)))
return
}
guard let aniUrl = outer["data"] as? String, !aniUrl.isEmpty else {
completion(.failure(AniConverterError(message: "Missing ani url in response")))
return
}
completion(.success(aniUrl))
}.resume()
}
}