import Flutter 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 "getPhoto": self.getPhoto(flutterResult: result) case "getStorageInfo": getStorageInfo(result: result) case "getPlatformVersion": result("iOS " + UIDevice.current.systemVersion) default: result(FlutterMethodNotImplemented) } } private class func blankof(type:T.Type) -> T { let ptr = UnsafeMutablePointer.allocate(capacity: MemoryLayout.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: 10) // 限制并发 allPhotos.enumerateObjects { (asset, index, stop) in group.enter() semaphore.wait() let resources = PHAssetResource.assetResources(for: asset) if let resource = resources.first { queue.async { let options = PHAssetResourceRequestOptions() options.isNetworkAccessAllowed = true PHAssetResourceManager.default().requestData( for: resource, options: options, dataReceivedHandler: { data in photoSize += Int64(data.count) }, completionHandler: { error in if let error = error { print("Error getting photo size: \(error)") } semaphore.signal() group.leave() } ) } } else { semaphore.signal() group.leave() } } group.notify(queue: .main) { storageInfo["photoSpace"] = photoSize result(storageInfo) } } } 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) 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 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 = true 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 ]) } } }