Originally created by @eraydiler on GitHub (Nov 16, 2018).
Hello,
I'm trying to use DifferenceKit library for updating UI triggered by relevant CoreStore methods.
I simply need to have two different collection (before-update, after-update) to use these library.
However, it looks like the dataProvider.objects, which holds elements represented in UI, is being automatically updated, so I am unable to make a comparison between previous and updated data.
Is there a way to access the previous state of these objects so that I can make a comparison with the updated objects?
extensionSimpleListViewController{funclistMonitor(_monitor:ListMonitor<LocalTask>,didUpdateObjectobject:LocalTask,atIndexPathindexPath:IndexPath){guardletoldObjects=dataProvider.objectsas?[LocalTask],varnewObjects=dataProvider.objectsas?[LocalTask]else{return}newObjects[indexPath.item]=object// These line is useless now, because newObjects is already updatedletchangeSet=StagedChangeset(source:oldObjects,target:newObjects)listView.reload(using:changeSet){datainupdate(objects:data)}}}
Originally created by @eraydiler on GitHub (Nov 16, 2018).
Hello,
I'm trying to use DifferenceKit library for updating UI triggered by relevant CoreStore methods.
I simply need to have two different collection (before-update, after-update) to use these library.
However, it looks like the `dataProvider.objects`, which holds elements represented in UI, is being automatically updated, so I am unable to make a comparison between previous and updated data.
Is there a way to access the previous state of these objects so that I can make a comparison with the updated objects?
```swift
extension SimpleListViewController {
func listMonitor(
_ monitor: ListMonitor<LocalTask>,
didUpdateObject object: LocalTask,
atIndexPath indexPath: IndexPath) {
guard
let oldObjects = dataProvider.objects as? [LocalTask],
var newObjects = dataProvider.objects as? [LocalTask]
else {
return
}
newObjects[indexPath.item] = object // These line is useless now, because newObjects is already updated
let changeSet = StagedChangeset(
source: oldObjects,
target: newObjects
)
listView.reload(using: changeSet) { data in
update(objects: data)
}
}
}
````
ListMonitor (or strictly speaking, NSFetchedResultsController) manages these changes for you so they are not really a good fit for DifferenceKit. If you run DifferenceKit on top of ListMonitors you will be unnecessarily duplicating a lot of heavy work.
Diff'ing libraries like DifferenceKit were designed for objects that don't manage states, meaning you have an "old version" and a "new version". With ORMs like Core Data, objects are live and update themselves in real time.
@JohnEstropia commented on GitHub (Nov 19, 2018):
`ListMonitor` (or strictly speaking, `NSFetchedResultsController`) manages these changes for you so they are not really a good fit for DifferenceKit. If you run DifferenceKit on top of `ListMonitor`s you will be unnecessarily duplicating a lot of heavy work.
Diff'ing libraries like `DifferenceKit` were designed for objects that don't manage states, meaning you have an "old version" and a "new version". With ORMs like Core Data, objects are *live* and update themselves in real time.
I have coded something before maybe it can help you too. If you are downloading data from a web server and want to compare the values, you may use a DifferenceKit. I personally use DeepDiff
So let's say you have new Data coming from a web server Which is called Post and each Post have some PostDetail 1:M Relationship
Storage.stack.rx.perform(asynchronous:{transactionin// Import uniqueObjectsletimported=trytransaction.importUniqueObjects(Into(PostCD.self),sourceArray:posts)// Optional Remove non existent data from the local databasetransaction.deleteAll(From<PostCD>(),Where<PostCD>("NOT (SELF IN %@)",imported))})
In order for this code to work, your NSManagedObject classes must conform to ImportableObject protocol.
importCoreStoreimportDeepDiffextensionPostDetailCD:ImportableObject{typealiasImportSource=PostDetailfuncdidInsert(fromsource:PostDetail,intransaction:BaseDataTransaction)throws{self.message=source.messageself.date=source.date}}extensionPostCD:ImportableUniqueObject{typealiasImportSource=Post// Your Model from the Server// MARK: ImportableUniqueObjecttypealiasUniqueIDType=StringstaticvaruniqueIDKeyPath:String{return#keyPath(PostCD.trackingID)}// MARK: ImportableObjectstaticfuncshouldInsert(fromsource:ImportSource,intransaction:BaseDataTransaction)->Bool{returntrue}staticfuncshouldUpdate(fromsource:ImportSource,intransaction:BaseDataTransaction)->Bool{returntrue}staticfuncuniqueID(fromsource:Post,intransaction:BaseDataTransaction)throws->String?{returnsource.trackingID}funcupdate(fromsource:Post,intransaction:BaseDataTransaction)throws{self.name=source.nameself.trackingID=source.trackingIDifletoldDetails=self.detail.arrayas?[PostDetailCD]{varoldArray=[PostDetail]()fordetailinoldDetails{oldArray.append(PostDetail.init(message:detail.message,date:detail.date))}letchanges=diff(old:oldArray,new:source.details)// if changes.count > 0 {// print("Difference: \(changes)")// }forchangeinchanges{switchchange{case.delete(letdetail):transaction.delete(self.detail.object(at:detail.index)as?PostDetailCD)case.insert(letdetail):ifletnewDetail=trytransaction.importObject(Into(PostDetailCD.self),source:detail.item){newDetail.parent=selfself.detail.insert(newDetail,at:detail.index)}case.replace(letdetail):ifletoldDetail=self.detail.object(at:detail.index)as?PostDetailCD{oldDetail.message=detail.newItem.messageoldDetail.date=detail.newItem.date}else{print("This shouldn't happen!!")}case.move(letdetail):self.detail.moveObjects(at:IndexSet(integer:detail.fromIndex),to:detail.toIndex)}}}}}
@tosbaha commented on GitHub (Nov 22, 2018):
I have coded something before maybe it can help you too. If you are downloading data from a web server and want to compare the values, you may use a DifferenceKit. I personally use `DeepDiff`
So let's say you have new Data coming from a web server Which is called `Post` and each `Post` have some `PostDetail` 1:M Relationship
```swift
Storage.stack.rx.perform(asynchronous: { transaction in
// Import uniqueObjects
let imported = try transaction.importUniqueObjects(Into(PostCD.self), sourceArray: posts)
// Optional Remove non existent data from the local database
transaction.deleteAll(From<PostCD>(), Where<PostCD>("NOT (SELF IN %@)", imported))
})
```
In order for this code to work, your NSManagedObject classes must conform to `ImportableObject` protocol.
```swift
import CoreStore
import DeepDiff
extension PostDetailCD:ImportableObject {
typealias ImportSource = PostDetail
func didInsert(from source: PostDetail, in transaction: BaseDataTransaction) throws {
self.message = source.message
self.date = source.date
}
}
extension PostCD:ImportableUniqueObject {
typealias ImportSource = Post // Your Model from the Server
// MARK: ImportableUniqueObject
typealias UniqueIDType = String
static var uniqueIDKeyPath: String {
return #keyPath(PostCD.trackingID)
}
// MARK: ImportableObject
static func shouldInsert(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool {
return true
}
static func shouldUpdate(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool {
return true
}
static func uniqueID(from source: Post, in transaction: BaseDataTransaction) throws -> String? {
return source.trackingID
}
func update(from source: Post, in transaction: BaseDataTransaction) throws {
self.name = source.name
self.trackingID = source.trackingID
if let oldDetails = self.detail.array as? [PostDetailCD] {
var oldArray = [PostDetail]()
for detail in oldDetails {
oldArray.append(PostDetail.init(message: detail.message, date: detail.date))
}
let changes = diff(old: oldArray, new: source.details)
// if changes.count > 0 {
// print("Difference: \(changes)")
// }
for change in changes {
switch change {
case .delete(let detail):
transaction.delete(self.detail.object(at: detail.index) as? PostDetailCD)
case .insert(let detail):
if let newDetail = try transaction.importObject(Into(PostDetailCD.self), source: detail.item) {
newDetail.parent = self
self.detail.insert(newDetail, at: detail.index)
}
case .replace(let detail):
if let oldDetail = self.detail.object(at: detail.index) as? PostDetailCD {
oldDetail.message = detail.newItem.message
oldDetail.date = detail.newItem.date
} else {
print("This shouldn't happen!!")
}
case .move(let detail):
self.detail.moveObjects(at: IndexSet(integer: detail.fromIndex), to: detail.toIndex)
}
}
}
}
}
```
@tosbaha I may be missing something your implementation, but will .replace be called if there are just updates to the PostDetail? The way I see it is by the time you call PosDetail.init, the detail instance would be the updated instance. (Unless you are managing the update order as well)
Also, since you are doing this in the context of the transaction, you would need extra handling if there was an error further on.
@JohnEstropia commented on GitHub (Dec 5, 2018):
@tosbaha I may be missing something your implementation, but will `.replace` be called if there are just updates to the `PostDetail`? The way I see it is by the time you call `PosDetail.init`, the `detail` instance would be the updated instance. (Unless you are managing the update order as well)
Also, since you are doing this in the context of the transaction, you would need extra handling if there was an error further on.
I haven't tested all possible cases but here is how it works. DeepDiff compares both Post and array PostDetail according to DeepDiff report it either deletes,insert or replace the item from the array. So if PostDetail array has 100 items and only one is added later on, I only insert once.Likewise, if 99 details are same but last detail is changed, I only replace the last item. Therefore I prevent heavy delete insert operations. As I said, I could be missing something but so far it works faster then deleting/replacing all details when the data on server changes. I will appreciate if you tell me if there is any mistake in my logic or implementation. Thanks.
@tosbaha commented on GitHub (Dec 5, 2018):
I haven't tested all possible cases but here is how it works. `DeepDiff` compares both Post and array `PostDetail` according to `DeepDiff` report it either deletes,insert or replace the item from the array. So if `PostDetail` array has 100 items and only one is added later on, I only insert once.Likewise, if 99 details are same but last detail is changed, I only replace the last item. Therefore I prevent heavy delete insert operations. As I said, I could be missing something but so far it works faster then deleting/replacing all details when the data on server changes. I will appreciate if you tell me if there is any mistake in my logic or implementation. Thanks.
I spoke too soon :( My code only works if last detail is changed since my insert code is buggy. It is not possible to insert at given index.
self.detail.insert(newDetail,at:detail.index)
Above code doesn't seem to have any effect. It always inserts at the last when I use try transaction.importObject
How can I solve this issue?
Only hacky way I have found is this
Since object is inserted at the end of Array, I move the object from end to whatever the index it is. self.detail.moveObjects(at: IndexSet(integer: self.detail.count - 1 ), to: index)
@tosbaha commented on GitHub (Dec 16, 2018):
I spoke too soon :( My code only works if last detail is changed since my insert code is buggy. It is not possible to insert at given index.
```swift
self.detail.insert(newDetail, at: detail.index)
```
Above code doesn't seem to have any effect. It always inserts at the last when I use ` try transaction.importObject`
How can I solve this issue?
Only hacky way I have found is this
Since object is inserted at the end of Array, I move the object from end to whatever the index it is.
`self.detail.moveObjects(at: IndexSet(integer: self.detail.count - 1 ), to: index)`
Assuming self.detail is a to-many relationship, and PostCD an NSManagedObject subclass, you cannot directly mutate the NSOrderedSet or NSSet property. See https://stackoverflow.com/a/27888302/809614
My suggestion for your case (if I understood the intent correctly):
Create your var oldArray: [PostDetail] from your existing message objects before you do any imports
Load the changes by calling diff(old: oldArray, new: source.details) (still before importing)
Copy self.details into a temporary var temp: [PostDetailCD] = []
Handle all changes and work with the temp array, not self.details
When you're done set self.details = NSOrderedSet(array: temp)
It is a lot faster to set a relationship all at once than mutating the internal collection for each iteration.
@JohnEstropia commented on GitHub (Dec 18, 2018):
Assuming `self.detail` is a to-many relationship, and `PostCD` an `NSManagedObject` subclass, you cannot directly mutate the `NSOrderedSet` or `NSSet` property. See https://stackoverflow.com/a/27888302/809614
My suggestion for your case (if I understood the intent correctly):
1. Create your `var oldArray: [PostDetail]` from your existing message objects before you do any imports
2. Load the changes by calling `diff(old: oldArray, new: source.details)` (still before importing)
3. Copy `self.details` into a temporary `var temp: [PostDetailCD] = []`
4. Handle all `changes` and work with the `temp` array, not `self.details`
5. When you're done set `self.details = NSOrderedSet(array: temp)`
It is a lot faster to set a relationship all at once than mutating the internal collection for each iteration.
Just a note here, that @eraydiler and @tosbaha 's cases are totally different.
@eraydiler is trying to use Core Data objects directly with within diff(), while @tosbaha 's is diff'ing only the ImportSource arrays. The latter may be necessary depending on the use case, but the former is something I would discourage.
@JohnEstropia commented on GitHub (Dec 18, 2018):
Just a note here, that @eraydiler and @tosbaha 's cases are totally different.
@eraydiler is trying to use Core Data objects directly with within `diff()`, while @tosbaha 's is diff'ing only the `ImportSource` arrays. The latter may be necessary depending on the use case, but the former is something I would discourage.
Thanks a lot @JohnEstropia I decided to just check if there is difference or not. If different set all at once. Thanks once again for taking your time for explaining in detail.
@tosbaha commented on GitHub (Dec 18, 2018):
Thanks a lot @JohnEstropia I decided to just check if there is difference or not. If different set all at once. Thanks once again for taking your time for explaining in detail.
Thanks for the answers @JohnEstropia, @tosbaha. After having some time I decided to go without using differenceKit.
@eraydiler commented on GitHub (Dec 21, 2018):
Thanks for the answers @JohnEstropia, @tosbaha. After having some time I decided to go without using differenceKit.
This is quite old but to anyone following the thread, CoreStore now has ListPublishers which work in tandem with DiffableDataSources. This uses the same algorithm that DifferenceKit uses, so check the README for more info. Closing this thread now
@JohnEstropia commented on GitHub (Jan 8, 2020):
This is quite old but to anyone following the thread, CoreStore now has `ListPublisher`s which work in tandem with `DiffableDataSource`s. This uses the same algorithm that DifferenceKit uses, so check the README for more info. Closing this thread now
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @eraydiler on GitHub (Nov 16, 2018).
Hello,
I'm trying to use DifferenceKit library for updating UI triggered by relevant CoreStore methods.
I simply need to have two different collection (before-update, after-update) to use these library.
However, it looks like the
dataProvider.objects, which holds elements represented in UI, is being automatically updated, so I am unable to make a comparison between previous and updated data.Is there a way to access the previous state of these objects so that I can make a comparison with the updated objects?
@JohnEstropia commented on GitHub (Nov 19, 2018):
ListMonitor(or strictly speaking,NSFetchedResultsController) manages these changes for you so they are not really a good fit for DifferenceKit. If you run DifferenceKit on top ofListMonitors you will be unnecessarily duplicating a lot of heavy work.Diff'ing libraries like
DifferenceKitwere designed for objects that don't manage states, meaning you have an "old version" and a "new version". With ORMs like Core Data, objects are live and update themselves in real time.@tosbaha commented on GitHub (Nov 22, 2018):
I have coded something before maybe it can help you too. If you are downloading data from a web server and want to compare the values, you may use a DifferenceKit. I personally use
DeepDiffSo let's say you have new Data coming from a web server Which is called
Postand eachPosthave somePostDetail1:M RelationshipIn order for this code to work, your NSManagedObject classes must conform to
ImportableObjectprotocol.@JohnEstropia commented on GitHub (Dec 5, 2018):
@tosbaha I may be missing something your implementation, but will
.replacebe called if there are just updates to thePostDetail? The way I see it is by the time you callPosDetail.init, thedetailinstance would be the updated instance. (Unless you are managing the update order as well)Also, since you are doing this in the context of the transaction, you would need extra handling if there was an error further on.
@tosbaha commented on GitHub (Dec 5, 2018):
I haven't tested all possible cases but here is how it works.
DeepDiffcompares both Post and arrayPostDetailaccording toDeepDiffreport it either deletes,insert or replace the item from the array. So ifPostDetailarray has 100 items and only one is added later on, I only insert once.Likewise, if 99 details are same but last detail is changed, I only replace the last item. Therefore I prevent heavy delete insert operations. As I said, I could be missing something but so far it works faster then deleting/replacing all details when the data on server changes. I will appreciate if you tell me if there is any mistake in my logic or implementation. Thanks.@tosbaha commented on GitHub (Dec 16, 2018):
I spoke too soon :( My code only works if last detail is changed since my insert code is buggy. It is not possible to insert at given index.
Above code doesn't seem to have any effect. It always inserts at the last when I use
try transaction.importObjectHow can I solve this issue?
Only hacky way I have found is this
Since object is inserted at the end of Array, I move the object from end to whatever the index it is.
self.detail.moveObjects(at: IndexSet(integer: self.detail.count - 1 ), to: index)@JohnEstropia commented on GitHub (Dec 18, 2018):
Assuming
self.detailis a to-many relationship, andPostCDanNSManagedObjectsubclass, you cannot directly mutate theNSOrderedSetorNSSetproperty. See https://stackoverflow.com/a/27888302/809614My suggestion for your case (if I understood the intent correctly):
var oldArray: [PostDetail]from your existing message objects before you do any importsdiff(old: oldArray, new: source.details)(still before importing)self.detailsinto a temporaryvar temp: [PostDetailCD] = []changesand work with thetemparray, notself.detailsself.details = NSOrderedSet(array: temp)It is a lot faster to set a relationship all at once than mutating the internal collection for each iteration.
@JohnEstropia commented on GitHub (Dec 18, 2018):
Just a note here, that @eraydiler and @tosbaha 's cases are totally different.
@eraydiler is trying to use Core Data objects directly with within
diff(), while @tosbaha 's is diff'ing only theImportSourcearrays. The latter may be necessary depending on the use case, but the former is something I would discourage.@tosbaha commented on GitHub (Dec 18, 2018):
Thanks a lot @JohnEstropia I decided to just check if there is difference or not. If different set all at once. Thanks once again for taking your time for explaining in detail.
@eraydiler commented on GitHub (Dec 21, 2018):
Thanks for the answers @JohnEstropia, @tosbaha. After having some time I decided to go without using differenceKit.
@JohnEstropia commented on GitHub (Jan 8, 2020):
This is quite old but to anyone following the thread, CoreStore now has
ListPublishers which work in tandem withDiffableDataSources. This uses the same algorithm that DifferenceKit uses, so check the README for more info. Closing this thread now