I am trying to search through a list of reminders in order to check if a reminder exists. There is an option in the method to save the value of whether or not the reminder exists. I am using a block, and within the block, when the reminder is found, the new value is assigned to the reminderExists variable, like it is supposed to. However, when I enter the save if statement, the value of reminderExists has changed from what it was assigned in the block. I am stumped with this issue, and I have tried many changes to diagnose the problem, to no avail. Thanks for any help!
- (BOOL)checkForReminderWithTitle:(NSString *)reminderTitle saveChanges:(BOOL)save {
NSNumber *currentGardenCurrentReminders = currentGarden.reminders;
__block NSNumber *reminderExists = [NSNumber numberWithBool:NO];
if (eventStore == nil)
{
eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
if (!granted)
NSLog(#"Access to store not granted");
}];
}
if (eventStore != nil) {
NSPredicate *predicate = [eventStore predicateForIncompleteRemindersWithDueDateStarting:[NSDate distantPast] ending:[NSDate distantFuture] calendars:[NSArray arrayWithObject:[eventStore defaultCalendarForNewReminders]]];
[eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
for (EKReminder *reminder in reminders) {
NSLog(#"%#", reminderTitle);
if ([reminder.title isEqualToString:reminderTitle]) {
reminderExists = [NSNumber numberWithBool:YES];
NSLog(#"reminder exists");
NSLog(#"after reminder exists is assigned to YES, value is %#", ([reminderExists boolValue]) ? #"True" : #"False");
if ([reminderExists boolValue]) NSLog(#"reminder exists variable works");
NSArray *alarms = reminder.alarms;
EKAlarm *alarm = [alarms objectAtIndex:0];
NSDateFormatter *nextReminderDateFormatter = [[NSDateFormatter alloc] init];
nextReminderDateFormatter.dateFormat = #"MM/dd/yyyy at hh:mm a";
NSString *nextReminderDateString = [nextReminderDateFormatter stringFromDate:alarm.absoluteDate];
NSLog(#"Next reminder is on %#", nextReminderDateString);
break;
}
}
}];
}
if (save && ([currentGardenCurrentReminders boolValue] != [reminderExists boolValue])) {
if ([reminderExists boolValue]) {
NSLog(#"within save, reminderexists == true");
} else NSLog(#"within save, reminderexists == false");
currentGarden.reminders = reminderExists;
[self.managedObjectContext save:nil];
}
return [reminderExists boolValue];
}
The call to fetchRemindersMatchingPredicate:completion: is asynchronous. This means that your if statement after this call is actually being done long before the call to fetchRemindersMatchingPredicate:completion: is complete.
You can't have a useful return value in a method that does asynchronous processing. You need to change your checkForReminderWithTitle:saveChanges: method to return void and instead have it pass a completion block that will be called when the call to fetchRemindersMatchingPredicate:completion: is done.
BTW - why is reminderExists an NSNumber pointer and not just a BOOL?
The call to [EKEventStore fetchRemindersMatchingPredicate:completion:] is asynchronous. So when you check the value of reminderExists below the completion block, the completion block may not have executed yet, and reminderExists may not yet have the correct value.
To fix the problem, put your check code in the end of the completion block. As #rmaddy correctly points out, this function can't return a value, since it relies on an async call.
Related
I wonder why my Core Data stop to save changes. In fact, code above worked hours ago. When i try to print out an error, it print (null). Code below is custom method for NSManagedObjectSubclass:
-(void)bindWithModel:(MenuAPIModel*)model{
self.basketId = [model.basketId integerValue];
self.coreId = [model.itemId integerValue];
self.name = [model name];
self.orderId = [model.orderId integerValue];
self.payedFrom = [model payedFrom];
self.persons = [model persons];
self.price = [model.price integerValue];
self.price3 = [model.price3 integerValue];
self.price12 = [model.price12 integerValue];
self.status = [model status];
[[NSManagedObjectContext MR_defaultContext] MR_saveOnlySelfWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
NSLog(#"error %#", error.localizedDescription);
}];
Error is null and contextDidSave is YES. But when i try to access entity it prints null, and SQL table is an empty. Why?
I assume that bindWithModel method is in NSManagedObject subclass. If so, then you should use managedObjectContext property from this class rather then MR_defaultContext:
[self.managedObjectContext MR_saveOnlySelfWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) { (...) }];
Previously it was working probably because context from [NSManagedObjectContext MR_defaultContext] was the same as self.managedObjectContext.
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.
I have a Chat-App with a Data-Modell like this.
User <--> Conversation <-->> Message
My Problem now: Sometimes, if I get old messages from a Backup, I have Messages twice in my DataModel. I'd like to have a NSSet-Like Class which recognizes, if a Message-Object has exactly the same values on it's properties. I've read, that I must not override the methods -hash and -isEqual:, so I don't know how to do it. Any Idea? Here is some code...
+(void)addMessages:(NSSet<JSQMessage *> *)messages toConversation:(Conversation *)conversation
{
DataManager * dataManager = [DataManager dataManager];
NSMutableSet * storeSet = [NSMutableSet setWithCapacity:messages.count];
for (JSQMessage * jsqMessage in messages) {
Message * message = [NSEntityDescription insertNewObjectForEntityForName:CDEntityNameMessage inManagedObjectContext:[dataManager managedObjectContext]];
message.senderId = jsqMessage.senderId;
message.senderDisplayName = jsqMessage.senderDisplayName;
message.text = jsqMessage.text;
message.date = jsqMessage.date;
[storeSet addObject:message];
}
[conversation addMessages:storeSet];
NSError *error;
if (![[dataManager managedObjectContext] save:&error]) {
NSLog(#"Something went wrong: %#", [error localizedDescription]);
} else {
//Saved successfull
}
}
And the Conversation -addMessages: Method is the one automatically generated from Xcode/CoreData
- (void)addMessages:(NSSet<Message *> *)values;
One way of doing it would be to add unique constraints on your entity for one or more attributes. But, this feature was added from iOS 9. Here's the link to the WWDC video explaining it:
https://developer.apple.com/videos/play/wwdc2015/220/
As a final option, you can always override hash and equal, if that suits your logic and requirements.
You hash method could look something like this:
- (NSUInteger)hash
{
NSInteger hashResult = 0;
for (NSObject *ob in self)
{
hashResult ^= [ob hash];
}
}
This is not the best implementation of a hash function. Check out this answer: https://stackoverflow.com/a/5915445/2696922
For the isEqual method, it could look something like:
- (BOOL)isEqual:(id)object
{
if (self == object)
{
return YES;
}
if (object == nil || ![object isKindOfClass:[JSQMessage class]])
{
return NO;
}
JSQMessage *jsqMessage = (JSQMessage*)object;
//You can have more parameters here based on your business logic
if (self.message != jsqMessage.message && self.date != jsqMessage.date)
{
return NO;
}
}
What I do now is checking manually, if there is a Object with same Attributes in my MOC. If there is one, I skip the creation. I know, it is a bit inefficient but with my expected number of messages, this should be no problem.
NSFetchRequest * fr = [NSFetchRequest fetchRequestWithEntityName:CDEntityNameMessage];
[fr setPredicate:[NSPredicate predicateWithFormat:#"text == %# AND date == %# AND conversation.user.objectId == %#", message.text, message.date, chatpartner.objectId]];
NSArray * results = [[self managedObjectContext] executeFetchRequest:fr error:nil];
if (results && results.count > 0) {
continue;
}
I'm on Xcode 6.0.1, making a test app with Event Kit. The following code successfully populates each event's title, but its notes are returned as (null) even when hasNotes property returns YES. And, I can see the notes for the same event on iPhone's default calendar app.
What am I doing wrong?
- (void)viewDidLoad
{
[super viewDidLoad];
[eventStore requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error)
{
NSLog(#" !! error");
// display error message here
}
else if (!granted)
{
NSLog(#"Not Granted");
// display access denied error message here
}
else
{
// access granted
NSCalendar *calendar = [NSCalendar currentCalendar];
// Create the start date components
NSDateComponents *oneWeekAgoComponents = [[NSDateComponents alloc] init];
oneWeekAgoComponents.day = -1;
NSDate *oneWeekAgo = [calendar dateByAddingComponents:oneWeekAgoComponents toDate:[NSDate date] options:0];
// Create the end date components
NSDateComponents *oneMonthFromNowComponents = [[NSDateComponents alloc] init];
oneMonthFromNowComponents.month = 1;
NSDate *oneMonthFromNow = [calendar dateByAddingComponents:oneMonthFromNowComponents toDate:[NSDate date] options:0];
// Create the predicate from the event store's instance method
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:oneWeekAgo endDate:oneMonthFromNow calendars:nil];
// Fetch all events that match the predicate
_eventArray = [eventStore eventsMatchingPredicate:predicate];
[self.tableView reloadData];
}
});
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
EKEvent *event = [self.eventArray objectAtIndex:indexPath.row];
cell.textLabel.text = event.title;
if (event.hasNotes) {
cell.detailTextLabel.text = event.notes;
} else {
cell.detailTextLabel.text = #"";
}
return cell;
}
I haven't fully resolved it, but got a clue.
NSArray *events = [eventStore eventsMatchingPredicate:predicate];
This didn't get the notes. So, instead I enumerate through returned events by doing
[eventStore enumerateEventsMatchingPredicate:predicate usingBlock:^(EKEvent *event, BOOL *stop) {
NSLog(#"title: %#",event.title);
NSLog(#"hasNotes: %s",event.hasNotes ? "YES":"NO");
NSLog(#"notes: %#",event.notes);
NSLog(#"-----");
[_eventTitles addObject:event.title];
[_eventTitles addObject:event.hasNotes ? event.notes : #""];
}];
This one returns actual notes (null).
I had a similar issue like this but when accessing the calendar object from the EKEvent. I my case this was because I had released the EKEventStore instance before I attempted to access the calendar (in cellForRowAtIndexPath:).
According to the Apple documentation "Reading and Writing Calendar Events":
An event store instance must not be released before other Event Kit
objects; otherwise, undefined behavior may occur.
https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/EventKitProgGuide/ReadingAndWritingEvents.html#//apple_ref/doc/uid/TP40004775-SW1
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