I'm searching for a better alternative to deal with this problem.
In a CoreData model I have an NSManagedObject called Project. In its subclass I override the accessor method (setter) for its label attribute. Here I check whether the same label is already used. If it is, I add an underscore and a number to the label, e.g. "MyProject" is renamed to "MyProject_1". Of course I also have to check whether I find the label "MyProject" or "MyProject_"+number. I do that with a Regular Expression.
NSString *regexString = [NSString stringWithFormat:#"%#_[0-9]+", value];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(label = %#) OR (label MATCHES %#)", value, regexString];
[request setPredicate:predicate];
Then I check how many results are fetched, lets say 5, so I know that the next one hast to be called "MyProject_6".
It works fine but you probably have already noticed that there is a little problem with this code: What happens if I have the following labels:
MyProject_1, MyProject_2, MyProject_3
and the user decides to call a project MyProject_55.
Then my search would retrieve 4 elements and the next project would be labeled MyProject_5 instead of MyProject_4. And what it's worse, at some point, I would end up having two MyProject_55. I know it's unlikely to happen, but it can :).
Any ideas for something better?
Here's the accessor method
#pragma mark - Setter for label
- (void)setLabel:(NSString *)aLabel
{
if ([[self primitiveValueForKey:#"label"] isEqualToString:aLabel])
{
return;
}
NSMutableArray *objects = [self fetchObjectsWithValueEqualTo:aLabel];
NSUInteger objectsCount = [objects count];
aLabel = objectsCount > 0 ? [NSString stringWithFormat:#"%#_%d",aLabel, objectsCount] : aLabel;
[self willChangeValueForKey:#"label"];
[self setPrimitiveValue:aLabel forKey:#"label"];
[self didChangeValueForKey:#"label"];
}
Its a little expensive but the simplest way out of this dilemma is once you have a new label decision "MyLabel_4" recheck if that label exists in the store.
Rinse and repeat until you really have a unique label. Core Data is very efficient so this isnt going to matter in a userland case.
Related
here is my code,
hand is the entity, and addRounds is a textField
-(IBAction)save{
[self dismissViewControllerAnimated:YES completion:nil];
[self.hand setValue:self.addRounds.text forKey:#"rounds"];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
NSLog(#"%#", self.addRounds.text);
NSLog(#"%#", self.hand.rounds);
}
the console out puts
2012-11-25 16:51:18.847 App[3187:c07] 1
2012-11-25 16:51:18.848 App[3187:c07] (null)
so, for some reason, its not saving properly. Could anyone please help me! Thank You!
-(IBAction)save{
if (self.managedObjectContext == nil)
{
self.managedObjectContext = [(RootAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
Hand *hand = [NSEntityDescription insertNewObjectForEntityForName:#"Hand" inManagedObjectContext:self.managedObjectContext];
[self dismissViewControllerAnimated:YES completion:nil];
hand.rounds = nil;
self.managedObjectContext = self.app.managedObjectContext;
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
Edit
So Basically There is a total of 4 Views.
1) view with Table view, user can press '+' button
2) This view allows the user to add a cell to the table view
3) allows the user to edit the table view cells
4) this is a completely different view that also allows the user to edit the table view cells.
I'm using the code at the top of the question to save in both views 3 & 4. It works perfectly in view 3 but not 4!!
UPDATE!!!
So, I recoded the app so that views 1 & 4 are the only two views in the app. When i push view 2 and view 3 in between view 1 and 4 it sets my managedObjectContext's rounds attribute to null.
Is the rounds attribute of your hand entity a string in your model?
First, you should set the type on your hand entity's rounds attribute to a number type. 16bit integer would probably suffice (we're talking about a card game, right?), but you could make it bigger if you like.
Next change your code to:
-(IBAction)save{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSNumber *rounds = [numberFormatter numberFromString:numberself.addRounds.text];
[self dismissViewControllerAnimated:YES completion:nil];
[self.hand setValue:rounds forKey:#"rounds"];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
NSLog(#"%#", self.addRounds.text);
NSLog(#"%#", self.hand.rounds);
Also, I'm assuming you've inserted the instance of hand that you're setting properties on somewhere before this point. Maybe I shouldn't assume. Have you already initialized this managed object instance, self.hand, to point to data in the store?
For example, I like to use lazy instantiation:
- (Hand*)hand
{
if (_hand = nil) {
_hand = [NSEntityDescription insertNewObjectForEntityForName:#"Hand" inManagedObjectContext:self.managedObjectContext];
}
return _hand;
}
This assumes a purely -create oriented- design. In most cases, you'd want to edit an existing object and update its rounds count. For this, you should attempt to retrieve a Hand that you're editing first. You'd do that with an NSFetchRequest, and there a zillions of examples of that, so I won't repeat them here. If There were no matches, this getter would create one as a fall-back. Also, best practice is to create a category for Hand (maybe Hand+Edit.m) which contains methods for retrieving different Hands, creating them, and updating common properties.
I'd create worker methods inside the Hand object category like these:
+ (void)incrementRoundsOnHand:(Hand *)hand withManagedObjectContext:(NSManagedObjectContext *)context
+ (void)incrementRoundsBy:(NSUInteger)count onHand:(Hand *)hand withManagedObjectContext:(NSManagedObjectContext *)context
// or some sort of unique identifier, date, number, etc
+ (Hand *)handWithName:(NSString *)name withManagedObjectContext:(NSManagedObjectContext *)context
I'd then have handWithName: (or whatever) do an NSFetchRequest, and if nothing matches, create a new hand and return it. Either way, you get a hand back. Important is that you don't deal with manipulating the specifics of your Hand entity outside of the Hand managed object class. Note, since these are class methods, they can be called directly.
I am using Core Data however haven't done any complex queries and am completely lost - come from SQL background so need help creating a NSPredicate.
My question
I need to retrieve a list of Perspective's (with unique name) which in-directly belong to a specific EntityManagedObject.
Overview of the database
As you can see EntityManagedObject can have many EntityManagedObjects and can have many ObjectiveManagedObject's.
EntityManagedObject therefore has a single EntityManagedObject as a parent, and ObjectiveManagedObjective has a single EntityManagedObject as a parent.
ObjectiveManagedObject has one Perspective. A Perspective can belong to many ObjectiveManagedObjects.
make a method,
-(NSArray *) getEntityManagedObjectsWithParentEntity:(EntityManagedObject *) parentObject;
another method,
-(NSArray *) getObjectiveManagedObjectsWithEntityManagedObjects:(NSArray *) entityManagedObjects;
then ,
-(NSArray *) getPerspectivesWithEntityManagedObject:(EntityManagedObject *) entityObject
{
NSArray *objectiveManagedObjects = [self getObjectiveManagedObjectsWithEntityManagedObjects: [self getEntityManagedObjectsWithParentEntity:entityObject] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Objectives IN %#", objectiveManagedObjects];
}
I am putting the finishing touches on an App, and have difficulties with removing records in bulk. On the hit of a button a set of approx. 3500 records need to be added to the database. This is not a problem, and takes approx. 3-4 seconds.
But sometimes (not often, but the option needs to be there) all these records need to be removed. I just ran this operation, and it took 20 minutes. What could be wrong here? There is only one dependency, all the records are children of a particular Collection.
I add all the items to a set, removed them from the Collection and then delete one by one. Every 5% I update the dialogue and when everything is done I commit the changes. But removing the items just takes ages (as I can see the progress dialogue progress very slowly)
- (void) deleteList:(DOCollection *) collection {
// For the progress dialogue
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:#"Clearing vocabulary list!" forKey:#"message"];
float totalItems = [collection.items count];
float progress = 0;
float nextProgressRefresh = 0.05;
NSMutableSet* itemsSet = [NSMutableSet set];
for (DOItem* item in collection.items) {
[itemsSet addObject:(NSNumber*)[NSNumber numberWithInt:[item.itemId intValue]]];
}
// Remove all of them from the collection
[managedObjectContext performBlockAndWait:^{
[collection setItems:[NSSet set]];
}];
for (NSNumber* itemId in itemsSet) {
DOItem* item = [itemController findItem:[itemId intValue]];
if (item != nil) {
[[self itemController] removeItem:item];
}
progress++;
if((nextProgressRefresh < (progress / totalItems))){
NSString* sProgress = [NSString stringWithFormat:#"%f", (progress / totalItems) * 0.85];
//[dict setValue:#"Saving the database...!" forKey:#"message"];
[dict setValue:sProgress forKey:#"progress"];
[[NSNotificationCenter defaultCenter] postNotificationName:kUpdatePleaseWaitDialogue object:dict];
nextProgressRefresh = nextProgressRefresh + 0.05;
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[managedObjectContext performBlockAndWait:^{
[[self collectionController] commitChanges];
}];
[[NSNotificationCenter defaultCenter] postNotificationName:kSavingDataComplete object:nil];
});
//NSLog(#"Wait 2!");
[NSThread sleepForTimeInterval:1];
}
in DOItemController:
- (void) removeItem: (NSManagedObject*) item {
[[self managedObjectContext] deleteObject:item];
}
Not sure about how you architected your data models. But I would set it up to cascade delete your objects. If the DOItem Objects are unique to the DOCollection, then you can set the delete rule to cascade. That will automagically delete the associated DOItem as well as remove it from the DOCollection item set object.
To delete the DOItem objects from the DOCollection, check your DOCollection.h file, you should have a method along the lines of
-(void)removeDOItemObjects:(NSSet *)value
if not, they may still be dynamically generated by Core Data for you. In your header file you should have something along the lines of:
#property(nonatomic,retain) DOItem *items
then in your implementation file, you should have something along the lines of:
#synthesize items
The appropriate methods that should be generated for these automatically:
-(void)addItemsObject:(DOItem*)value
-(void)addItems:(NSSet *)values
-(void)removeItemsObject:(DOItem *)value
-(void)removeItems:(DOItem *)values
-(NSSet *)items
See "Custom To-Many Relationship Accessor Methods" here for more info.
This method is provided for you when you create the data model and associated implementation files and should be highly optimized by Core Data. Then all you have to do to remove the objects is something along the lines of:
- (void) deleteList:(DOCollection *) collection {
// Remove all of them from the collection
[managedObjectContext performBlockAndWait:^{
// Updated 01/10/2012
[collection removeItems:collection.items];
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Core Data: Error saving context."); }
};
}];
}
You might want to check the performance of the delete using this method and continue to provide the user with feedback. If performance is an issue, consider striding, where you divide the set up into chunks and perform the above method fore each one of the strides, update the user interface, etc.
Again, am not sure about your application architecture, but on first glance, this looks like the issue.
Good Luck!
I have an NSMutableArray which can hold several objects. I would like to check if an object exists, and if so, alter it. I was wondering about checking it. I thought this would work:
if ([[[self.myLibrary objectAtIndex:1] subObject] objectAtIndex:1]) // do something
However, this will crash if there aren't any subObjects at index 1.
So I guess the problem is that the above does not return nil if there isn't anything at this Index.
Is there another easy way to check or will I have to count through the array etc.? I know there are other posts on stackoverflow on this, but I haven't found a simple answer yet.
Any explanations / suggestions welcome. Thanks!
No check simply using :
[myArray indexOfObject:myObject];
or
[myArray containsObject:myObject];
These methods check every object using isEqual.
For example:
NSUInteger objIdx = [myArray indexOfObject: myObject];
if(objIdx != NSNotFound) {
id myObj = [myArray objectAtIndex: objIdx];
// Do some alter stuff here
}
If this is a pattern you use a lot, you could add a category to NSArray called something like safeObjectAtIndex:, which takes an index, does the bounds checking internally:
-(id)safeObjectAtIndex:(NSUInteger)index {
if (index >= [self count])
return nil;
return [self objectAtIndex:index];
}
Assuming the object you are using to search with and the actual object in the array are the exact same instance, and not two different objects that are equal according to an overridden isEqual: method, you can do this:
if ([array containsObject:objectToSearchFor]) {
// modify objectToSearchFor
}
If the two objects are different instances which are equal according to isEqual:, you will have to use code like this:
NSUInteger index = [array indexOfObject:objectToSearchFor];
if (index != NSNotFound) {
id objectInArray = [array objectAtIndex:index];
// modify objectInArray
}
NSArray (which is the NSMUtableArray superclass) has lots of methods for finding objects. Have a look at the documentation.
You can either rely on the equals method (e.g. indexOfObject:) or provide a block (e.g indexOfObjectPassingTest:) which is pretty funky.
It's fairly common in Objective C to be using the Mutable version of a class but rely on methods in the non mutable superclass so it's always a good idea when checking the online documentation to look at the superclass.
I have a Table with over 3000 entries and searching is very slow.
At the moment I am doing just like in the 'TableSearch' example code (but without scopes)
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText: searchString];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
And the filterContentForSearchText method is as follows:
- (void) filterContentForSearchText:(NSString*)searchText
{
// Update the filtered array based on the search text
// First clear the filtered array.
[filteredListContent removeAllObjects];
// Search the main list whose name matches searchText
// add items that match to the filtered array.
if (fetchedResultsController.fetchedObjects)
{
for (id object in fetchedResultsController.fetchedObjects)
{
NSString* searchTarget = [tableTypeDelegate getStringForSearchFilteringFromObject:object];
if ([searchTarget rangeOfString:searchText
options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch)].location != NSNotFound)
{
[filteredListContent addObject:object];
}
}
}
}
My question is twofold:
How do can I make the searching process faster?
How can I stop the search from blocking the main thread? i.e. stop it preventing the user from typing more characters.
For the second part, I tried "performSelector:withObject:afterDelay:" and "cancelPreviousPerformRequests..." without much success. I suspect that I will need to use threading instead, but I do not have much experience with it.
Answer for: "How do can I make the searching process faster?"
It seams that you are using core data results in your table. So it's better let core data do the filtering for you.
So create a new fetchedResultController using a NSPredicate for filtering.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", searchText];
[fetchRequest setPredicate:predicate];
using MATCHES instead of = let you define a regular expression comparison (for a case insensitive compare)
I ended up doing the searching as a NSOperation, so as not to block the main thread. I also did like Reinhard suggested and used a fetchedResultController.
There is a really good video on NSOperations on the apple developer site. I was called something like Advanced iPhone part 1 if I recall.