iOS Magical record import from array - ios

Hi I'm making a synchronise function that update database when receive JSON response from server. I want the import only take place if there are different data (new record or update existing record) (To increase performance) (Using coredata and magicalRecord)
Here is my JSON parser method
- (void)updateWithApiRepresentation:(NSDictionary *)json
{
self.title = json[#"Name"];
self.serverIdValue = [json[#"Id"] integerValue];
self.year = json[#"Year of Release"];
self.month = json[#"Month of Release"];
self.day = json[#"Day of Release"];
self.details = json[#"Description"];
self.coverImage = json[#"CoverImage"];
self.thumbnail = json[#"Thumbnail"];
self.price = json[#"Buy"];
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:#"dd/MMMM/yyy"];
NSDate *date = [formatter dateFromString:[NSString stringWithFormat:#"%#/%#/%#",self.day,self.month,self.year]];
self.issueDate = date;
}
And my import method
+ (void)API_getStampsOnCompletion:(void(^)(BOOL success, NSError *error))completionBlock
{
[[ApiClient sharedInstance] getStampsOnSuccess:^(id responseJSON) {
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
NSMutableArray *stamps = [[NSMutableArray alloc]init];
[responseJSON[#"root"] enumerateObjectsUsingBlock:^(id attributes, NSUInteger idx, BOOL *stop) {
Stamp *stamp = [[Stamp alloc]init];
[stamp setOrderingValue:idx];
[stamp updateWithApiRepresentation:attributes];
[stamps addObject:stamp];
}];
[Stamp MR_importFromArray:stamps inContext:localContext];
} onFailure:^(NSError *error) {
if (completionBlock) {
completionBlock(NO, error);
}
}];
}
I'm getting error
CoreData: error: Failed to call designated initializer on NSManagedObject class 'Stamp'
2016-08-02 23:52:20.216 SingPost[2078:80114] -[Stamp setOrdering:]: unrecognized selector sent to instance 0x78f35a30
I have checked my Json parser is working fine. The problem is with my import method. I don't know what wrong with the function. Any help is much appreciate. Thanks!

The error message clearly describes the exact problem. You do this:
Stamp *stamp = [[Stamp alloc]init];
But init is not the designated initializer for NSManagedObject unless you added init in your subclass (which you didn't mention doing). You must call the designated initializer, which is initWithEntity:insertIntoManagedObjectContext:. There's also a convenience factory method on NSEntityDescription called insertNewObjectForEntityForName:inManagedObjectContext:. Either of those will work, but calling init will not.

Related

UIButton that increments an array from Core Data, done from another class

I have a button that whenever it is pressed is supposed to increment by one, which as a result will display a new string into a textfield. I have a Core Data manager class that contains a method which is supposed to retrieve my arrays, however whenever I press the button the app crashes. I'm left with an error stating ...
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UsefulCodes rangeOfCharacterFromSet:]: unrecognized selector sent to instance 0x6000000a3f60'"
"UsefulCodes" is the name of my Core Data entity, and "codeName" and "codeDescription" are the attributes fro my presetList and codeDescArray respectively. I think its not working because I'm trying to put a Core Data object into a textfield, but all the objects in the arrays are strings, how would I get it to pass successfully into the textfield? I'll post relevant code below.
From CoreDataManager -
#implementation CoreDataManager
+(CoreDataManager*)sharedInstance {
static CoreDataManager *sharedObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedObject = [[CoreDataManager alloc] init];
});
return sharedObject;
}
-(void)storeListFirstTime {
//http://morsecode.scphillips.com/morse.html
NSArray* presetList = [NSArray arrayWithObjects:#"AS",
#"BCNU",
#"CL",
#"CT",
#"CUL",
#"K",
#"QSL",
#"QSL?",
#"QRX?",
#"QRV",
#"QRV?",
#"QTH",
#"QTH?",
#"R",
#"SN",
#"SOS",
#"73",
#"88",
nil];
NSArray* codeDescArray = [NSArray arrayWithObjects:#"Wait",
#"Be seeing You",
#"Going off air",
#"Start Copying",
#"See you later",
#"Over",
#"I acknowledge receipt",
#"Do you acknowledge",
#"Should I wait",
#"Ready to copy",
#"Are you ready to copy?",
#"My location is ...",
#"What is your location?",
#"Roger",
#"Understood",
#"Distress message",
#"Best regards",
#"Love and kisses",
nil];
//Saves the initial list of items
for(int i = 0; i < presetList.count; i++) {
NSManagedObjectContext *context = [self context];
UsefulCodes *codeObj = [NSEntityDescription insertNewObjectForEntityForName:#"UsefulCodes"
inManagedObjectContext:context];
codeObj.codeName = [presetList objectAtIndex:i];
codeObj.codeDescription = [codeDescArray objectAtIndex:i];
NSLog(#"CODEOBJ: %#", codeObj);
}
[self saveContext];
}
//THIS IS THE METHOD I AM CALLING FOR MY BUTTON METHOD
-(NSArray*)fetchAllRecords {
NSManagedObjectContext *context = [self context]; //this context is conected to persistant container
NSError *error = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"UsefulCodes"];
NSArray *records = [context executeFetchRequest:request
error:&error];
if(error == nil && records.count > 0) {
return [records copy];
}
else {
//Handle error by returning new array
return [NSArray new];
}
}
The above code just sets the initial values I hard-coded into my arrays (I'm only interested in getting the values from presetList array to display in textfield) as well as accommodating any new codes and descriptions I've added to any arrays. Now here is the method in my "MainViewController" responsible for cycling through my array.
In header file I declare a property called num.
#property(nonatomic)int num;
This is the method in implementation file.
- (void)getQuickMorseCode {
NSArray *array = [[CoreDataManager sharedInstance] fetchAllRecords];
if(_num == 0 || _num > 0) {
if(_num >= array.count){
_num = 0;
}
//GETTING THE SPECIFIC INDEX OF THE ARRAY, THIS IS WHERE THE PROBLEM LIES
self.morseTextfield.text = array[_num];
[self convertInput:self.morseTextfield.text];
[self buttonAppear];
_num++;
}
}
My goal is to display the strings that are in the presetList array in my CoreDataManager file, but I'm unable to get that to pass into my textfield. Any insight to my problem is welcomed.
Your fetchAllRecords method returns an array of UsefulCodes objects. So each element of array is a UsefulCodes object. You cannot therefore assign the element to the text property of a textfield. That's the cause of your error.
You need instead to extract the relevant string from the UsefulCodes object. From your other code, you store the presetList values in the codename attribute of the UsefulCodes objects:
codeObj.codeName = [presetList objectAtIndex:i];
So, reverse that process to obtain the relevant string from the codename attribute:
UsefulCodes *codeObj = (UsefulCodes *)array[_num];
NSString *presetListValue = codeObj.codeName;
self.morseTextfield.text = presetListValue;

Core Data Lazy Loading with NSPrivateQueueConcurrencyType and custom setter not working

Problem: Fetching a managed object using a background thread does not lazy load the NSManaged object relationship correctly when the NSManaged object that is related has a custom setter. Doing fetch on main thread with main concurrency type works without a problem. Why is this?
Work Around: If I create a custom getter on the relationship object and check for nil, I can force the NSManaged object to load by calling other variables that don't have custom setter methods.
Background
The core data layout is pretty simple. I have a Game managed object and a Turn managed object. The turn object is a one to one relationship with the game object. I always fetch the game object in order to access the turn object. TurnImp and GameImp are implementation classes that inherit from the Game and Turn object so I don't put getter/setter methods in auto generated code.
Code
The Fetch
//
//Stick command on background
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
//
//Load Game
//
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
CoreDataHelper *coreDataHelper = appDelegate.coreDataHelper;
NSManagedObjectContext *childMOC = [coreDataHelper createChildManagedObjectContext];
//the request
NSFetchRequest *fetchRequest = [NSFetchRequest new];
//the object entity we want
NSEntityDescription *entity = [NSEntityDescription entityForName:GAMEIMP_GAME inManagedObjectContext:childMOC];
[fetchRequest setEntity:entity];
//the predicate rules, the what
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"gameId == %#", #"1404110671234567"];
[fetchRequest setPredicate:predicate];
//the sorting rules
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:GAMEIMP_OBJECT_ID ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//Fetch results
NSFetchedResultsController *resultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:childMOC sectionNameKeyPath:nil cacheName:nil];
NSError *error;
BOOL success = [resultsController performFetch:&error];
GameImp *game;
if (success) {
game = [resultsController.fetchedObjects objectAtIndex:0];
} else {
NSLog(#"Unable to get game. Error: %#", error);
}
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call custom getter method. Now 3 is returned.
lastRoundReward = [turnImp getLastRoundReward];
}
This childMOC creation
-(NSManagedObjectContext*) createChildManagedObjectContext {
NSManagedObjectContext *childMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childMOC.parentContext = self.mainManagedObjectContext;
return childMOC;
}
TurnImp Header
#interface TurnImp : Turn
#property(atomic) BOOL isValid;
- (void) setLastRoundReward: (int) lastRoundReward;
- (int) getLastRoundReward;
#end
TurnImp M
#implementation TurnImp
#synthesize isValid;
#synthesize lastRoundReward = _lastRoundReward;
/**
* Set the last round reward
* #param -
* #return -
*/
- (void) setLastRoundReward: (int) lastRoundReward {
_lastRoundReward = [NSNumber numberWithInt:lastRoundReward];
}
/**
* Get the int value of lastRoundReward
*/
- (int) getLastRoundReward {
//Note - HACK! Lazy loading not working, try another member
if (self.lastRoundReward == nil) {
//Force load
NSString *objectId = self.objectId;
}
return [self.lastRoundReward intValue];
}
Change childMoc to mainMoc and it works. MainMoc Code
//create the main MOC
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
More After Fixed Concurrency issue
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (results == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
//Issue is here!!! Should be 3, instead 0 because lastRoundReward is nil.
int lastRoundReward = [turnImp.lastRoundReward intValue];
//Work around, call variable objectId (not same as ObjectId)
NSString *objectId = turnImp.objectId;
//not it's 3...
lastRoundReward = [turnImp.lastRoundReward intValue];
}
}];
Work Around
I removed the following from TurnImp and it works as expected with the relationships.
#synthesize lastRoundReward = _lastRoundReward;
First, I have to confess that I have no idea what your problem statement means - what is lazy loading of a relationship supposed to do anyway?
However, a quick glance at your code reveals that you are creating a MOC with NSPrivateQueueConcurrencyType yet you are not properly wrapping its use inside an appropriate performBlock invocation.
When you clearly violate the Core Data Concurrency guidelines, you are playing in dangerous waters and will get undefined behavior.
Also, why create an instance of NSFetchedResultsController just to perform a fetch? That's overkill. Simply use a fetch request. Like so...
[childMOC performBlock:^{
// Execute the fetch on the childMOC and do your other work.
NSError *error;
NSArray *results = [childMOC executeFetchRequest:fetchRequest error:&error];
if (result == nil) {
// Handle error
} else if (results.count == 1) {
GameImp *game = [results firstObject];
TurnImp *turnImp = game.turn;
int lastRoundReward = [turn2.lastRoundReward intValue];
}
}];

Code not retrieving/displaying date from Parse.com in a UILabel

I am trying to retrieve the updatedAt section of a parse object and displaying it as a label. I have created a date formatter yet when I convert it to a string it just becomes a null value. Here is the code:
- (void)viewDidLoad {
[super viewDidLoad];
PFQuery *BizarroTime = [PFQuery queryWithClassName:#"Bizarro"];
[BizarroTime getObjectInBackgroundWithId:#"MkoIkCBMdP" block:^(PFObject *Bizarro, NSError *error) {
NSDate *BizarroDate = Bizarro[#"updatedAt"];
NSDateFormatter *df = [NSDateFormatter new];
[df setDateFormat:#"MMMM dd 'at' HH:mm"];
self.BizarroUpdatedAt.text = [df stringFromDate:BizarroDate];
}];
}
Any help with this would be awesome! Thanks!
To make danh's message more concise for future answer-seekers, the updatedAt field of a Parse PFObject* object cannot be accessed as if it's a value in the object's dictionary, ex.:
object[#"updatedAt"];
But must instead be accessed using Parse's updatedAt method:
[object updatedAt];
You should check that the returned object is non-nil. If there is an object, the way to get it's updated date is via the PFObject method called updatedAt ...
[BizarroTime getObjectInBackgroundWithId:#"MkoIkCBMdP" block:^(PFObject *Bizarro, NSError *error) {
if (Bizarro) {
NSDate *BizarroDate = [Bizarro updatedAt];
// format as you have it
} else {
NSLog(#"%#", error);
}
}];

Receiving An Intermittent EXC_BAD_ACCESS When Updating an EKEvent

I have a Cordova app with a custom plugin that is creating and updating EKEvents in the user's iCloud calendar.
I am using the following function to find a specific EKEvent based on its URL:
- (EKEvent*) getEventWithURL:(NSString *)pstrURL store:(EKEventStore *)pStore
{
EKCalendar* calendar = [self getCalendarByName:mstrCalendarName store:pStore createIfDoesNotExist:false];
NSArray* calendars = [[NSArray alloc] initWithObjects: calendar, nil];
NSDateFormatter *sDate = [[NSDateFormatter alloc] init];
[sDate setDateFormat:#"yyyy-MM-dd HH:mm"];
NSDate *myStartDate = [sDate dateFromString:#"2013-11-01 00:00"];
NSDateFormatter *eDate = [[NSDateFormatter alloc] init];
[eDate setDateFormat:#"yyyy-MM-dd HH:mm"];
NSDate *myEndDate = [eDate dateFromString:#"2014-12-31 23:59"];
NSPredicate *predicate = [pStore predicateForEventsWithStartDate:myStartDate endDate:myEndDate calendars: calendars];
// Fetch all events that match the predicate.
NSArray *events = [pStore eventsMatchingPredicate:predicate];
EKEvent *foundEvent = nil;
EKEvent *event;
for (id oEvent in events)
{
event = (EKEvent *)oEvent;
if ([event.URL isEqual:[NSURL URLWithString:pstrURL]])
{
foundEvent = event;
break;
}
}
return foundEvent;
}
It is then modified (start and end dates are changed) and saved in another method with the following code:
EKEvent *myEvent = nil;
BOOL saved = false;
EKCalendar* calendar = nil;
if(pstrCalendarTitle == nil)
{
calendar = pStore.defaultCalendarForNewEvents;
}
else
{
calendar = [self getCalendarByName: pstrCalendarTitle store: pStore createIfDoesNotExist:true];
}
// find event if it exists
myEvent = [self getEventWithURL:[NSString stringWithFormat: #"custom://%#", pstrTSDID ] store:pStore];
// if an event wasn't found, create a new one
if (myEvent == nil)
{
myEvent = [EKEvent eventWithEventStore: pStore];
}
// set all the fields to new values
myEvent.title = pstrTitle;
myEvent.location = pstrLocation;
myEvent.notes = pstrMessage;
myEvent.startDate = pdtStartDate;
myEvent.endDate = pdtEndDate;
myEvent.calendar = calendar;
myEvent.URL = [NSURL URLWithString:[NSString stringWithFormat: #"custom://%#", pstrTSDID ]];
// only add an alarm if one hasn't been created already
if ([[myEvent alarms] count] == 0)
{
EKAlarm *reminder = [EKAlarm alarmWithRelativeOffset:-2*60*60];
[myEvent addAlarm:reminder];
}
When creating a whole bunch of EKEvents (about 30 in a row) I don't get the EXC_BAD_ACCESS error, however when updating events I get the EXC_BAD_ACCESS error intermittently. Sometimes it is on the first update, and sometimes I am able to update 10 before seeing the error, which then crashes my app.
I suspect it may have something to do with the foundEvent variable not being retained, however my project is using ARC so it is my understanding that I don't need to do any memory management tasks. Unless ARC is getting confused with the way the event variable is being cast and then passed around in the loop in getEventWithURL?
For full disclosure, I do have Enable Zombie Objects enabled and the stack trace that I see doesn't reference any of my methods specifically, it starts at start_wqthread and then references some EKEventStore _databasechangedexternally internal methods.
For what it's worth I wasn't able to figure out what the issue was with modifying the events so I am instead deleting the event (if it exists) and creating a new one. My getEventWithURL method is now called deleteEventWithURL which does the removal of the event from the store. The app is no longer crashing after making this change.

NSInternalInconsistencyException recursive dirtying confusion

I'm doing my head in trying to figure out an issue I'm having with core-data. I understand what the error means, and I've had it before (and fixed it) but I can't figure out why I'm getting it now.
My app has the following structure :
MPModel -> MPPlace and MPModel -> MPProject
Where MPModel is a subclass of NSManagedObject and MPPlace and MPProject are subclasses of MPModel.
The data model has a relationship between MPPlace and MPProject where MPPlace has-many MPProjects and MPProject belongs-to MPPlace.
When the app loads, it fetches a number of MPPlace objects which works perfectly. When a user selected a place and then selects the projects option, the app attempts to retrieve the list of projects. This is where the app fails however, with the following error
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason:
'Failed to process pending changes before save. The context is still dirty after 100
attempts. Typically this recursive dirtying is caused by a bad validation method,
willSave, or notification handler.'
Each MPModel contains a number of custom methods, including saveAllLocally which simply saves an array of objects to the persistence store if they don't already exist, and loadNestedResources which fetches new objects from the server that are related to the current object. I am finding that the failure is occurring with the loadNestedResources method but I can't figure out why.
The loadNestedResources method looks like this :
- (void) loadNestedResourcesOfType:(Class)type atURL:(NSString *)resURL withBlock:(MPFindObjectsBlock)block
{
if (![type isSubclassOfClass:[MPModel class]]) {
block(nil, nil);
} else {
NSString *url = [NSString stringWithFormat:#"%#%#/%#%#", kMP_BASE_API_URL, [self.class baseURL], self.objID, [type baseURL]];
MPRequest *request = [[MPRequest alloc] initWithURL:url];
// Attempt to see if we already have this relation
NSString *relation = [[MPUtils stripClassName:type].lowercaseString stringByAppendingString:#"s"];
NSSet *relatedObjects = [self valueForKey:relation];
if (relatedObjects && relatedObjects.count > 0) {
// We have some objects so lets exclude these from our request
NSMutableArray *uuids = [NSMutableArray arrayWithCapacity:0];
for (MPModel *obj in relatedObjects) {
[uuids addObject:obj.uuid];
}
[request setParam:[uuids componentsJoinedByString:#";"] forKey:#"exclude_uuids"];
}
[MPUser signRequest:request];
[request setRequestMethod:#"POST"];
[request submit:^(MPResponse *resp, NSError *error) {
if (error) {
if (relatedObjects.count > 0) {
block([relatedObjects allObjects], nil);
} else {
block(nil, error);
}
} else {
// Combine all of our objects
NSMutableArray *allObjects = [[type createListWithResponse:resp] mutableCopy];
if (allObjects.count > 0) {
[allObjects addObjectsFromArray:[relatedObjects allObjects]];
// Make sure they're now all saved in the persistence store
NSArray *savedObjects = [MPModel saveAllLocally:allObjects forEntityName:NSStringFromClass(type)];
for (NSObject *obj in savedObjects) {
[obj setValue:self forKey:[MPUtils stripClassName:self.class].lowercaseString];
}
// Set them as our related objects for this relationship
[self setValue:[NSSet setWithArray:savedObjects] forKey:relation];
[MPModel saveAllLocally:savedObjects forEntityName:NSStringFromClass(type)];
block(allObjects, nil);
} else {
block([relatedObjects allObjects], nil);
}
}
}];
}
}
The methods runs perfectly right up until the second call to saveAllLocally which is where I get the error. The MPModel class also uses the willSave method, which has the following :
- (void) willSave
{
NSDate *now = [NSDate date];
if (!self.updated_at) {
self.updated_at = now;
}
if (!self.created_at) {
self.created_at = now;
}
if (!self.uuid) {
self.uuid = [self createUUID];
}
if (!self.last_sync) {
self.last_sync = now;
}
if ([self isUpdated] && self.changedValues.count > 0) {
if (!self.attribute_updated_at) {
NSDictionary *attributes = [self.entity attributesByName];
NSMutableDictionary *dates = [NSMutableDictionary dictionaryWithCapacity:0];
for (NSString *attr in attributes.allKeys) {
[dates setObject:now forKey:attr];
}
[self setAttributeUpdatedDates:dates];
}
if (_updatedAtSet) {
_updatedAtSet = NO;
} else {
if ([self.changedValues.allKeys containsObject:#"last_sync"]) {
self.updated_at = [self.changedValues objectForKey:#"last_sync"];
} else {
self.updated_at = [NSDate date];
}
_updatedAtSet = YES;
NSDictionary *changed = [self changedValues];
NSMutableDictionary *dates = [[self attributeUpdatedDates] mutableCopy];
for (NSString *key in changed.allKeys) {
[dates setObject:now forKey:key];
}
[self setAttributeUpdatedDates:dates];
}
}
}
From what I can gather, this should be fine as it shouldn't be setting any more values if the _updatedAtSet variable is set to true, however it is still breaking and I cannot figure out why!
Please can someone help me
Thanks
Have solved it.
I just moved the _updatedAtSet = NO; into the didSave method rather than where it is and it's working fine now. Thanks anyway

Resources