I want to get the inserted and the update objects from NSPersistentStoreDidImportUbiquitousChangesNotification to do some check on them.
Objects can be of two kind of classes: "Alpha" and "Beta". Both classes have the
property (nonatomic, retain) NSString* name
which is the one I should check.
How do I get it?
The following code doesn't work because it says "name" is an unknown selector:
-(void) checkObjects
{
NSDictionary *insertedObjects = [[note userInfo] objectForKey: #"inserted"];
NSDictionary *updatedObjects = [[note userInfo] objectForKey: #"updated"];
for(NSManagedObject *obj in insertedObjects){
if([obj.entity.managedObjectClassName isEqualToString:#"Alpha"]){
Alpha *alpha = (Alpha*) obj;
if (alpha.name isEqualToString:#"xyz"){
//Do some check
}
}else if([obj.entity.managedObjectClassName isEqualToString:#"Beta"]){
Beta *beta = (Beta*) obj;
if (beta.name isEqualToString:#"xyz"){
//Do some check
}
}
}
}
If I change:
Alpha *alpha = (Alpha*) obj;
Beta *beta = (Beta*) obj;
To:
Alpha *alpha = (Alpha*) obj.entity;
Beta *beta = (Beta*) obj.entity;
alpha = Alpha <-- It is the name of the class, not of the object I want!
beta = Beta <--- It is the name of the class, not of the object I want!
When you get NSPersistentStoreDidImportUbiquitousContentChangesNotification, the objects in userInfo are not managed objects, they're managed object IDs. That is, instances of NSManagedObjectID. If you want to look up attributes on the managed object, you need to get the object corresponding to the ID. Something like
NSDictionary *insertedObjectIDs = [[note userInfo] objectForKey:NSInsertedObjectsKey];
for(NSManagedObjectID *objID in insertedObjects) {
NSError *error = nil;
NSManagedObject *obj = [self.managedObjectContext existingObjectWithID:objID error:&error];
....continue...
}
You may need to change that if self doesn't have a managed object context.
Also, on a slight tangent-- it's generally better to use NSInsertedObjectsKey instead of #"inserted" and NSUpdatedObjectsKey instead of #"updated". Apple probably won't change the key names, but they could, so using the key names instead of string literals is a better choice.
Related
With literals syntax one can use NSDictionary *dictionary like this to get the objectForKey
NSDictionary * dictionary;
id object = dictionary[key];
But if the type of dictionary is of type id and you try to write
id dictionary;
id object = dictionary[key];
This will work until if your dictionary was really a dictionary otherwise it would crash.
The solution for that would be to have a method
-(id)safeObject:(id)object forKey:(id)aKey {
if (![object isKindOfClass:[NSDictionary class]]) {
return nil;
}
return [object objectForKeyedSubscript:aKey];
}
So now when I call it like this
id dictionary;
id object = [self safeObject:dictionary forKey:key];
This won't crash. But the problem with this one is that If I have to go deeper in the nested dictionary for example
id object = dictionary[key1][subKey1][subsubKey1];
It is really convenient to write with the literal syntax with old syntax it would be something like
id mainObject = [self safeObject:dictionary forKey:key1];
id subObject = [self safeObject:mainObject forKey:subKey1];
id object = [self safeObject:subObject forKey:subsubKey1];
So not that much readable. I want to have this solution with new literal syntax is this possible?
You could use valueForKeyPath, e.g.
id dictionary = #{#"key":#{#"subkey" : #{ #"subsubkey" : #"value"}}};
id object = [self safeObject:dictionary];
id value = [object valueForKeyPath:#"key.subkey.subsubkey"];
Also slightly change safeObject only to check if it's a dictionary,
- (id)safeObject:(id)object {
if (![object isKindOfClass:[NSDictionary class]]) {
return nil;
}
return object;
}
Hope this helps, is this what you're looking for?
I have a custom object saved to NSUserDefaults. I don't use Core Data as it is way to much for my one object. I am trying to create a method that takes a new object of this type and combine it with the data in a saved object of this type.
Here is what I have so far, but it is erroring out because some objects are of different types (BOOL, NSNumber, NSString, NSDictionary, etc..).
if (request) {
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList(
[AmbulanceRequest class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *name = [[NSString alloc] initWithUTF8String:property_getName(property)];
NSString *attributesString = [[NSString alloc] initWithUTF8String:
property_getAttributes(property)];
if( nil == [request valueForKey:name]) { <--- error here BOOL cannot be nil
[request setValue:[savedRequest valueForKey:name] forKey:name];
}
}
}
Error I am getting is: [NSKeyedUnarchiver initForReadingWithData:]: data is NULL
UDPATE:
I am just using decoder and encoder. When encoding and decoding in general I never have issues. Just in the code above. For example, here is one property that is throwing the error.
Of course these are in the appropriate encodeWithCoder and initWithCoder methods.
#property (nonatomic, assign) BOOL * trip;
self.roundTrip = [decoder decodeBoolForKey:#"trip"];
[encoder encodeBool:self.roundTrip forKey:#"trip"];
//// UPDATE 2: //////
Here is the full code of my method. As you can see the object is pulled back and decoded way before any of this iterating through the properties happens. I have two BooRequests the old one and a new one. I am trying to take the properties of the old one and save them to the new one if the new one doesn't have those same properties.
+(void) saveRequest:(BooRequest*)request {
// If current request then we need to save off the unSetPings
BooRequest *savedRequest = [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults]
objectForKey:kCurrentActiveRequest]];
if(request){
unsigned int numberOfProperties = 0;
objc_property_t *propertyArray = class_copyPropertyList([BooRequest class], &numberOfProperties);
#warning YO!!! this FOR LOOP is *NOT* done and probably doesn't even work!!!
for (NSUInteger i = 0; i < numberOfProperties; i++)
{
objc_property_t property = propertyArray[i];
NSString *name = [[NSString alloc] initWithUTF8String:property_getName(property)];
NSString *attributesString = [[NSString alloc] initWithUTF8String:property_getAttributes(property)];
if ([[[NSUserDefaults standardUserDefaults] dictionaryRepresentation].allKeys containsObject:#"keyForNonMandatoryObject"]) {
id thing = [request valueForKey:name];
if(NULL == [request valueForKey:name] || nil == thing){
[request setValue:[savedRequest valueForKey:name] forKey:name];
}
}
if(savedRequest) {
request.unSentPings = [[NSMutableArray alloc] initWithArray:savedRequest.unSentPings];
}
request.nextButtonToShowIndex = savedRequest.nextButtonToShowIndex;
} // end for loop
} else {
STPingSender *sender = [[STPingSender alloc] init];
BooRequest *req = [BooRequest currentRequest];
if(req && req.unSentPings && [req.unSentPings count] > 0){
[sender sendToServer:((BooRequest*)[BooRequest currentRequest]).unSentPings];
}
}
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:request]
forKey:kCurrentActiveRequest];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Here is my ultimate goal I have a BooRequest that is saved locally from the server. I make changes to it locally. 90% of the time their are not updates from the server which I need to update locally. But when there are I need the new local data merged with the new server data. But the local data I add is only used locally, the server never uses this additional data. It only needs to be merged locally. The reason I am iterating is that I don't want to have to update this method everytime this Objective-c class gets a new property added to it. I want some dynamic code that just says iterate through the properties and when you find the properties that match on the old object and new object, copy the old object properties into the new ones.
id dataObject = [[NSUserDefaults standardUserDefaults] objectForKey:kCurrentActiveRequest];
BooRequest *savedRequest = [NSKeyedUnarchiver unarchiveObjectWithData:dataObject];
when dataObject is nil, which means there is no such key in user defaults, the second line will emit the [NSKeyedUnarchiver initForReadingWithData:]: data is NULL error, in this case, you should not unarchive it, if the new BooRequest object is not nil, you can save the new object to user default directly.
id thing = [request valueForKey:name];
For properties that are of primitive type such as BOOL or NSInteger, the thing is NO and 0 respectively when you don't set the value of the properties, which means the if(NULL == [request valueForKey:name] || nil == thing) check is always false and the new request object is not updated from the old one. I suggest you to change those properties to NSObject type, so the check will pass.
#property (nonatomic, strong) NSNumber* boolProperty;
#property (nonatomic, strong) NSNumber *integerProperty;
Hope that helps.
I'm not sure why you are trying to store a custom object in NSUserDefaults in the first place... that is usually more pain than it is worth.
Why not just create an NSDictionary and save that to NSUserDefaults? Then your BooRequest class can init from dictionary as well as merge from dictionary. You can also create a method -saveToUserDefaults method in BooRequest that saves it back when completed. And depending on what BooRequest actually does you may not need a custom subclass if all you are doing is storing some key-value pairs.
It would be helpful to see what your local data looks like, and what your remote data looks like, and then the desired outcome.
You shouldn't blindly iterate through all the object properties but choose those worth being saved.
As said in another answer, don't use NSUserDefaults for this. If you don't want to use Core Data I recommend serializing Property Lists. As a bonus you'll be able to inspect/manually edit your object values.
How can I get the NSManagedObjectID of an object directly after saving?
I've tried using the NSNotification NSManagedObjectContextDidSaveNotification and getting the updated/inserted values and getting the object id from one (the only) managed object, but it's giving me "Unrecognized Selector" when I try to grab the object id.
Can I even get the Object Id right after I save?
- (void)handleDidSaveNotification:(NSNotification *)note
{
NSDictionary *dict = [note userInfo];
NSDictionary *updatedDict = [dict valueForKey:#"updated"];
NSLog(#"Notification: %#", dict);
NSLog(#"Updated Info: %#", updatedDict);
NSManagedObject *core = [updatedDict valueForKey:#"entity"];
NSManagedObjectID *objectId = [core objectID];
}
You are trying to set a dictionary (updatedDict) when the returned data is a NSSet.
you might simply need to get it from the set collection it is in ...
NSSet* s = [dict valueForKey:#"updated"];
[s valueForKey:#"objectID"]
This will return a set of NSManagedObjectIDs.
See NSSet on how to access objects.
In my iOS application, I am doing a heavy weight migration of an Entity, where I convert the type of an attribute from Integer64 in the old data model to a type of String in the new data model. The conversion of this attribute appears to be working fine. However, the problem I have run into is that another attribute of the same Entity is null (which is what it is supposed to be), and when this attribute is being migrated to the same entity in the new schema, an error is being flagged:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "date"; desired type = NSDate; given type = NSNull; value = <null>.'
I am not sure why this error is being flagged, because the attribute is marked as optional in the data model. I would like to simply migrate the nil value of the attribute "as is" to the same attribute in the new data model without any changes or modifications.
Here is the relevant code of my subclass of NSEntityMigrationPolicy that I am using:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError *__autoreleasing *)error {
NSManagedObject *newObject;
NSEntityDescription *sourceInstanceEntity = [sInstance entity];
//correct entity? just to be sure
if ([[sourceInstanceEntity name] isEqualToString:#"MyEntity"]) {
newObject = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:[manager destinationContext]];
//obtain the attributes
NSDictionary *keyValDict = [sInstance committedValuesForKeys:nil];
NSArray *allKeys = [[[sInstance entity] attributesByName] allKeys];
//loop over the attributes
for (NSString *key in allKeys) {
//get key and value
id value = [keyValDict objectForKey:key];
if ([key isEqualToString:#"integerType"]) {
//here retrieve old value
NSNumber *oldValue = [keyValDict objectForKey:key];
//here do conversion as needed
NSString *stringType = [oldValue stringValue];
//then store new value
[newObject setValue:stringType forKey:key];
} else { //no need to modify the value, Copy it across -- this is where I believe the problem is
[newObject setValue:value forKey:key];
}
}
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
}
return YES;
}
Can anyone see what it is I'm doing wrong?
The issue is that you are getting back a NSNull class in your dictionary which means you are trying to pass the wrong type of class to the new NSManagedObject instance.
If you read the documentation on -committedValuesForKeys: you will see that:
nil values are represented by an instance of NSNull.
Which is your problem.
Personally I would not approach the values this way. Instead I would do something like:
NSDictionary *allAttributes = [[sInstance entity] attributesByName];
for (NString *key in allAttributes) {
id value = [sInstance valueForKey:key];
if ([key isEqualToString:#"integerType"]) {
//here retrieve old value
NSNumber *oldValue = [keyValDict objectForKey:key];
//here do conversion as needed
NSString *stringType = [oldValue stringValue];
//then store new value
[newObject setValue:stringType forKey:key];
} else { //no need to modify the value, Copy it across -- this is where I believe the problem is
[newObject setValue:value forKey:key];
}
}
Whereby you are grabbing the values directly from the object and you will get a proper nil back.
I am using core data and have been using code like this:
[self.form setValue:self.comments.text forKey:#"comments"];
I want to put code like this into a loop, all my coredata names are the same as the property name. How can I say forKey:self.comments.name and get the same outcome as above or something like that?
EDIT:
If this is not possible, is there another way to set a ton of values into coredata from properties? I have 50+ attributes and properties alike that need to be set and would like to avoid using what im doing now.
If you really want it, you may use these functions from objc/runtime.h:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) // To get properties declared by a class.
const char *property_getName(objc_property_t property) // To get the name of one property
Something like this:
unsigned int propCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &propCount);
for(int idx = 0; idx < propCount; idx++) {
objc_property_t prop = *(properties + idx);
NSString *key = #(property_getName(prop));
NSLog(#"%#", key);
}
There really is no substitute for reading the docs on CoreData as the patterns for use and syntax will not be obvious at all without a little legwork.
That said, you typically fetch an instance of your NSManagedObject subclass from the data store:
NSManagedObjectContext* moc = [delegate managedObjectContext];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
NSError *error;
_enabledFilters = [NSMutableArray arrayWithArray:[moc executeFetchRequest:request error:&error]];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
In this example I now have an array of instances of my NSManagedObject called "Filter"
Then you can select the appropriate instance to reference, and access all of it's attributes with simple dot syntax.
Filter* thisFilter = (Filter*)[_displayFilters objectAtIndex:indexPath.row];
cell.label.text = thisFilter.name;
cell.label.backgroundColor = [UIColor clearColor];
NSString*targetName = thisFilter.imageName;
UIImage *image = [UIImage imageNamed:targetName];
cell.image.image = image;
Now I've taken info from my persistent data store, and used it within my app.
Going the other way and writing to an instance within your data store is only slightly different, in that you directly set the attributes of an instance of your NSManagedObject subclass, and then call save on the context to push any changes down to the store.
TL;DR - you owe it to yourself to spend an hour or two with the CoreData docs...
One way would be to declare an array of the attributes yourself.
NSArray *attributes = [NSArray arrayWithObjects:..., #"comments", .., nil]; // or a NSSet
for(NSString *attribute in attributes){
NSString *text = [[self performSelector:NSSelectorFromString(attribute)] text]; // presuming that it's safe to call 'text' on all your properties
[self.form setValue:text forKey:attribute];
}
Or you can use this if you want all the attributes of your core data model.