|
|
@@ -3,9 +3,8 @@ import StoreKit
|
|
|
import Photos
|
|
|
import UIKit
|
|
|
|
|
|
-public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
-
|
|
|
- var photoClassifier = ClassifyPhoto()
|
|
|
+public class ClassifyPhotoPlugin : NSObject, FlutterPlugin {
|
|
|
+ var photoClassifier = ClassifyPhoto()
|
|
|
|
|
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
|
|
let channel = FlutterMethodChannel(name: "classify_photo", binaryMessenger: registrar.messenger())
|
|
|
@@ -20,7 +19,7 @@ public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
|
|
|
self.getScreenshots(flutterResult: result)
|
|
|
case "getBlurryPhotos":
|
|
|
self.getBlurryPhotos(flutterResult: result)
|
|
|
- case "getPeoplePhotos":
|
|
|
+ case "getPeoplePhotos" :
|
|
|
self.getPeoplePhotos(flutterResult: result)
|
|
|
case "getSimilarPhotos":
|
|
|
self.getSimilarPhotos(flutterResult: result)
|
|
|
@@ -864,99 +863,232 @@ extension ClassifyPhotoPlugin {
|
|
|
// }
|
|
|
|
|
|
private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
|
|
|
+
|
|
|
// 使用与调用者相同的QoS级别
|
|
|
- DispatchQueue.global(qos: .userInitiated).async {
|
|
|
- let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
-
|
|
|
- // 使用原子操作确保线程安全
|
|
|
- let totalSize = Atomic<Int64>(0)
|
|
|
-
|
|
|
- // 创建一个与调用者相同QoS的组
|
|
|
- let processingGroup = DispatchGroup()
|
|
|
-
|
|
|
- // 创建一个与调用者相同QoS的队列,确保所有操作都有明确的QoS
|
|
|
- let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
|
|
|
- qos: .userInitiated,
|
|
|
- attributes: .concurrent)
|
|
|
-
|
|
|
- // 控制并发数量
|
|
|
- let semaphore = DispatchSemaphore(value: 4)
|
|
|
-
|
|
|
- // 分批处理资源
|
|
|
- let batchSize = 20
|
|
|
- let totalCount = assets.count
|
|
|
-
|
|
|
- // 如果没有资产,直接返回
|
|
|
- if totalCount == 0 {
|
|
|
- DispatchQueue.main.async {
|
|
|
- completion(0)
|
|
|
+ DispatchQueue.global(qos: .userInitiated).async {
|
|
|
+ let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
+
|
|
|
+ // 将 PHFetchResult 转换为数组
|
|
|
+ var assetArray: [PHAsset] = []
|
|
|
+ assets.enumerateObjects { (asset, _, _) in
|
|
|
+ assetArray.append(asset)
|
|
|
}
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
|
|
|
- let end = min(batchStart + batchSize, totalCount)
|
|
|
|
|
|
- processingGroup.enter()
|
|
|
- // 确保使用明确QoS的队列
|
|
|
- processingQueue.async {
|
|
|
- autoreleasepool {
|
|
|
- var batchSize: Int64 = 0
|
|
|
-
|
|
|
- for i in batchStart..<end {
|
|
|
- // 使用带超时的等待,避免无限期阻塞
|
|
|
- // 重要:在同一个队列中等待和信号,避免优先级反转
|
|
|
- let waitResult = semaphore.wait(timeout: .now() + 5)
|
|
|
-
|
|
|
- defer {
|
|
|
- // 确保信号量总是被释放
|
|
|
- if waitResult != .timedOut {
|
|
|
- semaphore.signal()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果等待超时,跳过当前资源
|
|
|
- if waitResult == .timedOut {
|
|
|
- print("警告: 等待资源超时,跳过资源")
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- let asset = assets.object(at: i)
|
|
|
-
|
|
|
- // 使用资源管理器获取大小
|
|
|
- PHAssetResource.assetResources(for: asset).forEach { resource in
|
|
|
- if let fileSize = resource.value(forKey: "fileSize") as? CLong {
|
|
|
- batchSize += Int64(fileSize)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 更新总大小
|
|
|
- totalSize.mutate { $0 += batchSize }
|
|
|
+ // 使用 ClassifyPhoto 中的方法计算大小
|
|
|
+ self.photoClassifier.calculateAssetsSize(assetArray) { sizeInfo in
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ // 返回总大小
|
|
|
+ completion(sizeInfo.totalSize)
|
|
|
}
|
|
|
-
|
|
|
- processingGroup.leave()
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // 使用带超时的等待,避免无限期阻塞
|
|
|
- let waitResult = processingGroup.wait(timeout: .now() + 30)
|
|
|
-
|
|
|
- // 返回结果到主线程
|
|
|
- DispatchQueue.main.async {
|
|
|
- if waitResult == .timedOut {
|
|
|
- print("警告: 处理照片大小超时")
|
|
|
- completion(FlutterError(code: "TIMEOUT",
|
|
|
- message: "计算照片大小超时",
|
|
|
- details: nil))
|
|
|
- } else {
|
|
|
- completion(totalSize.value)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+// // 使用与调用者相同的QoS级别
|
|
|
+// DispatchQueue.global(qos: .userInitiated).async {
|
|
|
+// let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
+//
|
|
|
+// // 如果没有资产,直接返回
|
|
|
+// if assets.count == 0 {
|
|
|
+// DispatchQueue.main.async {
|
|
|
+// completion(0)
|
|
|
+// }
|
|
|
+// return
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 使用原子操作确保线程安全
|
|
|
+// let totalSize = Atomic<Int64>(0)
|
|
|
+// let processedCount = Atomic<Int>(0)
|
|
|
+//
|
|
|
+// // 创建一个与调用者相同QoS的组
|
|
|
+// let processingGroup = DispatchGroup()
|
|
|
+//
|
|
|
+// // 创建一个与调用者相同QoS的队列
|
|
|
+// let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
|
|
|
+// qos: .userInitiated,
|
|
|
+// attributes: .concurrent)
|
|
|
+//
|
|
|
+// // 遍历所有资产
|
|
|
+// for i in 0..<assets.count {
|
|
|
+// processingGroup.enter()
|
|
|
+//
|
|
|
+// // 使用DispatchWorkItem明确指定QoS
|
|
|
+// let workItem = DispatchWorkItem(qos: .userInitiated) {
|
|
|
+// let asset = assets.object(at: i)
|
|
|
+//
|
|
|
+// // 获取资源
|
|
|
+// let resources = PHAssetResource.assetResources(for: asset)
|
|
|
+// var assetSize: Int64 = 0
|
|
|
+//
|
|
|
+// // 计算资源大小
|
|
|
+// for resource in resources {
|
|
|
+// if let fileSize = resource.value(forKey: "fileSize") as? CLong {
|
|
|
+// assetSize += Int64(fileSize)
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 更新总大小
|
|
|
+// totalSize.mutate { $0 += assetSize }
|
|
|
+//
|
|
|
+// // 更新处理计数
|
|
|
+// processedCount.mutate { $0 += 1 }
|
|
|
+//
|
|
|
+// processingGroup.leave()
|
|
|
+// }
|
|
|
+//
|
|
|
+// let workItem = DispatchWorkItem(qos: .userInitiated) {
|
|
|
+// let asset = assets.object(at: i)
|
|
|
+// var assetSize: Int64 = 0
|
|
|
+//
|
|
|
+// // 获取所有相关资源
|
|
|
+// let resources = PHAssetResource.assetResources(for: asset)
|
|
|
+//
|
|
|
+// // 对每个资源使用PHAssetResourceManager获取准确大小
|
|
|
+// let resourceManager = PHAssetResourceManager.default()
|
|
|
+// let resourcesGroup = DispatchGroup()
|
|
|
+//
|
|
|
+// for resource in resources {
|
|
|
+// resourcesGroup.enter()
|
|
|
+//
|
|
|
+// // 尝试获取准确的文件大小
|
|
|
+// let options = PHAssetResourceRequestOptions()
|
|
|
+// options.isNetworkAccessAllowed = true
|
|
|
+//
|
|
|
+// resourceManager.requestDataForAssetResource(resource, options: options) { (data, _, _) in
|
|
|
+// if let data = data {
|
|
|
+// assetSize += Int64(data.count)
|
|
|
+// } else if let fileSize = resource.value(forKey: "fileSize") as? CLong {
|
|
|
+// // 回退到元数据中的大小
|
|
|
+// assetSize += Int64(fileSize)
|
|
|
+// }
|
|
|
+// resourcesGroup.leave()
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 等待所有资源处理完成
|
|
|
+// resourcesGroup.wait()
|
|
|
+//
|
|
|
+// // 更新总大小
|
|
|
+// totalSize.mutate { $0 += assetSize }
|
|
|
+// processedCount.mutate { $0 += 1 }
|
|
|
+//
|
|
|
+// processingGroup.leave()
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 在指定QoS的队列上执行任务
|
|
|
+// processingQueue.async(execute: workItem)
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 使用带超时的等待,避免无限期阻塞
|
|
|
+// let waitResult = processingGroup.wait(timeout: .now() + 30)
|
|
|
+//
|
|
|
+// // 返回结果到主线程
|
|
|
+// DispatchQueue.main.async {
|
|
|
+// if waitResult == .timedOut {
|
|
|
+// print("警告: 处理照片大小超时,已处理 \(processedCount.value)/\(assets.count) 个资源")
|
|
|
+// completion(FlutterError(code: "TIMEOUT",
|
|
|
+// message: "计算照片大小超时",
|
|
|
+// details: nil))
|
|
|
+// } else {
|
|
|
+// completion(totalSize.value)
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
}
|
|
|
|
|
|
// private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
|
|
|
+// // 使用与调用者相同的QoS级别
|
|
|
+// DispatchQueue.global(qos: .userInitiated).async {
|
|
|
+// let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|
|
|
+//
|
|
|
+// // 使用原子操作确保线程安全
|
|
|
+// let totalSize = Atomic<Int64>(0)
|
|
|
+//
|
|
|
+// // 创建一个与调用者相同QoS的组
|
|
|
+// let processingGroup = DispatchGroup()
|
|
|
+//
|
|
|
+// // 创建一个与调用者相同QoS的队列,确保所有操作都有明确的QoS
|
|
|
+// let processingQueue = DispatchQueue(label: "com.yourapp.photosize.processing",
|
|
|
+// qos: .userInitiated,
|
|
|
+// attributes: .concurrent)
|
|
|
+//
|
|
|
+// // 控制并发数量
|
|
|
+// let semaphore = DispatchSemaphore(value: 4)
|
|
|
+//
|
|
|
+// // 分批处理资源
|
|
|
+// let batchSize = 20
|
|
|
+// let totalCount = assets.count
|
|
|
+//
|
|
|
+// // 如果没有资产,直接返回
|
|
|
+// if totalCount == 0 {
|
|
|
+// DispatchQueue.main.async {
|
|
|
+// completion(0)
|
|
|
+// }
|
|
|
+// return
|
|
|
+// }
|
|
|
+//
|
|
|
+// for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
|
|
|
+// let end = min(batchStart + batchSize, totalCount)
|
|
|
+//
|
|
|
+// processingGroup.enter()
|
|
|
+// // 确保使用明确QoS的队列
|
|
|
+// processingQueue.async {
|
|
|
+// autoreleasepool {
|
|
|
+// var batchSize: Int64 = 0
|
|
|
+//
|
|
|
+// for i in batchStart..<end {
|
|
|
+// // 使用带超时的等待,避免无限期阻塞
|
|
|
+// // 重要:在同一个队列中等待和信号,避免优先级反转
|
|
|
+// let waitResult = semaphore.wait(timeout: .now() + 5)
|
|
|
+//
|
|
|
+// defer {
|
|
|
+// // 确保信号量总是被释放
|
|
|
+// if waitResult != .timedOut {
|
|
|
+// semaphore.signal()
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 如果等待超时,跳过当前资源
|
|
|
+// if waitResult == .timedOut {
|
|
|
+// print("警告: 等待资源超时,跳过资源")
|
|
|
+// continue
|
|
|
+// }
|
|
|
+//
|
|
|
+// let asset = assets.object(at: i)
|
|
|
+//
|
|
|
+// // 使用资源管理器获取大小
|
|
|
+// PHAssetResource.assetResources(for: asset).forEach { resource in
|
|
|
+// if let fileSize = resource.value(forKey: "fileSize") as? CLong {
|
|
|
+// batchSize += Int64(fileSize)
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 更新总大小
|
|
|
+// totalSize.mutate { $0 += batchSize }
|
|
|
+// }
|
|
|
+//
|
|
|
+// processingGroup.leave()
|
|
|
+// }
|
|
|
+// }
|
|
|
+//
|
|
|
+// // 使用带超时的等待,避免无限期阻塞
|
|
|
+// let waitResult = processingGroup.wait(timeout: .now() + 30)
|
|
|
+//
|
|
|
+// // 返回结果到主线程
|
|
|
+// DispatchQueue.main.async {
|
|
|
+// if waitResult == .timedOut {
|
|
|
+// print("警告: 处理照片大小超时")
|
|
|
+// completion(FlutterError(code: "TIMEOUT",
|
|
|
+// message: "计算照片大小超时",
|
|
|
+// details: nil))
|
|
|
+// } else {
|
|
|
+// completion(totalSize.value)
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+// private func calculatePhotosSize(assetIds: [String], completion: @escaping FlutterResult) {
|
|
|
// // 使用与调用者相同的QoS级别,避免优先级反转
|
|
|
// DispatchQueue.global(qos: .userInitiated).async {
|
|
|
// let assets = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
|