【问题标题】:Struct implementing PHPhotoLibraryChangeObserver实现 PHPhotoLibraryChangeObserver 的结构
【发布时间】:2020-08-31 12:12:10
【问题描述】:

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

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

  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
        ...
    }

}

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

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

编辑:重新提出问题

编辑 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
    }
    
    ...
    
}

编辑:解决了更新问题

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

【问题讨论】:

  • 我正在尝试将 PhotoKit 必须更改的唯一可变数据推送到充当委托的引用类型属性。模型的其余部分可以异步处理它,因此它可以访问委托中的数据,而无需将 self(模型)传递给 PhotoKit。
  • 我写下了到目前为止的结果。似乎可行,除了 photoLibraryDidChange 没有被调用...肯定有一个愚蠢的错误,我只是看不到它
  • 可能取决于你的结构是如何被传递的?请记住,它是一个结构,因此每个分配都会创建一个副本。能否详细介绍一下 Model 是如何维护的等等?
  • 我调试并检查了适配器类没有被多次实例化。 Swift 在更改时复制应该将指向引用类型属性的指针转移到新值。情况似乎如此。我还明确要求了适配器对象的 id,在模型上进行了几次突变后,它是相同的。这样就排除了多个副本和丢失的参考。
  • 模拟器错误。在真实设备上它可以工作......真是浪费时间

标签: swift photokit


【解决方案1】:

我最终得到了 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
    ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多