feat: 上传进度
This commit is contained in:
parent
f40828484c
commit
84db43f3b5
Binary file not shown.
@ -1,52 +1,60 @@
|
||||
import SwiftUI
|
||||
import PhotosUI
|
||||
|
||||
/// 上传结果,包含原图和压缩图的上传信息
|
||||
struct UploadResults {
|
||||
let original: ImageUploaderGetID.UploadResult
|
||||
let compressed: ImageUploaderGetID.UploadResult
|
||||
// MARK: - Data Models
|
||||
|
||||
/// 上传进度信息
|
||||
public struct UploadProgress {
|
||||
public let current: Int
|
||||
public let total: Int
|
||||
public let progress: Double
|
||||
public let isOriginal: Bool
|
||||
|
||||
public init(current: Int, total: Int, progress: Double, isOriginal: Bool) {
|
||||
self.current = current
|
||||
self.total = total
|
||||
self.progress = progress
|
||||
self.isOriginal = isOriginal
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传结果,包含原图和压缩图的上传信息
|
||||
public struct UploadResults {
|
||||
public let original: ImageUploaderGetID.UploadResult
|
||||
public let compressed: ImageUploaderGetID.UploadResult
|
||||
|
||||
public init(original: ImageUploaderGetID.UploadResult,
|
||||
compressed: ImageUploaderGetID.UploadResult) {
|
||||
self.original = original
|
||||
self.compressed = compressed
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Photo Picker
|
||||
|
||||
/// 照片选择器,封装了系统相册选择功能
|
||||
/// 使用UIViewControllerRepresentable包装PHPickerViewController,提供SwiftUI兼容的图片选择界面
|
||||
struct PhotoPicker: UIViewControllerRepresentable {
|
||||
// MARK: - Properties
|
||||
|
||||
/// 绑定的已选图片数组,用于存储用户选择的图片
|
||||
@Binding var selectedImages: [UIImage]
|
||||
|
||||
/// 最多可选图片数量,默认为1
|
||||
let selectionLimit: Int
|
||||
|
||||
/// 图片过滤器,默认为图片类型,可过滤特定类型的媒体
|
||||
let filter: PHPickerFilter
|
||||
|
||||
/// 图片上传完成回调,返回上传结果或错误
|
||||
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
|
||||
var onUploadProgress: ((UploadProgress) -> Void)?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// 初始化照片选择器
|
||||
/// - Parameters:
|
||||
/// - selectedImages: 绑定的图片数组,用于接收用户选择的图片
|
||||
/// - selectionLimit: 最多可选图片数量,默认为1
|
||||
/// - filter: 媒体类型过滤器,默认为图片
|
||||
/// - onImageUploaded: 图片上传完成后的回调闭包
|
||||
init(
|
||||
selectedImages: Binding<[UIImage]>,
|
||||
selectionLimit: Int = 1,
|
||||
filter: PHPickerFilter = .images,
|
||||
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil
|
||||
) {
|
||||
init(selectedImages: Binding<[UIImage]>,
|
||||
selectionLimit: Int = 1,
|
||||
filter: PHPickerFilter = .images,
|
||||
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil,
|
||||
onUploadProgress: ((UploadProgress) -> Void)? = nil) {
|
||||
self._selectedImages = selectedImages
|
||||
self.selectionLimit = selectionLimit
|
||||
self.filter = filter
|
||||
self.onImageUploaded = onImageUploaded
|
||||
self.onUploadProgress = onUploadProgress
|
||||
}
|
||||
|
||||
// MARK: - UIViewControllerRepresentable
|
||||
|
||||
/// 创建并返回配置好的PHPickerViewController
|
||||
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||||
configuration.filter = filter
|
||||
@ -58,42 +66,30 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
||||
return picker
|
||||
}
|
||||
|
||||
/// 更新视图控制器(空实现,因为不需要更新)
|
||||
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
|
||||
|
||||
/// 创建协调器,用于处理PHPickerViewController的代理方法
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
/// 协调器类,处理PHPickerViewController的代理方法
|
||||
class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
||||
/// 对父视图的弱引用
|
||||
let parent: PhotoPicker
|
||||
|
||||
/// 图片上传器实例
|
||||
private let uploader = ImageUploaderGetID()
|
||||
|
||||
init(_ parent: PhotoPicker) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
/// 当用户完成图片选择时调用
|
||||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||||
// 清空已选图片
|
||||
parent.selectedImages.removeAll()
|
||||
|
||||
// 使用DispatchGroup管理多个异步任务
|
||||
let group = DispatchGroup()
|
||||
var loadedImages: [Int: UIImage] = [:] // 用于保持图片顺序的字典
|
||||
var loadedImages: [Int: UIImage] = [:]
|
||||
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
|
||||
compressed: ImageUploaderGetID.UploadResult?)] = [:]
|
||||
compressed: ImageUploaderGetID.UploadResult?)] = [:]
|
||||
|
||||
// 遍历所有选中的图片
|
||||
for (index, result) in results.enumerated() {
|
||||
group.enter() // 进入组
|
||||
group.enter()
|
||||
|
||||
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||||
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
|
||||
@ -102,134 +98,138 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. 保存原始图片
|
||||
loadedImages[index] = image
|
||||
|
||||
// 2. 压缩图片(质量压缩到50%)
|
||||
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 上传原图
|
||||
self.uploader.uploadImage(image) { [weak self] originalResult in
|
||||
guard let self = self else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
|
||||
switch originalResult {
|
||||
case .success(let originalUploadResult):
|
||||
// 4. 原图上传成功后上传压缩图
|
||||
self.uploader.uploadImage(compressedImage) { compressedResult in
|
||||
defer { group.leave() }
|
||||
|
||||
switch compressedResult {
|
||||
case .success(let compressedUploadResult):
|
||||
// 保存两个上传结果
|
||||
uploadResults[index] = (originalUploadResult, compressedUploadResult)
|
||||
print("✅ 原图和压缩图上传成功!")
|
||||
print("📂 原图信息:")
|
||||
print(" - 文件ID: \(originalUploadResult.fileId)")
|
||||
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
|
||||
print("📦 压缩图信息:")
|
||||
print(" - 文件ID: \(compressedUploadResult.fileId)")
|
||||
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
|
||||
|
||||
// 使用MaterialService上传文件信息到后端
|
||||
MaterialService.shared.uploadMaterialInfo(
|
||||
fileId: originalUploadResult.fileId,
|
||||
previewFileId: compressedUploadResult.fileId
|
||||
) { success, errorMessage in
|
||||
if success {
|
||||
print("✅ 文件信息上传成功 素材上传成功!!!!!")
|
||||
} else if let errorMessage = errorMessage {
|
||||
print("❌ 文件信息上传失败: \(errorMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 压缩图上传失败: \(error.localizedDescription)")
|
||||
uploadResults[index] = (originalUploadResult, nil)
|
||||
}
|
||||
self.uploader.uploadImage(
|
||||
image,
|
||||
progress: { [weak self] progress in
|
||||
let progressInfo = UploadProgress(
|
||||
current: Int(progress * 100),
|
||||
total: 100,
|
||||
progress: progress,
|
||||
isOriginal: true
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
self?.parent.onUploadProgress?(progressInfo)
|
||||
}
|
||||
print("📤 原图上传进度: \(Int(progress * 100))%")
|
||||
},
|
||||
completion: { [weak self] originalResult in
|
||||
guard let self = self else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 原图上传失败: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
switch originalResult {
|
||||
case .success(let originalUploadResult):
|
||||
self.uploader.uploadImage(
|
||||
compressedImage,
|
||||
progress: { [weak self] progress in
|
||||
let progressInfo = UploadProgress(
|
||||
current: Int(progress * 100),
|
||||
total: 100,
|
||||
progress: progress,
|
||||
isOriginal: false
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
self?.parent.onUploadProgress?(progressInfo)
|
||||
}
|
||||
print("📊 压缩图上传进度: \(Int(progress * 100))%")
|
||||
},
|
||||
completion: { compressedResult in
|
||||
defer { group.leave() }
|
||||
|
||||
switch compressedResult {
|
||||
case .success(let compressedUploadResult):
|
||||
uploadResults[index] = (originalUploadResult, compressedUploadResult)
|
||||
print("✅ 原图和压缩图上传成功!")
|
||||
print("📂 原图信息:")
|
||||
print(" - 文件ID: \(originalUploadResult.fileId)")
|
||||
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
|
||||
print("📦 压缩图信息:")
|
||||
print(" - 文件ID: \(compressedUploadResult.fileId)")
|
||||
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
|
||||
|
||||
MaterialService.shared.uploadMaterialInfo(
|
||||
fileId: originalUploadResult.fileId,
|
||||
previewFileId: compressedUploadResult.fileId
|
||||
) { success, errorMessage in
|
||||
if success {
|
||||
print("✅ 文件信息上传成功 素材上传成功!!!!!")
|
||||
} else if let errorMessage = errorMessage {
|
||||
print("❌ 文件信息上传失败: \(errorMessage)")
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 压缩图上传失败: \(error.localizedDescription)")
|
||||
uploadResults[index] = (originalUploadResult, nil)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 原图上传失败: \(error.localizedDescription)")
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
// 所有上传任务完成后的处理
|
||||
group.notify(queue: .main) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// 1. 更新选中的图片(只显示原图)
|
||||
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
|
||||
self.parent.selectedImages.append(contentsOf: sortedImages)
|
||||
|
||||
// 2. 检查是否所有上传都成功
|
||||
if let firstResult = uploadResults.first?.value,
|
||||
let original = firstResult.original,
|
||||
let compressed = firstResult.compressed {
|
||||
// 3. 如果成功,返回上传结果
|
||||
let results = UploadResults(original: original, compressed: compressed)
|
||||
self.parent.onImageUploaded?(.success(results))
|
||||
} else {
|
||||
// 4. 如果失败,返回错误
|
||||
self.parent.onImageUploaded?(.failure(NSError(domain: "com.wake.upload",
|
||||
code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"])))
|
||||
self.parent.onImageUploaded?(.failure(NSError(
|
||||
domain: "com.wake.upload",
|
||||
code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
|
||||
)))
|
||||
}
|
||||
|
||||
// 5. 关闭图片选择器
|
||||
picker.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AvatarUploader
|
||||
// MARK: - Avatar Uploader
|
||||
|
||||
/// 头像上传视图,提供头像选择功能
|
||||
/// 封装了头像显示和选择逻辑,支持点击选择新头像
|
||||
struct AvatarUploader: View {
|
||||
// MARK: - Properties
|
||||
|
||||
/// 绑定的当前选中头像图片
|
||||
@Binding var selectedImage: UIImage?
|
||||
|
||||
/// 头像显示尺寸
|
||||
let size: CGFloat
|
||||
|
||||
/// 上传完成回调,返回上传结果或错误
|
||||
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
|
||||
|
||||
// MARK: - State
|
||||
|
||||
/// 控制图片选择器的显示状态
|
||||
@State private var isImagePickerPresented = false
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
// 头像按钮,点击后显示图片选择器
|
||||
Button(action: { isImagePickerPresented = true }) {
|
||||
ZStack {
|
||||
if let selectedImage = selectedImage {
|
||||
// 显示已选中的头像
|
||||
Image(uiImage: selectedImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: size, height: size)
|
||||
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
|
||||
} else {
|
||||
// 默认头像占位视图
|
||||
Color.gray.opacity(0.1)
|
||||
.frame(width: size, height: size)
|
||||
.overlay(
|
||||
@ -244,11 +244,10 @@ struct AvatarUploader: View {
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.contentShape(Rectangle()) // 确保整个区域都可点击
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 使用无样式按钮
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.sheet(isPresented: $isImagePickerPresented) {
|
||||
// 显示图片选择器
|
||||
PhotoPicker(
|
||||
selectedImages: Binding(
|
||||
get: { [selectedImage].compactMap { $0 } },
|
||||
@ -256,10 +255,12 @@ struct AvatarUploader: View {
|
||||
selectedImage = images.first
|
||||
}
|
||||
),
|
||||
selectionLimit: 1, // 限制只能选择一张图片
|
||||
selectionLimit: 1,
|
||||
onImageUploaded: { result in
|
||||
// 图片上传完成后的处理
|
||||
onUploadComplete?(result)
|
||||
},
|
||||
onUploadProgress: { progress in
|
||||
print("上传进度:\(progress.current)/\(progress.total),进度:\(progress.progress * 100)%")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -69,8 +69,13 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
/// 上传图片到服务器
|
||||
/// - Parameters:
|
||||
/// - image: 要上传的图片
|
||||
/// - completion: 完成回调,返回Result类型的结果
|
||||
public func uploadImage(_ image: UIImage, completion: @escaping (Result<UploadResult, Error>) -> Void) {
|
||||
/// - progress: 上传进度回调 (0.0 到 1.0)
|
||||
/// - completion: 完成回调
|
||||
public func uploadImage(
|
||||
_ image: UIImage,
|
||||
progress: @escaping (Double) -> Void,
|
||||
completion: @escaping (Result<UploadResult, Error>) -> Void
|
||||
) {
|
||||
print("🔄 开始准备上传图片...")
|
||||
|
||||
// 1. 转换图片为Data
|
||||
@ -85,11 +90,37 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
getUploadURL(for: imageData) { [weak self] result in
|
||||
switch result {
|
||||
case .success((let fileId, let uploadURL)):
|
||||
// 3. 确认上传
|
||||
self?.confirmUpload(fileId: fileId, fileName: "avatar_\(UUID().uuidString).jpg", fileSize: imageData.count) { confirmResult in
|
||||
completion(confirmResult)
|
||||
}
|
||||
print("📤 获取到上传URL,开始上传文件...")
|
||||
|
||||
// 3. 上传文件
|
||||
_ = self?.uploadFile(
|
||||
fileData: imageData,
|
||||
to: uploadURL,
|
||||
mimeType: "image/jpeg",
|
||||
onProgress: { uploadProgress in
|
||||
print("📊 上传进度: \(Int(uploadProgress * 100))%")
|
||||
progress(uploadProgress)
|
||||
},
|
||||
completion: { uploadResult in
|
||||
switch uploadResult {
|
||||
case .success:
|
||||
// 4. 确认上传
|
||||
self?.confirmUpload(
|
||||
fileId: fileId,
|
||||
fileName: "avatar_\(UUID().uuidString).jpg",
|
||||
fileSize: imageData.count,
|
||||
completion: completion
|
||||
)
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 文件上传失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 获取上传URL失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
@ -211,6 +242,182 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
||||
/// 上传文件到指定URL
|
||||
/// - Parameters:
|
||||
/// - fileData: 要上传的文件数据
|
||||
/// - uploadURL: 上传URL
|
||||
/// - mimeType: 文件MIME类型
|
||||
/// - onProgress: 进度回调,0.0 到 1.0
|
||||
/// - completion: 完成回调
|
||||
public func uploadFile(
|
||||
fileData: Data,
|
||||
to uploadURL: URL,
|
||||
mimeType: String = "application/octet-stream",
|
||||
onProgress: @escaping (Double) -> Void,
|
||||
completion: @escaping (Result<Void, Error>) -> Void
|
||||
) -> URLSessionUploadTask {
|
||||
var request = URLRequest(url: uploadURL)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue(mimeType, forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let task = session.uploadTask(
|
||||
with: request,
|
||||
from: fileData
|
||||
) { _, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
(200...299).contains(httpResponse.statusCode) else {
|
||||
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
|
||||
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
|
||||
return
|
||||
}
|
||||
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
// 添加进度观察
|
||||
if #available(iOS 11.0, *) {
|
||||
let progressObserver = task.progress.observe(\.fractionCompleted) { (progressValue, _) in
|
||||
DispatchQueue.main.async {
|
||||
onProgress(progressValue.fractionCompleted)
|
||||
}
|
||||
}
|
||||
|
||||
task.addCompletionHandler { [weak task] in
|
||||
progressObserver.invalidate()
|
||||
task?.progress.cancel()
|
||||
}
|
||||
} else {
|
||||
// Fallback for earlier iOS versions
|
||||
var lastProgress: Double = 0
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
let bytesSent = task.countOfBytesSent
|
||||
let totalBytes = task.countOfBytesExpectedToSend
|
||||
let currentProgress = totalBytes > 0 ? Double(bytesSent) / Double(totalBytes) : 0
|
||||
|
||||
// 只有当进度有显著变化时才回调,避免频繁更新UI
|
||||
if abs(currentProgress - lastProgress) > 0.01 || currentProgress >= 1.0 {
|
||||
lastProgress = currentProgress
|
||||
DispatchQueue.main.async {
|
||||
onProgress(min(currentProgress, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
if currentProgress >= 1.0 {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
task.addCompletionHandler {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
|
||||
// MARK: - 文件上传状态
|
||||
|
||||
/// 文件上传状态
|
||||
public struct FileStatus {
|
||||
public let file: Data
|
||||
public var status: UploadStatus
|
||||
public var progress: Double
|
||||
|
||||
public enum UploadStatus {
|
||||
case pending
|
||||
case uploading
|
||||
case completed
|
||||
case failed(Error)
|
||||
}
|
||||
|
||||
public init(file: Data, status: UploadStatus = .pending, progress: Double = 0) {
|
||||
self.file = file
|
||||
self.status = status
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - URLSessionTask 扩展
|
||||
|
||||
private class TaskObserver: NSObject {
|
||||
private weak var task: URLSessionTask?
|
||||
private var handlers: [() -> Void] = []
|
||||
|
||||
init(task: URLSessionTask) {
|
||||
self.task = task
|
||||
super.init()
|
||||
task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.state), options: .new, context: nil)
|
||||
}
|
||||
|
||||
func addHandler(_ handler: @escaping () -> Void) {
|
||||
handlers.append(handler)
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
guard keyPath == #keyPath(URLSessionTask.state),
|
||||
let task = task,
|
||||
task.state == .completed else {
|
||||
return
|
||||
}
|
||||
|
||||
// 调用所有完成处理器
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.handlers.forEach { $0() }
|
||||
self?.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanup() {
|
||||
task?.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.state))
|
||||
handlers.removeAll()
|
||||
}
|
||||
|
||||
deinit {
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
private extension URLSessionTask {
|
||||
private static var taskObserverKey: UInt8 = 0
|
||||
|
||||
private var taskObserver: TaskObserver? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &Self.taskObserverKey) as? TaskObserver
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &Self.taskObserverKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
func addCompletionHandler(_ handler: @escaping () -> Void) {
|
||||
if #available(iOS 11.0, *) {
|
||||
if let observer = taskObserver {
|
||||
observer.addHandler(handler)
|
||||
} else {
|
||||
let observer = TaskObserver(task: self)
|
||||
observer.addHandler(handler)
|
||||
taskObserver = observer
|
||||
}
|
||||
} else {
|
||||
// iOS 11 以下版本使用通知
|
||||
let name = NSNotification.Name("TaskCompleted\(self.taskIdentifier)")
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: name,
|
||||
object: self,
|
||||
queue: .main
|
||||
) { _ in
|
||||
handler()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 响应模型
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user