I have a hierarchy of managed object contexts, like so:
A
/ \
B C
...where A's parent is the persistent store. A and C use a private queue, and B uses the main queue. I've found that doesn't seem to matter, but I'll note it anyway.
So, here is the best way to replicate the issue:
- Context B creates an object. Because I need to reference this same object multiple places, I obtain a permanent object ID via obtainPermanentIDsForObjects. I then save.
- Context A inherits the change, saves it out to the permanent store. Context C sees the change notification and merges the changes in. All is good so far.
- I grab the object from context C and change one of the properties. I then save. Context A inherits the change and saves it to disk.
This is where it gets weird. Context B never sees the change. There is a change notification I intercept like so:
-(void)contextDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *context = [notification object];
if(context!=_moc.parentContext)
return;
NSLog(#"Context %# did save, merging into %#, info:\n%#", context, _moc, [notification userInfo]);
[_moc mergeChangesFromContextDidSaveNotification:notification];
}
And I see the change go out, and seeing it merged in to context B. But the object I created in the first step never sees any changes.
I've traced this all back to one very specific thing. If I don't obtain a permanent object ID on the object in the very first step, the changes get merged back to the original object in context B just fine. But this leaves me without a reliable object ID to use in other contexts (as far as I know).
This seems like it could be a bug. I hope not. But for some reason when I obtain a permanent object ID it blocks changes from being merged back in from other contexts.
Has anyone else seen this behavior? Any solutions?
(I know changes from context C are making it in to A, and A is saving them to disk. If I relaunch the app the changes are there from disk.)
Related
I'm trying to use some of the new diffing classes built into iOS 13 along with Core Data. The problem I am running into is that controllerdidChangeContentWith doesn't work as expected. It passes me a snapshot reference, which is a reference to a
NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>
meaning I get a list of sections/Object ID's that have changed.
This part works wonderfully. But the problem comes when you get to the diffing in the collection view. In the WWDC video they happily call
dataSource.apply(snapshot, animatingDifferences: true)
and everything works magically, but that is not the case in the actual API.
In my initial attempt, I tried this:
resolvedSnapshot.appendItems(snapshot.itemIdentifiersInSection(withIdentifier: section).map {
controller.managedObjectContext.object(with: $0 as! NSManagedObjectID) as! Activity
}, toSection: .all)
And this works for populating the cells, but if data is changed on a cell (IE. the cell title) the specific cell is never reloaded. I took a look at the snapshot and it appears the issue is simply that I have references to these activity objects, so they are both getting updated simultaneously (Meaning the activity in the old snapshot is equivalent to the one in the new snapshot, so the hashes are equal.)
My current solution is using a struct that contains all my Activity class variables, but that disconnects it from CoreData. So my data source became:
var dataSource: UICollectionViewDiffableDataSource<Section, ActivityStruct>
That way the snapshot actually gets two different values, because it has two different objects to compare. This works, but it seems far from elegant, is this how we were meant to use this? Or is it just in a broken state right now? The WWDC video seems to imply it shouldn't require all this extra boilerplate.
I ran into the same issue and I think I figured out what works:
There are two classes: UICollectionViewDiffableDataSource and UICollectionViewDiffableDataSourceReference
From what I can tell, when you use the first, you're taking ownership as the "Source of Truth" so you create an object that acts as the data source. When you use the second (the data source reference), you defer the "Source of Truth" to another data source (in this case, CoreData).
You would instantiate a ...DataSourceReference essentially the same way as a ...DataSource:
dataSourceReference = UICollectionViewDiffableDataSourceReference(collectionView: collectionView, cellProvider: { (collectionView, indexPath, object) -> UICollectionViewCell? in
let identifier = <#cell identifier#>
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
<#cell configuration#>
return cell
})
And then later when you implement the NSFetchedResultsControllerDelegate, you can use the following method:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference)
{
dataSourceReference.applySnapshot(snapshot, animatingDifferences: true)
}
I watched the WWDC video as well and didn't see this referenced. Had to make a few mistakes to get here. I hope it works for you!
I am new to service fabric and started by looking at the MSDN articles covering the topic. I began by implementing the Hello World sample here.
I changed their original RunAsync implementation to:
var myDictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<int, DataObject>>("myDictionary");
while (!cancellationToken.IsCancellationRequested)
{
DataObject dataObject;
using (var tx = this.StateManager.CreateTransaction())
{
var result = await myDictionary.TryGetValueAsync(tx, 1);
if (result.HasValue)
dataObject = result.Value;
else
dataObject = new DataObject();
//
dataObject.UpdateDate = DateTime.Now;
//
//ServiceEventSource.Current.ServiceMessage(
// this,
// "Current Counter Value: {0}",
// result.HasValue ? result.Value.ToString() : "Value does not exist.");
await myDictionary.AddOrUpdateAsync(tx, 1, dataObject, ((k, o) => dataObject));
await tx.CommitAsync();
}
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
I also introduced a DataObject type and have exposed an UpdateDate property on that type.
[DataContract(Namespace = "http://www.contoso.com")]
public class DataObject
{
[DataMember]
public DateTime UpdateDate { get; set; }
}
When I run the app (F5 in visual studio 2015), a dataObject instance (keyed as 1) is not found in the dictionary so I create one, set UpdateDate, add it to the dictionary and commit the transaction. During the next loop, it finds the dataObject (keyed as 1) and sets UpdateDate, updates the object in the dictionary and commits the transaction. Perfect.
Here's my question. When I stop and restart the service project (F5 in visual studio 2015) I would expect that on my first iteration of the RunAsync that the dataObject (keyed as 1) would be found but it's not. I would expect all state to be flushed to its replica.
Do I have to do anything for the stateful service to flush its internal state to its primary replica?
From what I've read, it makes it sound as though all of this is handled by service fabric and that calling commit (on the transaction) is sufficient. If I locate the primary replica (in Service Fabric Explorer->Application View) I can see that the RemoteReplicator_xxx LastACKProcessedTimeUTC is updated once I commit the transaction (when stepping through).
Any help is greatly appreciated.
Thank you!
-Mark
This is a function of the default local development experience in Visual Studio. If you watch the Output window closely after hitting F5 you'll see a message like this:
The deployment script detects that there's an existing app of the same type and version already registered, so it removes it and deploys the new one. In doing that, the data associated with the old application is removed.
You have a couple of options to deal with this.
In production, you would perform an application upgrade to safely roll out the updated code while maintaining the state. But constantly updating your versions while doing quick iteration on your dev box can be tedious.
An alternative is to flip the project property "Preserve Data on Start" to "Yes". This will automatically bump all versions of the generated application package (without touching the versions in your source) and then perform an app upgrade on your behalf.
Note that because of some of the system checks inherent in the upgrade path, this deployment option is likely to be a bit slower than the default remove-and-replace. However, when you factor in the time it takes to recreate the test data, it's often a wash.
You need to think of a ReliableDictionary as holding collections of objects as opposed to a collection of references. That is, when you add an “object” to the dictionary, you must think that you are handing the object off completely; and you must not alter this object’s state in the anymore. When you ask ReliableDictionary for an “object”, it gives you back a reference to its internal object. The reference is returned for performance reasons and you are free to READ the object’s state. (It would be great if the CLR supported read-only objects but it doesn't.) However, you MUST NOT MODIFY the object’s state (or call any methods that would modify the object’s state) as you would be modifying the internal data structures of the dictionary corrupting its state.
To modify the object’s state, you MUST make a copy of the object pointed to by the returned reference. You can do this by serializing/deserializing the object or by some other means (such as creating a whole new object and copying the old state to the new object). Then, you write the NEW OBJECT into the dictionary. In a future version of Service Fabric, We intend to improve ReliableDictionary’s APIs to make this required pattern of use more discoverable.
Does CTreeCtrl::DeleteItem also delete all the node's sub-tree HTREEITEMs or must I recursively traverse the sub-tree myself and call DeleteItem on each one?
Looking at the MFC source code for CTreeCtrl::DeleteItem, it does this:
_AFXCMN_INLINE BOOL CTreeCtrl::DeleteItem(_In_ HTREEITEM hItem)
{
ASSERT(::IsWindow(m_hWnd));
return (BOOL)::SendMessage(m_hWnd, TVM_DELETEITEM, 0, (LPARAM)hItem);
}
Now looking at the documentation of TVM_DELETEITEM, it says the following (my emphasis):
Removes an item and all its children from a tree-view control
This would imply that recursively deleting is unnecessary, but I must admit that I've not tested it - the documentation may be misleading (as it sometimes is). One way to test is to note this line in the documentation:
The parent window receives a TVN_DELETEITEM notification code when
each item is removed.
So, by providing a handler for that message, you could verify that child items are deleted correctly.
From MSDN Docs on CTreeCtrl::DeleteItem where it says If hitem has the TVI_ROOT value, all items are deleted from the tree view control, I would infer that it will alo delete sub-nodes...
Is there an easier way to do something like the following in Core Data:
Entry *entry = [[Entry alloc] init];
entry.name = #"An Entry";
[entry save];
I realize you don't have to allocate an NSManagedObject, have to insert directly into the context like the following:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:[self managedObjectContext]];
But this is a lot of code. Also I would like to save by just messaging rather than have to save the entire context:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Could I put these in an NSManagedObject abstract class and have my managed objects extend that abstract class? Basically, I'm just trying to encapsulate more in my models and write less code in my controllers. Any help appreciated.
You can define your own subclass of NSManagedObject, and set all of your entities to use it.
The subclass can have whatever initialiser/save patterns you define, so long as it calls the proper parent class's initialiser.
You could have a +entity method, which might link to a statically defined context (this will restrict you to a single managed object context of course, but that's not always bad as long as you can also call the more primitive initialisers when you need a second context).
You might even have a +entityWithName: method.
As for saving the context, once again you can always define a subclass and add a simple -save method which saves the context and throws an exception if the save fails. You may choose to do this with a category extending the NSManagedObject class, instead of a subclass.
Note it is impossible to save just the change you made to that one entry object. You can only save all changes to an entire managed object context. If you need to save a single record, then you need to create a temporary managed object context, make a single change in it, then save the temporary context, and then sync the temporary context change over to all other managed object context's that currently exist in the app (there is an API to do make this complicated process relatively easy).
I don't like the code you posted to save the context, for several reasons:
Don't define a managedObjectContext variable that just points to self.managedObjectContext. In almost all situations that's an extra line of code for no benefit. At best you're making your code hard to read, at worst you might be introducing bugs.
Why are you checking if it is nil? Usually you should design your code so that it cannot ever be nil. Do the nil check in the constructor of your object, and if it's nil that is a critical failure. Your whole app is completely useless for the user, and you should make it clear to the user that they can't use the app by doing something drastic, such as a crash (an error alert first would be nice, but I wouldn't bother. I'd just throw an exception).
Why are you doing a check for hasChanges? I cannot think of many situations where you would need to do this check. Perhaps your app is allowing a user to make many changes, and then saving them several minutes later? This is bad. The context should be changed milliseconds after a group of changes are made, or else you're risking data loss. Your app could crash, the phone could run out of battery, or the user might receive a phone call and your app is consuming enough RAM that the OS will terminate it instantly in order to present the "incoming call" screen. You shouldn't need to check for hasChanges because you always perform a save operation immediately after making some changes.
As I kind of mentioned before, if the save fails you should present an error to the user then throw an exception. Avoid using NSLog() in deployment code, it's really only useful for development and beta builds.
Check out
NSManagedObject+ActiveRecord.h
Inside Restkit : http://restkit.org/
It is based on :
https://github.com/magicalpanda/MagicalRecord
I am using in RestKit app, but you can adopt it quite easily.
Good luck
I've looked around SO for similar answers, but my issue seems a litte different.
I have a UITableView that is tied to a NSFetchResultsController. The goal is to pull up some data, add a couple rows into the Context, and the table is automatically updated. Simple, right?
init -> empty table -> performFetch -> create some objects in the Context -> delegate sees this and updates my table.
I'm using the boilerplate NSFetchResultsController for noticing when the current context has been modified.
When I run this with a clean Simulator/iOS platform, the NSFetchController successfully recognizes that data in the Context has been updated. But if I run the app a second time, I get the following error:
CoreData: error: Serious application error. Exception was caught during Core Data change
processing. This is usually a bug within an observer of
NSManagedObjectContextObjectsDidChangeNotification. *** -[__NSArrayI objectAtIndex:]:
index 40 beyond bounds for empty array with userInfo (null)
The crash occurs on calling [self.tableView beginUpdates];
In my debugging I can see that '[fetchedResultsController fetchedObjects]' is completely empty and I think thats the problem - shouldn't this be updating with my test data since I modified the context? I'm using the Apple Recipe and CoreDataBooks examples as reference.
I think this is because you Data Modle in class just not fit the entity in you .xcdatamodeld file.