实现 PHPhotoLibraryChangeObserver 的结构

Struct implementing PHPhotoLibraryChangeObserver

我的模型有一个结构,它需要符合仅 NSObject 的协议。

我正在寻找一种可行的替代方法来将模型转换为 class。要求是:

  1. 保持模型为值类型
  2. 调用 photoLibraryDidChange 时更新模型

如果 PHPhotoLibraryChangeObserver 不需要实现为 NSObject

,这将是理想的实现
struct Model: PHPhotoLibraryChangeObserver {

    var images:[UIImages] = []

    fileprivate var allPhotos:PHFetchResult<PHAsset>?

    mutating func photoLibraryDidChange(_ changeInstance: PHChange) {
        let changeResults = changeInstance.changeDetails(for: allPhotos)
        allPhotos = changeResults?.fetchResultAfterChanges
        updateImages()
    }

    mutating func updateImages() { 
        // update self.images
        ...
    }

}

我无法将模型传递给实施观察者协议的外部 class,因为所有更改都发生在副本上(它是值类型...)

有什么想法吗?最佳做法?

编辑:重新表述问题

编辑 2:进度

我已经将委托实现为模型的引用类型 var,并将数据推送到其中。现在 photoLibraryDidChange 不再被调用了。

这是精简的实现:


class PhotoKitAdapter:NSObject, PHPhotoLibraryChangeObserver {
    
    var allPhotos: PHFetchResult<PHAsset>?
    
    var images:[UIImage] = []
    
    override init(){
        super.init()
    }
    
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        DispatchQueue.main.async {
            if let changeResults = changeInstance.changeDetails(for: self.allPhotos!) {
                self.allPhotos = changeResults.fetchResultAfterChanges
                //this neve gets executed. It used to provide high quality images
                self.updateImages()
            }
        }
    }
    
    func startFetching(){
        let allPhotosOptions = PHFetchOptions()
        allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
        PHPhotoLibrary.shared().register(self)
        //this gets executed and fetches the thumbnails
        self.updateImages()
    }
    
    fileprivate func appendImage(_ p: PHAsset) {
        let pm = PHImageManager.default()
        if p.mediaType == .image {
            pm.requestImage(for: p, targetSize: CGSize(width: 1024, height: 768), contentMode: .default, options: nil){
                image, _  in
                if let im = image {
                    self.images.append(im)
                }
                
            }
        }
    }
    
    fileprivate func updateImages() {
        self.images = []
        if let ap = allPhotos {
            for index in 0..<min(ap.count, 10) {
                let p = ap[index]
                appendImage(p)
            }
        }
    }
    
}


struct Model {
    
    private var pkAdapter = PhotoKitAdapter()
    
    var images:[UIImage] {
        pkAdapter.images
    }
    
    func startFetching(){
        pkAdapter.startFetching()
    }
    
    // example model implementation
    mutating func select(_ image:UIImage){
        // read directly from pkAdapter.images and change other local variables
    }
    

}


我在 photoLibraryDidChange 中放置了一个断点,但它并没有到达那里。我还检查了 pkAdapter 始终是同一个对象,并且不会在“更改时复制”时重新初始化。

**编辑:添加模型视图**

这是负责模型管理的模型视图的相关部分


class ModelView:ObservableObject {
    
    @Published var model =  Model()
  
    init() {
        self.model.startFetching()
    }
    
    var images:[UIImage] {
        self.model.images
    }
    
    ...
    
}

编辑:解决了更新问题

这是模拟器中的错误...在真实设备上它可以工作

我最终得到了 2 种可能的设计:

  1. 将 PhotoKit 接口与模型完全分离,让模型视图管理并连接它们,因为模型视图可以访问模型实例。
  2. 创建一个 PhotoKit 接口作为模型的 var,并将 PhotoKit 接口生成的可变数据推送到其中,以便可以使用转义闭包进行更改。该模型从不从界面调用,只是通过计算机公开 PhotoKit 中的数据 属性.

我将在下面展示两个示例实现。他们在很多方面都很天真,他们通过每次更新 PhotoLibrary 时刷新所有图片来忽略性能问题。实施适当的增量更新(和其他优化)只会使代码混乱,并且不会提供对原始问题解决方案的额外见解。

解耦 PhotoKit 接口

模型视图

class ModelView:ObservableObject {
    
    var pkSubscription:AnyCancellable?
    
    private var pkAdapter = PhotoKitAdapter()
    
    @Published var model =  Model()
  
    init() {
        pkSubscription = self.pkAdapter.objectWillChange.sink{ _ in
            DispatchQueue.main.async {
                self.model.reset()
                for img in self.pkAdapter.images {
                    self.model.append(uiimage: img)
                }
            }
        }
        self.pkAdapter.startFetching()
    }
}

型号

struct Model {
    
    private(set) var images:[UIImage] = []
    
    mutating func append(uiimage:UIImage){
        images.append(uiimage)
    }
    
    mutating func reset(){
        images = []
    }
}

PhotoKit 界面

class PhotoKitAdapter:NSObject, PHPhotoLibraryChangeObserver, ObservableObject {
    
    var allPhotos: PHFetchResult<PHAsset>?
    var images:[UIImage] = []
    
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        DispatchQueue.main.async {
            if let changeResults = changeInstance.changeDetails(for: self.allPhotos!) {
                self.allPhotos = changeResults.fetchResultAfterChanges
                self.updateImages()
                self.objectWillChange.send()
            }
        }
    }
    
    func startFetching(){
        PHPhotoLibrary.requestAuthorization{status in
            if status == .authorized {
                let allPhotosOptions = PHFetchOptions()
                allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
                self.allPhotos = PHAsset.fetchAssets(with: allPhotosOptions)
                PHPhotoLibrary.shared().register(self)
                self.updateImages()
                
            }
        }

        
    }
    
    fileprivate func appendImage(_ p: PHAsset) {
        // This actually appends multiple copies of the image because 
        // it gets called multiple times for the same asset. 
        // Proper tracking of the asset needs to be implemented
        let pm = PHImageManager.default()
        if p.mediaType == .image {
            pm.requestImage(for: p, targetSize: CGSize(width: 1024, height: 768), contentMode: .default, options: nil){
                image, _  in
                if let im = image {
                    self.images.append(im)
                    self.objectWillChange.send()
                }
                
            }
        }
    }
    
    fileprivate func updateImages() {
        self.images = []
        if let ap = allPhotos {
            for index in 0..<min(ap.count, 10) {
                let p = ap[index]
                appendImage(p)
            }
        }
    }

PhotoKit界面为模型属性

模型视图

class ModelView:ObservableObject {
    
    @Published var model =  Model()
  
}

型号

struct Model {
    
    private var pkAdapter = PhotoKitAdapter()
    
    var images:[UIImage] { pkAdapter.images }
    
}

PhotoKit 界面

class PhotoKitAdapter:NSObject, PHPhotoLibraryChangeObserver{
    
    var allPhotos: PHFetchResult<PHAsset>?
    var images:[UIImage] = []

    override init(){
        super.init()
        startFetching()
    }
    
    // the rest of the implementation is the same as before
    ...