I want to be able to support copy and paste for a tableview row showing a core data entity. This entity has one attribute and two relationships. When I use the dictionary archiving technique recommended by Apple (from 'NSPersistentDocument Core Data Tutorial') I find that the relationships throw an error. Here's the essential piece of code where the problem occurs:
for (id sectionObject in selectedSectionsArray){
NSDictionary *thisDictionary = [sectionObject dictionaryRepresentation]; // 'sectionObject' has 1 attribute and 2 relationships (one-to-many)
[copyObjectsArray addObject:[sectionObject dictionaryRepresentation]];
}
NSPasteboard *generalPasteboard = [NSPasteboard generalPasteboard];
[generalPasteboard declareTypes:[NSArray arrayWithObjects:MSSectionsPBoardType, NSStringPboardType, nil] owner:self];
NSData *copyData = [NSKeyedArchiver archivedDataWithRootObject:copyObjectsArray]; // Here's where it crashes. ERROR MESSAGE: "-[NSManagedObject encodeWithCoder:] unrecognized selector sent to instance 0x22fd410"
Therefore, it seems the only way to copy a relationship to the pasteboard must be to archive its URI. In that case, I have to deal with the headache of referencing temporary ID's. Could someone please confirm that this is the case? Does it have to be so hard?
You haven't read that document closely enough. In the section Custom Employee Logic, it explains that relationships will not be copied for several reasons described there. It then explains how the code handles copying only specific attributes. It seems like you followed the document as far as choosing specific attributes to copy but not about leaving out relationships.
As for the error you're seeing,
-[NSManagedObject encodeWithCoder:] unrecognized selector sent to instance 0x22fd410
This happens because you're calling archivedDataWithRootObject: on a dictionary that contains objects that don't conform to NSCoding, specifically, your managed objects. Archiving like this only works automatically for property list types-- for everything else, you have to implement NSCoding, or you get this error.
Copying a managed object ID's URI is probably reasonable if you want to copy the relationships. If you're having problems with temporary object IDs, do one of the following:
Save changes
Call obtainPermanentIDsForObjects:error: for the objects to get permanent IDs without saving.
Related
I a developing a macOS commandline application in Xcode, which uses User Defaults. I have the following code for my User Defaults
if let configDefaults = UserDefaults.init(suiteName: "com.tests.configuration") {
configDefaults.set("myStringValue", forKey: "stringKey")
configDefaults.synchronize()
print(configDefaults.dictionaryRepresentation())
}
This will create my own .plist file in the ~/Library/Preferences folder. If I look into the file, I can see only my single value which I added, which is perfectly fine. But when I call dictionaryRepresentation() on my UserDefaults object, the there are a lot of other attributes (I guess from the shared UserDefaults object), like
com.apple.trackpad.twoFingerFromRightEdgeSwipeGesture or AKLastEmailListRequestDateKey
Looking into the documentation of UserDefaults, it seems that this has to do with the search list of UserDefaults and that the standard object is in the search list:
func dictionaryRepresentation() -> [String : Any]
Returns a dictionary that contains a union of all key-value pairs in the domains in the search list.
There are also the methods addSuite and removeSuite for a UserDefaults object, so I am guessing I need to remove the .standard suite from my configDefaults object, but I don't know the name, which should be used for that in the method.
Is it possible to remove the .standard defaults from the dictionary representation? I basically just want all of my own data in a dictionary, nothing more.
The reason I am trying to get only my values from the UserDefaults, is that a have a number of object of a custom type Connection (which store the configuration to connect to a server), which are saved in the UserDefaults. On program start I want to be able to load all objects into my app. Therefore I thought I could use dictionaryRepresentation(), as it would return all elements in the UserDefaults. But then there should be only my Connection objects in the dictionary, so that I can cast it to [String: Connection].
Given your purpose (in your latest edit of your question), what you should do is store a collection of Connection objects under a single key. Then, look up that key to get the collection.
It's not clear if the collection should be an array of Connection objects or a dictionary mapping strings to Connections. That's a choice you can make.
But, in any case, you shouldn't rely on the defaults being empty of everything else.
In other words, you would do:
UserDefaults.standard.set(yourStringToConnectionDictionary, forKey:"yourSingleKey")
and later:
let connectionMap = UserDefaults.dictionary(forKey:"yourSingleKey")
then look up Connections in the connectionMap by their name/ID/whatever.
Though the other solution proposed by Ken Thomases may be better from a design standpoint, I've found a solution that does exactly what I initially wanted. Calling
UserDefaults.standard.persistentDomain(forName: "com.company.TestApp.configuration")
Returns a dictionary containing only the values I've added to the domain com.company.TestApp.configuration, using
let configs = UserDefaults.init(suiteName: "com.company.TestApp.configuration")!
configs.set(someData, forKey: someKey)
Strangely in the Apple documentation says this about persistentDomain(forName:):
Calling this method is equivalent to initializing a user defaults object with init(suiteName:) passing domainName and calling the dictionaryRepresentation() method on it.
But this is not the case (see my question). Clarification on that subject is more than welcome.
I have simple cocoa app with 2 entities. They have one to one relationship between them.
In my only window, I have 2 NSTableViews, one display the customer and in another his address. If I change the relationship between the 2 entities in one to many, then everything in my app works (add, edit, delete).
However, if I change the relationship between the 2 entities in one to one, I can insert the customer, but when I attempt to insert record in the second NSTableView (address) app crashes with following error:
2015-10-09 03:50:28.357 TwoEntitiesRecord[1793:56879] -[__NSSetM managedObjectContext]: unrecognized selector sent to instance 0x608000040f90
2015-10-09 03:50:28.357 TwoEntitiesRecord[1793:56879] -[__NSSetM managedObjectContext]: unrecognized selector sent to instance 0x608000040f90
2015-10-09 03:50:28.361 TwoEntitiesRecord[1793:56879] (
I am not posting any code for the app, since I didn't write one. It is all done trough binding.
Any help will be deeply appreciated.
You have an incorrect binding. As you can tell from the error message, you are asking an NSSet for its managedObjectContext. An NSSet does not have a managed object context.
So, look at your bindings. One of them is probably bound to the property that represents a relationship, as that would be an instance of NSSet. Your binding is probably asking it for the MOC.
Just in case that someone has same issue: In one of my array controllers, the one for the address, in array controller attribute inspector I had unchecked auto rearrange content checkbox.
Honestly I have no idea why after I checked this checkbox app suddenly stop crashing on insert, but that was the solution for my problem.
I'm new to RestKit. I'm trying to map a many-to-many relationship, between entities Space and User. For the purposes of this question, assume all the Space objects are in the Core Data store correctly already.
In the REST API, I can make a single call to get all the users for a given space. First of all, when I initialise my network fetching class, I set up a mapping appropriate for parsing a list of users:
RKManagedObjectMapping *teamMapping = [RKManagedObjectMapping mappingForEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:self.manager.objectStore.primaryManagedObjectContext] inManagedObjectStore:self.manager.objectStore];
[teamMapping mapKeyPath:#"id" toAttribute:#"userID"];
[teamMapping mapKeyPath:#"name" toAttribute:#"name"];
teamMapping.primaryKeyAttribute = #"userID";
[self.manager.mappingProvider setMapping:teamMapping forKeyPath:#"users.user"];
Then, to populate the links for the users to the spaces, I do this:
NSArray *spaces = [self.managedObjectContext executeFetchRequest:[NSFetchRequest fetchRequestWithEntityName:#"Space"] error:nil];
for (QBSpace *space in spaces)
{
[self.manager loadObjectsAtResourcePath:[NSString stringWithFormat:#"space/%#/users", space.wikiName] usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
space.team = nil;
for (QBUser *user in objects)
{
[space addTeamObject:user];
}
};
}];
}
That is, I manually add the space->user link based on the returned array of objects.
Is that the correct way to do it? It seems to work fine, but I'm worried I'm missing a trick with regards to RestKit doing things automatically.
The returned XML for the file in question looks like
<users type="array">
<user>
<id>aPrimaryKey</id>
<login>loginName</login>
<login_name warning="deprecated">loginName</login_name>
<name>Real Name</name>
</user>
So there is nothing in the returned file to indicate the space that I'm trying to link to: I passed that in in the URL I requested, but there's nothing in the returned document about it, which is why I came up with this block based solution.
To all the RestKit users, does this look like an acceptable way to form many-to-many links?
If it works, it's ok?
You could think about having a Space custom ManagedObject. Have it respond to the RKObjectLoaderDelegate.
Then, when you get the reply, you could trigger the loadAtResourcePath from within its
objectLoader:didLoadObject:(id)object or objectLoader:didLoadObjectDictionary:
methods.
You could also make your Space class respond to the delegates from User and then tie them into their space at that point?
It might be better, but that would depend on what you are trying to achieve. Food for thought anyway. :)
I have a table view that is managed by an NSFetchedResultsController. I am having an issue with a find-or-create operation, however. When the user hits the bottom of my table view, I am querying my server for another batch of content. If it doesn't exist in the local cache, we create it and store it. If it does exist, however, I want to append that data to the fetched results controller and display it. I can't quite figure that part out.
Here's what I'm doing thus far:
The NSFetchedRequestController when initialized queries for the latest 100 results from the database (using setFetchLimit:). Even if there are 1000 rows, I only want 100 accessible at first.
Passing the returned array of values from my server to an NSOperation to process.
In the operation, create a new managed object context to work with.
In the operation, I iterate through the array and execute a fetch request to see if the object exists (based on its server id).
If the object doesn't exist, we create it and insert it into the operations' managed object context.
After the iteration completes, we save the managed object context, which triggers a merge notification on my main thread.
During the merge, the newly created objects from step 4 are inserted into the table, but any object that already existed and was just fetched does not. Here's the relevant code from my NSOperation
for (NSDictionary *resultsDict in self.results)
{
NSNumber *dbID = [NSNumber numberWithLong:[[resultsDict valueForKey:#"id"] longValue]];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:kResultEntityName inManagedObjectContext:moc]];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"(dbID == %#)", dbID]];
NSError *error = nil;
NSManagedObject *newObject = nil;
// Query the data store to see if the object exists
newObject = [[moc executeFetchRequest:fetchRequest error:&error] lastObject];
[fetchRequest release];
// If it doesn't exist, create it.
if ((tweet == nil))
{
// Create the NSManagedObject and insert it into the MOC.
}
}
What I want to pass to my main thread is the newly created objects plus any existing objects that may have been fetched in the fetch request each time the loop iterates.
I feel like it's something simple I'm missing, and could use a nudge in the right direction.
At this point, any objects that weren't locally cached in my Core Data store before will appear, but the ones that previously existed do not come along for the ride.
Can you explain this a little? I am not sure what you mean by the ones that previously existed. Is it objects that didn't match the filter on your table that now do after the retrieval or are you saying that the previous objects disappear from the table?
Also, how are you implementing the delegate methods for the NSFetchedResultsController? Are you doing a simple table reload or are you inserting/moving rows?
Update
There are a couple of ways to do this but they would require some experimentation on your part. I would first play with the idea of "touching" the objects that were fetched. Perhaps updating a 'lastAccessed' date or something so that these objects will come across the merge as "updated". I suspect that is the easiest path.
Baring that, another option would be to broadcast a notification of those objects back to the main thread (using their NSManagedObjectID as the carrier across the thread boundaries) so that you can manually update them; however that is less than ideal.
Hey Justin, could your fetchLimit be causing this?
The NSFetchedRequestController when
initialized queries for the latest 100
results from the database (using
setFetchLimit:). Even if there are
1000 rows, I only want 100 accessible
at first.
You will still have a limit of 100 results on the NSFetchedResultsController no matter how many managed objects you insert & merge.
Do you have a sorting turned on? If so, some of the inserted managed objects might be showing up because they displace some of the existing 100 due to the ordering of results.
I've gone through the same issues with fetching "pages" of results, and went with a lower-bound constraint on my fetch (using the sorted attribute in an NSComparisonPredicate) and adjusting that with the last item in the most recent "page" of results.
I also tried the fetchLimit approach, and that worked too. You don't need to remove & rebuild the NSFetchedResultsController, you can just adjust the fetchLimit of the fetchRequest:
tableMaxItems += 100;
[myFRC.fetchRequest setFetchLimit:tableMaxItems];
Here is how I ended up fixing this. Prior to merging in the changes from my NSOperation's MOC, I iterate through the values stored in NSUpdatedObjectsKey and touch them with willAccessValueForKey. To get them into NSUpdatedObjects key I took Marcus' advice above and added a lastAccessed property that I set to the current date in the NSOperation if the object already existed in the persistent store..
- (void)mergeChanges:(NSNotification *)notification
{
NSAssert([NSThread mainThread], #"Not on the main thread");
NSSet *updatedObjects = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
for (NSManagedObject *object in updatedObjects)
{
[[managedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
I have a Core Data based mac application that is working perfectly well until I save a file. When I save a file it seems like something changes in core data because my original fetch request no longer fetches anything. This is the fetch request that works before saving but returns an empty array after saving.
NSEntityDescription *outputCellEntityDescription = [NSEntityDescription entityForName:#"OutputCell"
inManagedObjectContext:[[self document] managedObjectContext]];
NSFetchRequest *outputCellRequest = [[[NSFetchRequest alloc] init] autorelease];
[outputCellRequest setEntity:outputCellEntityDescription];
NSPredicate *outputCellPredicate = [NSPredicate predicateWithFormat:#"(cellTitle = %#)", outputCellTitle];
[outputCellRequest setPredicate:outputCellPredicate];
NSError *outputCellError = nil;
NSArray *outputCellArray = [[[self document] managedObjectContext] executeFetchRequest:outputCellRequest
error:&outputCellError];
I have checked with [[[self document] managedObjectContext] registeredObjects] to see that the object still exists after the save and nothing seems to have changed and the object still exists. It is probably something fairly basic but does anyone know what I might be doing wrong? If not can anyone give me any pointers to what might be different in the Core Data model after a save so I might have some clues why the fetch request stops working after saving?
Edit
I have got as far as working out that it is the relationships that seem to be breaking after a save. If I omit the lines setting a predicate for the request, the request returns objects in the array. I have checked through the registeredObjects and it appears that the relationships are intact, but if I do something like save a file, re-open it and then check the registeredObjects the relationships are set to nil. I've opened a save file as an xml file and the relationships appear to be intact when the file is first saved.
I've added a screen shot of the part of the core data model were the relationships are broken. Does anyone have any idea why saving a file in core data might break the relationships? For reference I'm using the default implementation of save built into core data so there is no custom save code.
http://emberapp.com/splash6/images/littlesnapper/sizes/m.png
Edit
I have no -awakeFromFetch: methods that are triggering when this problem is caused.
I have sub-classed NSManagedObject for some of the problem objects, using the Core Recipes model for KVO:
+(void)initialize
{
if (self == [OutputCell class])
{
NSArray *nameKeys = [NSArray arrayWithObjects:#"cell", #"sheet", #"table", nil];
[self setKeys:nameKeys
triggerChangeNotificationsForDependentKey:#"cellTitle"];
NSArray *measuresKeys = [NSArray arrayWithObjects:#"fivePercentile", #"maximum", #"mean", #"median",#"minimum",#"ninetyFivePercentile",#"standardDeviation",nil];
[self setKeys:measuresKeys
triggerChangeNotificationsForDependentKey:#"analysisResults"];
}
}
This method doesn't seem to be firing during or after a save so it doesn't seem to be this that is causing the problem. I'm currently going through all the other methods in the code to find if any of them happen to get called during or after a save.
Edit
Following on from Marcus' suggestion below I've managed to make a fetch request fail before the model is saved. My problem now is the message this getting returned in the console when it fails:
HIToolbox: ignoring exception '+entityForName: could not locate an NSManagedObjectModel for entity name 'OutputCell'' that raised inside Carbon event dispatch
The console message is logged following this call:
NSEntityDescription *outputCellEntityDescription = [NSEntityDescription entityForName:#"OutputCell"
inManagedObjectContext:[[self document] managedObjectContext]];
Should the extra ' following the OutputCell in the console message be there? Do objects normally have this extra ', or has it come from somewhere? If it has come from somewhere and is causing the fetch request to fail, does anyone have any bright ideas where this might have come from or how I might track it's source down?
Sounds like you are setting something to nil somewhere and causing you to get nil back. I would walk through your save and fetch code in the debugger and look for objects being set to nil when you do not expect it.
update
Do you have any code anywhere that can be manipulating the relationships? Perhaps something in the -awakeFromFetch: that is causing the relationships to get corrupted?
If they are saving correctly the first time and then failing then that truly points at something in your code corrupting those relationships. Are you subclassing NSManagedObject for these objects? If so are you by chancing overriding the -init... method?
update
That last tick should definitely not be there. Check your fetch request, this might all boil down to a simple typo in a string somewhere...