带有SwiftUI MVVM反馈的核心数据

发布于 2025-01-22 03:30:04 字数 3746 浏览 1 评论 0 原文

我正在寻找一种使用 coredata 对象使用 mvvm (Ditching @fetchrequest )的方法。实验后,我到达了以下实现:

软件包url: https://github.com/timmmysapp/timmmysapp/dataSpstruct/dataSubstruct < /a>

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()
    }
}

frib.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 ,因为我在所有人中都在使用它我的故事板项目。

I am looking for a way to use CoreData Objects using MVVM (ditching @FetchRequest). After experimenting, I have arrived at the following implementation:

Package 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)
    }
}

So far this is working like a charm, however I wanted to check if this is the best way to do it, and improve it if possible. Please note that I have been using @FetchRequest with SwiftUI for over a year now and decided to move to MVVM since I am using it in all my Storyboard projects.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

吃→可爱长大的 2025-01-29 03:30:04

对于尖端的方式,将 nsfetchedResultScontroller 包裹在SwiftUI兼容代码中,您可能想看 asyncstream

但是, @fetchrequest 当前被实现为 dynamicproperty ,因此,如果您也这样做,它将允许从 @environment 中访问托管对象上下文更新 func在 dynamiCproperty body view> view 上调用。您可以在内部使用 @StateObject 作为FRC委托。

请谨慎使用MVVM,因为它使用的对象是SwiftUI设计用于使用价值类型的对象,以消除您可以使用对象获得的一致性错误。请参阅doc 在结构和类之间选择。如果您在Swiftui顶部构建MVVM对象层,则可能会重新引入这些错误。最好使用 View 数据结构来设计,并在编码Legacy View Controller时留下MVVM。但是,说实话,如果您学习了子视图控制器模式并了解响应者链,那么MVVM查看模型对象确实不需要。

仅供参考,当使用combine的 observableObject 时,我们不 sink 管道或使用 concellables 。相反, @publed 的管道的管道。但是,如果您不使用 combineLatest ,然后也许重新考虑您是否真的应该使用联合收割机。

For a cutting edge way to wrap the NSFetchedResultsController in SwiftUI compatible code you might want to take a look at AsyncStream.

However, @FetchRequest currently is implemented as a DynamicProperty so if you did that too it would allow access the managed object context from the @Environment in the update func which is called on the DynamicProperty before body is called on the View. You can use an @StateObject internally as the FRC delegate.

Be careful with MVVM because it uses objects where as SwiftUI is designed to work with value types to eliminate the kinds of consistency bugs you can get with objects. See the doc Choosing Between Structures and Classes. If you build an MVVM object layer on top of SwiftUI you risk reintroducing those bugs. You're better off using the View data struct as it's designed and leave MVVM for when coding legacy view controllers. But to be perfectly honest, if you learn the child view controller pattern and understand the responder chain then there is really no need for MVVM view model objects at all.

And FYI, when using Combine's ObservableObject we don't sink the pipeline or use cancellables. Instead, assign the output of the pipeline to an @Published. However, if you aren't using CombineLatest, then perhaps reconsider if you should really be using Combine at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文