我正在寻找一种使用MVVM(放弃@FetchRequest
)来使用CoreData
对象的方法。经过试验,我得到了以下实现:
软件包URL: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)
}
}
到目前为止,这是工作像一个魅力,但我想检查这是否是最好的方式来做,并改善它,如果可能的话。请注意,我已经使用@FetchRequest
与SwiftUI
超过一年了,现在决定转移到MVVM,因为我正在使用它在我所有的Storyboard
项目。
1条答案
按热度按时间vlju58qv1#
要了解用SwiftUI兼容代码 Package
NSFetchedResultsController
的最新方法,您可能需要看看AsyncStream。但是,
@FetchRequest
当前被实现为DynamicProperty
,因此如果您也这样做,它将允许从update
函数中的@Environment
访问托管对象上下文,该函数在View
上调用body
之前在DynamicProperty
上调用。您可以在内部使用@StateObject
作为FRC委托。使用MVVM时要小心,因为它使用的对象是SwiftUI设计用来处理值类型的对象,以消除对象可能出现的各种一致性错误。请参见文档Choosing Between Structures and Classes。如果你在SwiftUI上构建MVVM对象层,你可能会再次引入这些错误。你最好按照设计使用
View
数据结构,而在编写遗留视图控制器时使用MVVM。但老实说,如果您学习了子视图控制器模式并理解了响应器链,那么就真的根本不需要MVVM视图模型对象。顺便说一句,当使用合并的
ObservableObject
时,我们不使用sink
管道或cancellables
。相反,assign
管道的输出到@Published
。但是,如果你没有使用CombineLatest
,那么也许你应该重新考虑是否真的应该使用Combine。