Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_CoreDataDependency doesn't update its Fetched item. #68

Open
tgrapperon opened this issue May 19, 2023 Discussed in #67 · 3 comments
Open

_CoreDataDependency doesn't update its Fetched item. #68

tgrapperon opened this issue May 19, 2023 Discussed in #67 · 3 comments
Assignees

Comments

@tgrapperon
Copy link
Owner

Discussed in #67

Originally posted by m-housh May 19, 2023
Forgive my ignorance with CoreData in general, but I'm playing around with the _CoreDataDependency and trying to determine how to update a Fetched item. I have tried several approaches, mainly the following...

struct TodoFeature: Reducer {
  struct State: Equatable {
     var todos: Todo.FetchedResults = .empty
  }

  Action: Equatable {
    ...
    case toggleComplete(todo: Fetched<Todo>)
  }

  @Dependency(\.persistentContainer) var persistentContainer;

  var body: some ReducerOf<Self> {
    Reduce { state, action in
      switch action {
       ...
       case .toggleComplete(todo: let todo):
          todo.withManagedObject { update in 
            update.complete.toggle()
            try! update.managedObjectContext!.save()
          }
         return .none
      }
      
    }
  }
}

This does not update the view / state, however if I shut the app down and restart it, the complete value is toggled. So I'm not sure if I need to invalidate the cached items that have been fetched, but calling my task that initially loads the todo's does not seem to update the todo's state either. Once again, forgive my ignorance in working with core data in general as it seems like there's something basic that I'm missing.

Here's the repository for more complete example.

https://github.com/m-housh/CoreData_Test

@tgrapperon tgrapperon self-assigned this May 19, 2023
@acosmicflamingo
Copy link
Contributor

I used print statements to verify that state is indeed being mutated as one would expect. Since the reducer is not recognizing that anything changed at all, that leads me to believe that Fetched<ManagedObject> itself is having an issue with equatability. What's interesting is that there is never an instance when Fetched actually conforms to the Equatable protocol, but the compiler would complain when I try to explicitly conform it myself. Turns out that Hashable conforms to Equatable, so because Fetched conforms to Hashable, it will automatically conform to Equatable. I don't see any explicit overloading of ==, so Fetched must be relying on whatever the compiler automatically generates for us.

What I also find odd is that print(state.todo.first) shows me this:

Fetched(
  id: _NSCoreDataTaggedObjectID(),
  context: NSManagedObjectContext(),
  viewContext: NSManagedObjectContext(↩︎),
  token: nil
)

So strange that I don't see the object property in there, which is what holds the actual value of interest. Could it be that the synthesized version is doing a comparison of something like this?

extension Fetched: Equatable {
  public static func == (lhs: Fetched, rhs: Fetched) -> Bool {
    return lhs.id == rhs.id &&
      lhs.context == rhs.context &&
      lhs.viewContext == rhs.viewContext &&
      lhs.token == rhs.token
  }
}

That would certainly explain why despite mutating state, the reducer is not actually sending updates to ViewStore.

@acosmicflamingo
Copy link
Contributor

Oh fascinating! Looking through the Swift proposal SE-0185 relating to synthesizing Equatable and Hashable conformance, computed properties are completely ignored:

Synthesized requirements for structs
For a struct, synthesis of P's requirements is based on the conformances of only its stored instance properties. Neither static properties nor computed instance properties (those with custom getters) are considered.

Something tells me it's not a coincidence that the very property we need to check for equatability might be excluded from whatever the Swift compiler synthesizes for us ;)

@acosmicflamingo
Copy link
Contributor

Oh no :( :( :( object as a reference type makes this so messy...you have two value types Fetched being compared, but if object is a reference type, then by the time we test for equatability, the mutation that occurred in one of the Fetched value types will be seen in the other. I have no clue how to work around that...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants