Sorting NSArray on two properties - int and date - ios

note from N00B land again. I have read lots about sorting arrays - wanted to try the block method, but haven't wrapped my head around it. Instead, I opted for the descriptors method. I read this Sort NSArray of custom objects by their NSDate properties and this How to sort an NSMutableArray with custom objects in it? amongst oodles and oodles of others. In my code I did this:
NSString *lastHighScore = #"_highScore";
NSString *dateScoreCreated = #"_dateCreated";
NSSortDescriptor *highScoreDescriptor = [[[NSSortDescriptor alloc]
initWithKey:lastHighScore
ascending:NO
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
NSSortDescriptor *dateScoreCreatedDescriptor = [[[NSSortDescriptor alloc]
initWithKey:dateScoreCreated
ascending:NO
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
NSArray *descriptors = [NSArray arrayWithObjects:highScoreDescriptor,
dateScoreCreatedDescriptor, nil];
NSArray *sortedArray = [[[FlipHighScoreStore sharedStore] allHighScores] sortedArrayUsingDescriptors:descriptors];
Sadly, I am getting an error to begin with - initializer element is not a compile-time element. I looked this up and tried setting NSSortDescriptor *highScoreDescriptor = nil but then I get a warning saying that highScoreDescriptor "Type Specifier Missing, default to int" which in this case is ok, but is not so ok for the Date object in the next descriptor. (Turns out I am also getting an error saying that I am redefining highSoreDescriptor with a different type.)
Also, is there a list somewhere of what selectors are available? I doubt that localizedCaseInsensitiveCompare: is what I want to use since the first property "_highScore" is an int and the second "_dateCreated" is a date. I read somewhere that the default is "compare" so can I just put "compare:"? (Found one answer, I think - I can use (intValue) for the first descriptor:
selector:#selector(intValue)] autorelease];
More reading makes me think that I can do away with the selector line entirely for the date sort. Is that correct?
Lastly, if I say ascending:NO is that the same as descending? I would guess that it is, but one never knows with programming, does one?
Do I wrap all of this code in its own method? Or can I (until later) just plunk it in the code where I am laying out the table?
This project is not ARC.

To answer my own question, with a little help from a friend, I was basically doing two things wrong. First, I was writing the code outside of a method - which is why I was getting all of the errors about initializer elements. I guess I was very tired when I was adding this.
As for the actual sorting, I deleted the selector option from the descriptor description and the sorting actually happened!
Lastly, yes, ascending:NO is equal to descending.
The last bit, will have to wait until I a ready to tackle more refactoring of the application.

Related

Issue related with modifying the value in an NSMutablearray

I am facing an issue with the values in NSMutablearray. I have two NSMutablearray, both are holding the same content using mutablecopy. The issue is that when I modify a value in one array, the corresponding value in the second array also get modified. How to resolove this. Please help me.
mutableCopy copies by reference, not value. So, any change to one of those objects affects for both arrays.
You could realize different methods to overcome this situation.
// first method
nameArray2 = [NSMutableArray new];
[nameArray2 addObjectsFromArray:nameArray1];
// second method
nameArray2 = [[NSMutableArray alloc] initWithArray:nameArray1 copyItems:YES];
Best regards.

objectAtIndex for Nested Array iOS

Beginners iOS dev question here.
I have a plist in the following format:
I've converted this plist into an array in my app using:
NSMutableArray *array2 = [[NSMutableArray alloc] initWithContentsOfFile:path];
and have verified the content using NSLog.
However the issue I have is in understanding objectAtIndex. How do i obtain Item2 in Item1 i.e. the value 6? I'm used to Javas "array[1][2]" style :p
You can actually still use array[1][2] in Objective-C (with Xcode 4). Otherwise the "Objective-C way" to do it is:
[[array objectAtIndex:1] objectAtIndex:2];
But it is lots more code and for someone from most other backgrounds, not as readable either.
It was quite tedious, but Apple have provided a simple way to do it now. Simply put, it would be:
array2[1][2];
The first number inside the square brackets sends -[array2 objectAtIndex: 1] to the top level array. The second number sends the inner array the same message with 2 as an argument.

NSSet from attributes of objects (performance)

I am trying to create a NSSet of strings collected from an unknown (possibly large) number of objects' attributes.
The user of my app logs objects during the month - they have different attributes wheras i am only interested in the NSString *category name.
Object.h
NSString *category
...
During a month he may log between 10 and 1000 such objects, stored in coredata. The user can define own categories - and i have to find out which categories were used during that month (to create an export file).
Currently i do the following (pseudocode)
NSArray *allObjects = [_dataHandler fetchAllObjectsForMonth:monthToExport];
NSMutableSet *allCategoryNamesSet = [[NSMutableSet alloc]init];
for(Object *obj in allObjects){
[allCategoryNamesSet addObject:obj.category];
}
Wheras this works, it gets really slow with a lot of objects as the fetching takes time and the iterating as well of course.
I have tried something like that as well:
NSArray *categories = [allObjects valueForKeyPath:#"category"];
NSSet *allCategorieNamesSet = [NSSet initWithArray:categories];
maybe i did something wrong but it didnt quite work :/
What i am interested in is, if there is a solution that could significantly speed up this process? something i might have overlooked.
Any ideas?
If you are interested only in the (distinct) value of the category attribute, set
[request setResultType:NSDictionaryResultType];
[request setPropertiesToFetch:#[#"category"]];
[request setReturnsDistinctResults:YES];
for your fetch request. The fetch request then return an array of dictionaries (instead of managed objects) containing the category values.
The advantage is that this "filtering" is done on the SQLite level. One disadvantage (or "caveat") is that this implies setIncludesPendingChanges:NO, i.e. the fetch request is done only against the saved database and does not include unsaved pending modifications.
Checkout MagicalRecord, which is a convenience library for working with Core Data. This should help you do a lot of these tasks.
Good luck.

Core Data many-to-many relationship. Saving an array of strings

I have a GameData entity which is meant to store an array of strings. So I made a 'Value' Entity which has a value string attribute and made a many-to-many relationship between the two entities.
To save the data I use the following code:
//Save values
NSMutableSet* values = [[NSMutableSet alloc] init];
for(NSString* n in gameData.values){
NSManagedObject *val = [NSEntityDescription
insertNewObjectForEntityForName:#"Value"
inManagedObjectContext:context];
[val setValue:n forKey:#"value"];
[values addObject:val];
}
[gd setValue:values forKey:#"values"];
The gameData.values array is currently empty so the code never actually goes into the for loop...but for some reason it crashes at this line [gd setValue:values forKey:#"values"] with the following error.
-[__NSSetM managedObjectContext]: unrecognized selector sent to instance 0x1f0485d0
Where or how am I sending a managedObjectContext selector to my values NSMutableSet??
Maybe you need to check whether the type of your entity is "To Many".
I cannot comment, thats why i am creating an answer.
Why don't you create the subclass for your entities using the xcode and import their header files and use code like below
//Save values
//NSMutableSet* values = [[NSMutableSet alloc] init]; -- No Need of this
for(NSString* n in gameData.values){
Value *val = [NSEntityDescription
insertNewObjectForEntityForName:#"Value"
inManagedObjectContext:context];
[val setValue:n]; // set your string
[val setGame:gd]; // set the game relation here. you can do this, if you have
configured inverse relations. If no create inverse relationship it will be helpful.
}
//[gd setValue:values forKey:#"values"]; you don't need this.
Now just save the context. Everything should be fine. This is much more cleaner than your way. I have never ever used key value for accessing core data entity properties because, it will be confusing and error prone as you have to remember the exact spelling of the property, and it wont throw any error if you have used wrong spelling or wrong key.
i think you should look at core data programming guide
Edit: If your GameEntity stores array of strings then one to many relationship is just enough. You need many to many only if GameEntity has many Strings and Each Strings i.e. Value entity also has many GameEntity. In that case the above code slightly changes.
Instead of
[val setGame:gd];
You need to use
[val addGameObject:gd];

How to prevent Core Data making duplicates in iOS 5?

I've run into a problem.
Over the weekend I've been working on a project where I'm pulling a large xml from a webservice.
It basically has 3 tiers - Clients, Managers, Staff all hierarchical. So the first time the app runs, it pulls this xml and parses it and creates all the entries in the 3 releated Entities - Clients, Managers and Staff.
Every time the app launches I need to pull that same XML down, but this time, I only need to 'update' any of the existing records that have changed, or add new ones for new clients, managers or staff that have appeared since last time.
So - at the moment, as I said, it's pulling it all, parsing it correctly and creating the correct entities and filling in all the attributes.
However, with no data change, on the 2nd launch it's DUPLICATING all of the data - so instead of 15 clients ( the correct number ) I have 30 and so on...
Do I really have to add lots of code in my parsing to check that instead of creating a new NSManagedObject, I check if it's already there?
And if it is - I have to then manually check every attribute?
That's awfully painful and longwinded - isn't there a way to make Core Data do this kinda stuff for me - automatically?
Thanks for any help or suggestions.
I fear you have to keep your DB clean by yourself … The easiest way would be using NSFetchRequest: When importing your updated data you can run a query against the existing data and decide what you want to do.
As Marcus S. Zarra mentioned in another thread about this topic:
When you are importing a new row you can run a query against the
existing rows to see if it is already in place. To do this you create
a NSFetchRequest against your entity, set the predicate to look for
the guid property and set the max rows returned to 1.
I would recommend keeping this NSFetchRequest around during your
import so that you can reuse it while going through the import. If the
NSFetchRequest returns a row you can update that row. If it does not
return a row then you can insert a new row.
When done correctly you will find the performance more than
acceptable.
Another source for good information are Apples Programming Guides: Core Data Programming Guide
As Stated in Apple Docs https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html
You need to loop the data model and handle it from there like this
Example:
// loop over employeeIDs
// anID = ... each employeeID in turn
// within body of loop
NSString *predicateString = [NSString stringWithFormat: #"employeeID == %#", anID];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
Personally I do not like this method and I wrote this snippet of code that handles this in a pro-efficient manor and which is straight forward! I noticed with Apples method I ran into issues with strings having different characters such as capitol letters and spaces. Below code is tested and working if you rename all your corresponding objects correctly I honestly believe this is the most efficient way to accomplish not adding duplicates in core data.
-(void)AvoidDuplicatesinDataModel
{
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Users"
inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"users"
ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *Fetcherror;
NSMutableArray *mutableFetchResults = [[managedObjectContext
executeFetchRequest:request error:&Fetcherror] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error
}
//here usersNameTextField.text can be any (id) string that you are searching for
if ([[mutableFetchResults valueForKey:#"users"]
containsObject:usernameTextField.text]) {
//Alert user or handle your duplicate methods from here
return;
}
}

Resources