实现 PHPhotoLibraryChangeObserver 的结构
Struct implementing PHPhotoLibraryChangeObserver
我的模型有一个结构,它需要符合仅 NSObject 的协议。
我正在寻找一种可行的替代方法来将模型转换为 class。要求是:
- 保持模型为值类型
- 调用
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 种可能的设计:
- 将 PhotoKit 接口与模型完全分离,让模型视图管理并连接它们,因为模型视图可以访问模型实例。
- 创建一个 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
...
我的模型有一个结构,它需要符合仅 NSObject 的协议。
我正在寻找一种可行的替代方法来将模型转换为 class。要求是:
- 保持模型为值类型
- 调用
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 种可能的设计:
- 将 PhotoKit 接口与模型完全分离,让模型视图管理并连接它们,因为模型视图可以访问模型实例。
- 创建一个 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
...