SwiftUI MVVM反馈中的核心数据

3

我正在寻找一种使用 CoreData 对象并采用 MVVM(放弃使用 @FetchRequest)的方法。经过实验,我得出了以下实现:

包网址:https://github.com/TimmysApp/DataStruct

Datable.swift:

protocol Datable {
    associatedtype Object: NSManagedObject
//MARK: - Mapping
    static func map(from object: Object) -> Self
    func map(from object: Object) -> Self
//MARK: - Entity
    var object: Object {get}
//MARK: - Fetching
    static var modelData: ModelData<Self> {get}
//MARK: - Writing
    func save()
}

extension Datable {
    static var modelData: ModelData<Self> {
        return ModelData()
    }
    func map(from object: Object) -> Self {
        return Self.map(from: object)
    }
    func save() {
        _ = object
        let viewContext = PersistenceController.shared.container.viewContext
        do {
            try viewContext.save()
        }catch {
            print(String(describing: error))
        }
    }
}

extension Array {
    func model<T: Datable>() -> [T] {
        return self.map({T.map(from: $0 as! T.Object)})
    }
}

ModelData.swift:

 class ModelData<T: Datable>: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
    var publishedData = CurrentValueSubject<[T], Never>([])
    private let fetchController: NSFetchedResultsController<NSFetchRequestResult>
    override init() {
        let fetchRequest = T.Object.fetchRequest()
        fetchRequest.sortDescriptors = []
        fetchController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: PersistenceController.shared.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
        super.init()
        fetchController.delegate = self
        do {
            try fetchController.performFetch()
            publishedData.value = (fetchController.fetchedObjects as? [T.Object] ?? []).model()
        }catch {
            print(String(describing: error))
        }
    }
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        guard let data = controller.fetchedObjects as? [T.Object] else {return}
        self.publishedData.value = data.model()
    }
}

Attempt.swift:

struct Attempt: Identifiable, Hashable {
    var id: UUID?
    var password: String
    var timestamp: Date
    var image: Data?
}

//MARK: - Datable
extension Attempt: Datable {
    var object: AttemptData {
        let viewContext = PersistenceController.shared.container.viewContext
        let newAttemptData = AttemptData(context: viewContext)
        newAttemptData.password = password
        newAttemptData.timestamp = timestamp
        newAttemptData.image = image
        return newAttemptData
    }
    static func map(from object: AttemptData) -> Attempt {
        return Attempt(id: object.aid ?? UUID(), password: object.password ?? "", timestamp: object.timestamp ?? Date(), image: object.image)
    }
}

ViewModel.swift:

class HomeViewModel: BaseViewModel {
    @Published var attempts = [Attempt]()
    required init() {
        super.init()
        Attempt.modelData.publishedData.eraseToAnyPublisher()
            .sink { [weak self] attempts in
                self?.attempts = attempts
            }.store(in: &cancellables)
    }
}

目前这个方法非常有效,但我想确认这是否是最佳实践,并在可能的情况下进行改进。请注意,我已经使用SwiftUI中的@FetchRequest一年多了,并决定转向MVVM,因为我在所有Storyboard项目中都在使用它。

1个回答

2

如果您想要一种前沿的方式来包装 NSFetchedResultsController 为 SwiftUI 兼容代码,您可能需要看一下 AsyncStream

然而,@FetchRequest 目前被实现为一个 DynamicProperty,所以如果你也这样做了,它将允许从 @Environment 中访问托管对象上下文,在调用 Viewbody 之前,在 DynamicProperty 上调用 update 函数。您可以在内部使用 @StateObject 作为 FRC 委托。

使用 MVVM 时要小心,因为它使用对象,而 SwiftUI 设计为与值类型一起工作,以消除可能存在的一致性错误。请参阅文档“选择结构和类”。如果您在 SwiftUI 上构建一个 MVVM 对象层,您有可能会重新引入这些错误。最好按照设计使用 View 数据结构,把 MVVM 留给编写传统视图控制器的人。但说实话,如果你学习了子视图控制器模式并理解了响应链,那么就真的没有必要使用 MVVM 视图模型对象。

另外,当使用 Combine 的 ObservableObject 时,我们不会对流进行订阅或使用 cancellables。相反,我们将流的输出分配给一个 @Published。但是,如果您不使用 CombineLatest,那么也许应该重新考虑是否真的需要使用 Combine。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接