| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 |
- import Flutter
- import StoreKit
- import Photos
- import UIKit
- public class ClassifyPhotoPlugin: NSObject, FlutterPlugin {
- var photoClassifier = ClassifyPhoto()
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "classify_photo", binaryMessenger: registrar.messenger())
- let instance = ClassifyPhotoPlugin()
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- print("iOS: Received method call: \(call.method)")
- switch call.method {
- case "getScreenshots":
- self.getScreenshots(flutterResult: result)
- case "getBlurryPhotos":
- self.getBlurryPhotos(flutterResult: result)
- case "getPeoplePhotos":
- self.getPeoplePhotos(flutterResult: result)
- case "getSimilarPhotos":
- self.getSimilarPhotos(flutterResult: result)
- case "getPhoto":
- self.getPhoto(flutterResult: result)
- case "getStorageInfo":
- getStorageInfo(result: result)
- case "getExifInfo":
- guard let args = call.arguments as? [String: Any],
- let filePath = args["filePath"] as? String else {
- result(FlutterError(
- code: "INVALID_ARGUMENTS",
- message: "Missing filePath parameter",
- details: nil
- ))
- return
- }
- getExifInfo(filePath: filePath, completion: result)
- case "getPhotosSize":
- guard let args = call.arguments as? [String: Any],
- let assetIds = args["assetIds"] as? [String] else {
- result(FlutterError(
- code: "INVALID_ARGUMENTS",
- message: "Missing filePath parameter",
- details: nil
- ))
- return
- }
- calculatePhotosSize(assetIds: assetIds, completion: result)
- case "getPlatformVersion":
- result("iOS " + UIDevice.current.systemVersion)
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- private class func blankof<T>(type:T.Type) -> T {
- let ptr = UnsafeMutablePointer<T>.allocate(capacity: MemoryLayout<T>.size)
- let val = ptr.pointee
- return val
- }
-
- /// 磁盘总大小
- private class func getTotalDiskSize() -> Int64 {
- var fs = blankof(type: statfs.self)
- if statfs("/var",&fs) >= 0{
- return Int64(UInt64(fs.f_bsize) * fs.f_blocks)
- }
- return -1
- }
-
- private func getStorageInfo(result: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async {
- var storageInfo: [String: Int64] = [:]
- // 获取总容量和可用容量
- if let space = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory()) {
- let totalSpace = ClassifyPhotoPlugin.getTotalDiskSize()
- let freeSpace = space[.systemFreeSize] as? Int64 ?? 0
- storageInfo["totalSpace"] = totalSpace
- storageInfo["freeSpace"] = freeSpace
- storageInfo["usedSpace"] = totalSpace - freeSpace
- }
- // 获取照片占用的空间
- let options = PHFetchOptions()
- let allPhotos = PHAsset.fetchAssets(with: .image, options: options)
- var photoSize: Int64 = 0
- let group = DispatchGroup()
- let queue = DispatchQueue(label: "com.app.photosize", attributes: .concurrent)
- let semaphore = DispatchSemaphore(value: 5) // 限制并发
- allPhotos.enumerateObjects { (asset, index, stop) in
- group.enter()
- semaphore.wait()
- queue.async {
- let resources = PHAssetResource.assetResources(for: asset)
- if let resource = resources.first {
- if let unsignedInt64 = resource.value(forKey: "fileSize") as? CLong {
- photoSize += Int64(unsignedInt64)
- }
- }
- semaphore.signal()
- group.leave()
- }
- }
- group.notify(queue: .main) {
- storageInfo["photoSpace"] = photoSize
- result(storageInfo)
- }
- }
- }
- // 获取截图
- private func getScreenshots(flutterResult: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
-
- let fetchOptions = PHFetchOptions()
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
-
- // 只处理截图
- self.photoClassifier.fetchScreenshots(from: allPhotos) { screenshots in
- // 清理内存
- self.cleanupMemory()
-
- self.photoClassifier.calculateAssetsSize(screenshots) { sizeInfo in
- self.processPhotoGroup(
- assets: screenshots,
- groupName: "screenshots",
- sizeInfo: sizeInfo
- ) { groupData in
- // 再次清理内存
- self.cleanupMemory()
-
- DispatchQueue.main.async {
- if !groupData.isEmpty {
- flutterResult([["group": groupData, "type": "screenshots"]])
- } else {
- flutterResult([])
- }
- }
- }
- }
- }
- }
- }
- // 获取模糊照片
- private func getBlurryPhotos(flutterResult: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
-
- let fetchOptions = PHFetchOptions()
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
-
- // 只处理模糊照片
- self.photoClassifier.detectBlurryPhotos(from: allPhotos) { blurryPhotos in
- // 清理内存
- self.cleanupMemory()
-
- self.photoClassifier.calculateAssetsSize(blurryPhotos) { sizeInfo in
- self.processPhotoGroup(
- assets: blurryPhotos,
- groupName: "blurry",
- sizeInfo: sizeInfo
- ) { groupData in
- // 再次清理内存
- self.cleanupMemory()
-
- DispatchQueue.main.async {
- if !groupData.isEmpty {
- flutterResult([["group": groupData, "type": "blurry"]])
- } else {
- flutterResult([])
- }
- }
- }
- }
- }
- }
- }
-
- private func getPeoplePhotos(flutterResult: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
-
- let fetchOptions = PHFetchOptions()
- // 限制处理的照片数量,提高性能
- fetchOptions.fetchLimit = 1000
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
-
- // 显示进度
- DispatchQueue.main.async {
- print("开始处理人物照片,总数: \(allPhotos.count)")
- }
-
- // 只处理人物照片
- self.photoClassifier.classifyByPeople(assets: allPhotos) { peopleGroups in
- // 清理内存
- self.cleanupMemory()
-
- let peopleAssets = Array(peopleGroups.values.flatMap { $0 })
-
- // 显示找到的人脸照片数量
- DispatchQueue.main.async {
- print("找到包含人脸的照片: \(peopleAssets.count)")
- }
-
- self.photoClassifier.calculateAssetsSize(peopleAssets) { sizeInfo in
-
- let resultData = Atomic<[[String: Any]]>([])
-
- // 分批处理人物组,避免一次性处理太多数据
- let processingQueue = DispatchQueue(label: "com.yourapp.peopleProcessing")
- let group = DispatchGroup()
-
- // 处理每个人物组
- for (personName, personPhotos) in peopleGroups {
- if personPhotos.isEmpty { continue }
-
- // 限制每组处理的照片数量
- let limitedPhotos = Array(personPhotos.prefix(500))
-
- group.enter()
- processingQueue.async {
- autoreleasepool {
- self.processPhotoGroup(
- assets: limitedPhotos,
- groupName: personName,
- sizeInfo: sizeInfo
- ) { groupData in
- if !groupData.isEmpty {
- resultData.mutate { $0.append([
- "group": groupData,
- "type": "people",
- "name": personName
- ]) }
- }
- group.leave()
- }
- }
- }
- }
-
- group.notify(queue: .main) {
- // 最终清理内存
- self.cleanupMemory()
- flutterResult(resultData.value)
- }
- }
- }
- }
- }
- // 获取人物照片
- // private func getPeoplePhotos(flutterResult: @escaping FlutterResult) {
- // DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- // guard let self = self else { return }
- //
- // let fetchOptions = PHFetchOptions()
- // let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
- //
- // // 只处理人物照片
- // self.photoClassifier.classifyByPeople(assets: allPhotos) { peopleGroups in
- // // 清理内存
- // self.cleanupMemory()
- //
- // let peopleAssets = Array(peopleGroups.values.flatMap { $0 })
- // self.photoClassifier.calculateAssetsSize(peopleAssets) { sizeInfo in
- //
- // let resultData = Atomic<[[String: Any]]>([])
- // let processingQueue = DispatchQueue(label: "com.yourapp.peopleProcessing")
- // let group = DispatchGroup()
- //
- // // 处理每个人物组
- // for (personName, personPhotos) in peopleGroups {
- // if personPhotos.isEmpty { continue }
- //
- // group.enter()
- // processingQueue.async {
- // autoreleasepool {
- // self.processPhotoGroup(
- // assets: personPhotos,
- // groupName: personName,
- // sizeInfo: sizeInfo
- // ) { groupData in
- // if !groupData.isEmpty {
- // resultData.mutate { $0.append([
- // "group": groupData,
- // "type": "people",
- // "name": personName
- // ]) }
- // }
- // group.leave()
- // }
- // }
- // }
- // }
- //
- // group.notify(queue: .main) {
- // // 最终清理内存
- // self.cleanupMemory()
- // flutterResult(resultData.value)
- // }
- // }
- // }
- // }
- // }
- // 添加内存清理方法
- private func cleanupMemory() {
- // 强制清理内存
- autoreleasepool {
- // 触发内存警告,促使系统回收内存
- UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.beginIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.endIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- }
- }
- // 获取相似照片
- private func getSimilarPhotos(flutterResult: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
-
- let fetchOptions = PHFetchOptions()
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
-
- // 只处理相似照片
- self.photoClassifier.detectSimilarPhotos(
- assets: allPhotos,
- progressHandler: { (stage, progress) in
- print("Similar Photos Progress: \(stage) - \(progress)")
- },
- completion: { similarGroups in
- // 清理内存
- self.cleanupMemory()
-
- // 限制处理的组数,避免内存过载
- let maxGroupsToProcess = min(50, similarGroups.count)
- let limitedGroups = Array(similarGroups.prefix(maxGroupsToProcess))
-
- let similarAssets = Array(limitedGroups.flatMap { $0 })
- self.photoClassifier.calculateAssetsSize(similarAssets) { sizeInfo in
-
- let resultData = Atomic<[[String: Any]]>([])
-
- // 分批处理照片组,每批处理少量组
- let batchSize = 5
- let totalBatches = Int(ceil(Double(limitedGroups.count) / Double(batchSize)))
-
- self.processSimilarPhotoGroupsInBatches(
- groups: limitedGroups,
- batchIndex: 0,
- totalBatches: totalBatches,
- batchSize: batchSize,
- sizeInfo: sizeInfo,
- resultData: resultData
- ) {
- // 所有批次处理完成后的回调
- // 最终清理内存
- self.cleanupMemory()
- flutterResult(resultData.value)
- }
- }
- }
- )
- }
- }
-
- // 添加分批处理照片组的方法
- private func processSimilarPhotoGroupsInBatches(
- groups: [[PHAsset]],
- batchIndex: Int,
- totalBatches: Int,
- batchSize: Int,
- sizeInfo: ClassifyPhoto.PhotoSizeInfo,
- resultData: Atomic<[[String: Any]]>,
- completion: @escaping () -> Void
- ) {
- // 检查是否处理完所有批次
- if batchIndex >= totalBatches {
- completion()
- return
- }
-
- // 计算当前批次的范围
- let startIndex = batchIndex * batchSize
- let endIndex = min(startIndex + batchSize, groups.count)
- let currentBatchGroups = groups[startIndex..<endIndex]
-
- let processingQueue = DispatchQueue(label: "com.yourapp.similarProcessing.batch\(batchIndex)")
- let group = DispatchGroup()
-
- // 处理当前批次的照片组
- for (index, photoGroup) in currentBatchGroups.enumerated() {
- if photoGroup.isEmpty { continue }
-
- group.enter()
- processingQueue.async {
- autoreleasepool {
- self.processPhotoGroup(
- assets: photoGroup,
- groupName: "similar_\(startIndex + index)",
- sizeInfo: sizeInfo
- ) { groupData in
- if !groupData.isEmpty {
- resultData.mutate { $0.append([
- "group": groupData,
- "type": "similar"
- ]) }
- }
- group.leave()
- }
- }
- }
- }
-
- // 当前批次处理完成后
- group.notify(queue: .global()) {
- // 清理内存
- self.cleanupMemory()
-
- // 延迟一小段时间再处理下一批,给系统一些恢复时间
- DispatchQueue.global().asyncAfter(deadline: .now() + 0.3) {
- // 递归处理下一批
- self.processSimilarPhotoGroupsInBatches(
- groups: groups,
- batchIndex: batchIndex + 1,
- totalBatches: totalBatches,
- batchSize: batchSize,
- sizeInfo: sizeInfo,
- resultData: resultData,
- completion: completion
- )
- }
- }
- }
-
- private func getPhoto(flutterResult: @escaping FlutterResult) {
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
- let fetchOptions = PHFetchOptions()
- let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
-
- self.photoClassifier.classifyPhotos(
- assets: allPhotos,
- progressHandler: { (stage, progress) in
- print("Progress: \(stage) - \(progress)")
- },
- completion: { result in
- // 使用串行队列处理结果,避免同时处理多个组
- let processingQueue = DispatchQueue(label: "com.yourapp.photoProcessing")
- let resultData = Atomic<[[String: Any]]>([])
-
- // 创建一个函数来依次处理每个组
- func processGroups(index: Int, groups: [(assets: [PHAsset], name: String, type: String, sizeInfo: ClassifyPhoto.PhotoSizeInfo)]) {
- // 检查是否处理完所有组
- if index >= groups.count {
- // 所有组处理完毕,返回结果
- DispatchQueue.main.async {
- print("Final result count: \(resultData.value.count)")
- flutterResult(resultData.value)
- }
- return
- }
-
- let currentGroup = groups[index]
-
- // 处理当前组
- self.processPhotoGroup(
- assets: currentGroup.assets,
- groupName: currentGroup.name,
- sizeInfo: currentGroup.sizeInfo
- ) { groupData in
- // 使用自动释放池减少内存占用
- autoreleasepool {
- if !groupData.isEmpty {
- resultData.mutate { $0.append([
- "group": groupData,
- "type": currentGroup.type,
- currentGroup.type == "location" ? "name" : "" : currentGroup.name
- ].filter { !($0.value is String && $0.value as! String == "") }) }
- }
-
- // 手动触发内存清理
- self.cleanupMemory()
-
- // 延迟一小段时间,让系统有机会回收内存
- DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
- // 处理下一个组
- processGroups(index: index + 1, groups: groups)
- }
- }
- }
- }
-
- // 准备所有需要处理的组
- var allGroups: [(assets: [PHAsset], name: String, type: String, sizeInfo: ClassifyPhoto.PhotoSizeInfo)] = []
-
- // 添加截图组
- if !result.screenshots.isEmpty {
- allGroups.append((result.screenshots, "screenshots", "screenshots", result.screenshotsSize))
- }
-
- // 添加相似照片组
- for photoGroup in result.similarPhotos {
- if !photoGroup.isEmpty {
- allGroups.append((photoGroup, "similar", "similar", result.similarPhotosSize))
- }
- }
-
- // 添加模糊照片组
- if !result.blurryPhotos.isEmpty {
- allGroups.append((result.blurryPhotos, "blurry", "blurry", result.blurryPhotosSize))
- }
- // 添加人物照片组
- for (personName, personPhotos) in result.people {
- if !personPhotos.isEmpty {
- allGroups.append((personPhotos, personName, "people", result.peopleSize))
- }
- }
-
- // 开始处理第一个组
- if allGroups.isEmpty {
- DispatchQueue.main.async {
- print("No groups to process")
- flutterResult([])
- }
- } else {
- processGroups(index: 0, groups: allGroups)
- }
- }
- )
-
- // self.photoClassifier.classifyPhotos(
- // assets: allPhotos,
- // progressHandler: { (stage, progress) in
- // print("Progress: \(stage) - \(progress)")
- // },
- // completion: { result in
- // var resultData: [[String: Any]] = []
- // let mainGroup = DispatchGroup()
- //
- // // 处理截图
- // mainGroup.enter()
- // self.processPhotoGroup(assets: result.screenshots, groupName: "screenshots", sizeInfo: result.screenshotsSize) { groupData in
- // if !groupData.isEmpty {
- // resultData.append(["group": groupData, "type": "screenshots"])
- // }
- // mainGroup.leave()
- // }
- //
- // // 处理相似照片组
- // for photoGroup in result.similarPhotos {
- // mainGroup.enter()
- // self.processPhotoGroup(assets: photoGroup, groupName: "similar", sizeInfo: result.similarPhotosSize) { groupData in
- // if !groupData.isEmpty {
- // resultData.append(["group": groupData, "type": "similar"])
- // }
- // mainGroup.leave()
- // }
- // }
- //
- // // 处理地点分组
- //// for (location, assets) in result.locations {
- //// mainGroup.enter()
- //// self.processPhotoGroup(assets: assets, groupName: location, sizeInfo: result.locationsSize) { groupData in
- //// if !groupData.isEmpty {
- //// resultData.append(["group": groupData, "type": "location", "name": location])
- //// }
- //// mainGroup.leave()
- //// }
- //// }
- //
- // // 处理人物分组
- //// for (person, assets) in result.people {
- //// mainGroup.enter()
- //// self.processPhotoGroup(assets: assets, groupName: person, sizeInfo: result.peopleSize) { groupData in
- //// if !groupData.isEmpty {
- //// resultData.append(["group": groupData, "type": "people"])
- //// }
- //// mainGroup.leave()
- //// }
- //// }
- //
- // // 处理模糊照片
- // mainGroup.enter()
- // self.processPhotoGroup(assets: result.blurryPhotos, groupName: "blurry", sizeInfo: result.blurryPhotosSize) { groupData in
- // if !groupData.isEmpty {
- // resultData.append(["group": groupData, "type": "blurry"])
- // }
- // mainGroup.leave()
- // }
- //
- // mainGroup.notify(queue: .main) {
- // print("Final result count: \(resultData.count)")
- // flutterResult(resultData)
- // }
- // }
- // )
- }
- }
-
- // // 添加内存清理辅助方法
- // private func cleanupMemory() {
- // // 清理图像缓存
- // URLCache.shared.removeAllCachedResponses()
- //
- // // 强制进行一次垃圾回收
- // autoreleasepool {
- // let _ = [String](repeating: "temp", count: 1)
- // }
- //
- // #if os(iOS)
- // // 发送低内存警告
- // UIApplication.shared.perform(Selector(("_performMemoryWarning")))
- // #endif
- // }
-
- // 添加内存清理方法
- // private func cleanupMemory() {
- // // 强制清理内存
- // autoreleasepool {
- // // 触发内存警告,促使系统回收内存
- // UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.beginIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- // UIApplication.shared.performSelector(onMainThread: #selector(UIApplication.endIgnoringInteractionEvents), with: nil, waitUntilDone: true)
- // }
- // }
-
- // 处理照片组的辅助方法
- private func processPhotoGroup(
- assets: [PHAsset],
- groupName: String,
- sizeInfo: ClassifyPhoto.PhotoSizeInfo,
- completion: @escaping ([String: Any]) -> Void
- ) {
- let photoProcessGroup = DispatchGroup()
- var photosData: [[String: Any]] = []
-
- for asset in assets {
- photoProcessGroup.enter()
-
- let options = PHContentEditingInputRequestOptions()
- options.isNetworkAccessAllowed = false
-
- DispatchQueue.global(qos: .background).async {
- asset.requestContentEditingInput(with: options) { (input, info) in
- defer { photoProcessGroup.leave() }
-
- if let input = input, let url = input.fullSizeImageURL {
- let photoInfo: [String: Any] = [
- // "path": url.path,
- "id": asset.localIdentifier,
- // "width": asset.pixelWidth,
- // "height": asset.pixelHeight,
- // "creationDate": asset.creationDate?.timeIntervalSince1970 ?? 0
- ]
- photosData.append(photoInfo)
- }
- }
- }
- }
-
- photoProcessGroup.notify(queue: .main) {
- completion([
- "photos": photosData,
- "totalSize": sizeInfo.totalSize,
- "count": sizeInfo.count
- ])
- }
- }
-
- private func getExifInfo(filePath: String, completion: @escaping FlutterResult) {
- // 创建文件 URL
- let fileURL: URL
- if filePath.starts(with: "file://") {
- guard let url = URL(string: filePath) else {
- print("Invalid URL string: \(filePath)")
- completion([:])
- return
- }
- fileURL = url
- } else {
- fileURL = URL(fileURLWithPath: filePath)
- }
-
- // 检查文件是否存在
- guard FileManager.default.fileExists(atPath: fileURL.path) else {
- print("File does not exist at path: \(fileURL.path)")
- completion([:])
- return
- }
-
- // 创建图片源
- guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) else {
- print("Failed to create image source for path: \(fileURL.path)")
- completion([:])
- return
- }
-
- var exifInfo: [String: Any] = [:]
-
- // 获取所有元数据
- if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any] {
- // EXIF 数据
- if let exif = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] {
- exifInfo["aperture"] = exif[kCGImagePropertyExifFNumber as String]
- exifInfo["exposureTime"] = exif[kCGImagePropertyExifExposureTime as String]
- exifInfo["iso"] = exif[kCGImagePropertyExifISOSpeedRatings as String]
- exifInfo["focalLength"] = exif[kCGImagePropertyExifFocalLength as String]
- exifInfo["dateTimeOriginal"] = exif[kCGImagePropertyExifDateTimeOriginal as String]
- }
-
- // TIFF 数据
- if let tiff = imageProperties[kCGImagePropertyTIFFDictionary as String] as? [String: Any] {
- exifInfo["make"] = tiff[kCGImagePropertyTIFFMake as String]
- exifInfo["model"] = tiff[kCGImagePropertyTIFFModel as String]
- }
-
- // GPS 数据
- if let gps = imageProperties[kCGImagePropertyGPSDictionary as String] as? [String: Any] {
- exifInfo["latitude"] = gps[kCGImagePropertyGPSLatitude as String]
- exifInfo["longitude"] = gps[kCGImagePropertyGPSLongitude as String]
- exifInfo["altitude"] = gps[kCGImagePropertyGPSAltitude as String]
- }
-
- // 图片基本信息
- exifInfo["pixelWidth"] = imageProperties[kCGImagePropertyPixelWidth as String]
- exifInfo["pixelHeight"] = imageProperties[kCGImagePropertyPixelHeight as String]
- exifInfo["dpi"] = imageProperties[kCGImagePropertyDPIHeight as String]
- exifInfo["colorModel"] = imageProperties[kCGImagePropertyColorModel as String]
- exifInfo["profileName"] = imageProperties[kCGImagePropertyProfileName as String]
- }
-
- completion(exifInfo)
- }
- }
- // 计算选择的图片大小
- 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)
- let processingGroup = DispatchGroup()
-
- // 创建一个具有相同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
-
- for batchStart in stride(from: 0, to: totalCount, by: batchSize) {
- let end = min(batchStart + batchSize, totalCount)
-
- processingGroup.enter()
- // 使用具有明确QoS的队列
- processingQueue.async {
- var batchSize: Int64 = 0
-
- for i in batchStart..<end {
- autoreleasepool {
- // 使用带超时的等待,避免无限期阻塞
- let waitResult = semaphore.wait(timeout: .now() + 5)
-
- defer {
- // 确保信号量总是被释放
- if waitResult != .timedOut {
- semaphore.signal()
- }
- }
-
- // 如果等待超时,跳过当前资源
- if waitResult == .timedOut {
- print("警告: 等待资源超时,跳过资源")
- return
- }
-
- let asset = assets.object(at: i)
-
- // 使用资源管理器获取大小
- PHAssetResource.assetResources(for: asset).forEach { resource in
- var resourceSize: Int64 = 0
-
- // 尝试获取文件大小
- if let fileSize = resource.value(forKey: "fileSize") as? CLong {
- resourceSize = Int64(fileSize)
- }
-
- batchSize += resourceSize
- }
- }
- }
-
- // 更新总大小
- 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)
- }
- }
- }
- }
- }
- class SubscriptionHandler: NSObject {
-
- @available(iOS 15.0.0, *)
- func checkTrialEligibility() async -> Bool {
- do {
- // 获取产品信息
- let productIds = ["clean.vip.1week"]
- let products = try await Product.products(for: Set(productIds))
-
- // 检查第一个产品的试用资格
- if let product = products.first {
- return await product.subscription?.isEligibleForIntroOffer ?? false
- }
- return false
- } catch {
- print("Error checking trial eligibility: \(error)")
- return false
- }
- }
- }
|