using nested fast enumeration takes too long, how to optimize? - ios

I am doing this is soon as the app starts, luckily I have to do it only once in a singleton class called CMIDataManager, my app is taking too long to launch.
The plist contains:
Commanders.plist:
German - Array
Soviet - Array
each commander array has 19 commander and each commander has 5 abilities (mapping through a unique ability uid).
Abilities.plist:
GermanAbilities - Array
SovietAbilities - Array
Each array contains 40 abilities with a uid (used for mapping commanders to abilities)
At the start, I need to make a model class, so I iterate commander's abilities uid against each Ability hid, once a match is found I add the ability model object to Commaders model object.
How can I do it faster? Would using block based enumeration speed it up? How can I use it?
-(void)loadCommandersAndAbilities{
#pragma German Abilities iteration
NSString* abilitiesPlistPath = [[NSBundle mainBundle] pathForResource:#"Abilities" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:abilitiesPlistPath];
NSArray *tempArray = [dictionary objectForKey:#"GermanAbilities"];
NSArray *tempArray2 = [dictionary objectForKey:#"SovietAbilities"];
NSMutableArray *tempAbilitiesArray = [[NSMutableArray alloc] initWithCapacity:tempArray.count];
for (NSDictionary *dict in tempArray) {
Ability *ability = [[Ability alloc] init];
[ability populateWithDictionary:dict];
[tempAbilitiesArray addObject:ability];
NSLog(#"Adding object %# to temp abilities",ability.name);
}
self.germanAbilitiesArray = [NSArray arrayWithArray:tempAbilitiesArray];
[tempAbilitiesArray removeAllObjects];
#pragma Soviet abilities iteration
for (NSDictionary *dict in tempArray2) {
Ability *ability = [[Ability alloc] init];
[ability populateWithDictionary:dict];
[tempAbilitiesArray addObject:ability];
}
self.sovietAbilitiesArray = [NSArray arrayWithArray:tempAbilitiesArray];
#pragma German commander itertation
NSString* commandersPlistPath = [[NSBundle mainBundle] pathForResource:#"Commanders" ofType:#"plist"];
dictionary = [[NSDictionary alloc] initWithContentsOfFile:commandersPlistPath];
tempArray = [dictionary objectForKey:#"German"];
tempArray2 = [dictionary objectForKey:#"Soviet"];
NSLog(#"Temp German commadner array is %#", tempArray);
NSLog(#"Temp Soviet commadner array is %#", tempArray2);
NSMutableArray *tempCommandersArray = [[NSMutableArray alloc] initWithCapacity:tempArray.count];
NSMutableArray *tempCommandersArray2 = [[NSMutableArray alloc] initWithCapacity:tempArray2.count];
for (NSDictionary *dict in tempArray) {
Commander *commander = [[Commander alloc] init];
[commander populateWithDictionary:dict];
for (NSNumber *uid in commander.abilitiesUIDArray) {
NSLog(#"uid %#", uid);
for (Ability *ability in self.germanAbilitiesArray) {
NSLog(#"ability uid is : %#, target uid %# ",ability.uid, uid);
if ([ability.uid isEqualToNumber: uid]) {
NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
}
}
}
[tempCommandersArray addObject:commander];
}
self.germanCommandersArray = [NSArray arrayWithArray:tempCommandersArray];
NSLog(#"Final german Commaders %#",self.germanCommandersArray);
#pragma Soviet commander itertation
for (NSDictionary *dict in tempArray2) {
Commander *commander = [[Commander alloc] init];
[commander populateWithDictionary:dict];
for (NSNumber *uid in commander.abilitiesUIDArray) {
NSLog(#"uid %#", uid);
for (Ability *ability in self.sovietAbilitiesArray) {
NSLog(#"ability uid is : %#, target uid %# ",ability.uid, uid);
if ([ability.uid isEqualToNumber: uid]) {
NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
}
}
}
[tempCommandersArray2 addObject:commander];
}
self.sovietCommandersArray = [NSArray arrayWithArray:tempCommandersArray2];
NSLog(#"Final Soviet Commaders %#",self.germanCommandersArray);
}
Adding images:

The obvious thing is that your abilities array shouldn't be an array but a dictionary. That way you don't compare the uid with the uid of every ability, but look it up in a single operation.

seems like the issues was in this loop:
if ([ability.uid isEqualToNumber: uid]) {
[commander.abilitiesArray addObject:ability];
}
once i have find a match for commander's ability in the list of all abilities, I don't need to check for that ability to match with rest of the abilities, so I added a break statement.
if ([ability.uid isEqualToNumber: uid]) {
//NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
//NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
break;
}
I also added this to the code to make it run on background thread, bringing the launch time down from 6 s to .5 second.
-(instancetype)init {
self = [super init];
if(self) {
[self performSelectorInBackground:#selector(loadCommandersAndAbilities) withObject:nil];
//[self loadCommandersAndAbilities];
// NSOperationQueue
}
return self;
}
loadCommandersAndAbilities: is the method listed in the original question, I also added notifications to let my view controller know when the method has finished.
//end of loadCommandersAndAbilities
[[NSNotificationCenter defaultCenter] postNotificationName:#"TableViewDataDownloaded" object:nil];

Related

Crash with a loop which look in a mutable array [duplicate]

I have the following situation where
I have an NSMutableArray filled with an xml file which I want to search.
When I enter something in the search field I get this Error:
-[NSCFString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x5b388b0
What does it means and how can I fix it??
I suppose the error is somewhere around here.
- (void)searchTableView{
searchedList = [[NSMutableArray alloc] init];
NSLog(#"new list %#", searchedList);
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in list) {
NSArray *array = [dictionary objectForKey:#"TITLE"];
[searchArray addObjectsFromArray:array];
}
for (NSString *TempArray in searchArray) {
NSRange titleResults = [TempArray rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResults.length > 0)
[searchedList addObject:TempArray];
}
[searchArray release];
searchArray = nil;
}
it means you are calling a method designed for an NSArray (countByEnumeratingWithState:objects:count on a NSString.
I don't know ifthis code is copy/paste from yours, but if so, at the end where you use [searchList addObject:TempArray] you don't have an object named searchList.
Also, work on your naming conventions. big time.

Adding Strings in NSMutableArray

I am in my IOS application in which i am getting ID from server which i am saving in string and then add strings in NSMutableArray.I am not getting perfect method by which i can add the strings in array and use the array outside the scope.
Here is my code Please help me out::
- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse:(NSDictionary *)inResponseDictionary
{
NSMutableArray *array=[[NSMutableArray alloc]init];
i=0;
NSLog(#"%s %# %#", __PRETTY_FUNCTION__, inRequest.sessionInfo, inResponseDictionary);
if (inRequest.sessionInfo == kUploadImageStep)
{
snapPictureDescriptionLabel.text = #"Setting properties...";
NSLog(#"%#", inResponseDictionary);
NSString* photoID =[[inResponseDictionary valueForKeyPath:#"photoid"] textContent];
flickrRequest.sessionInfo = kSetImagePropertiesStep;
// for uploading pics on flickr we call this method
[flickrRequest callAPIMethodWithPOST:#"flickr.photos.setMeta" arguments:[NSDictionary dictionaryWithObjectsAndKeys:photoID, #"photo_id", #"PicBackMan", #"title", #"Uploaded from my iPhone/iPod Touch", #"description", nil]];
[self.array addObject:photoID];
arr=array[0];
counterflicker++;
NSLog(#" Count : %lu", (unsigned long)[array count]);
}
How can i add the photoID(Strings) in the array?
Please help me out..
for adding NSString in NSMutableArray is like this
NSString *str = #"object";
NSMutableArray *loArr = [[NSMutableArray alloc] init];
[loArr addObject:str];
In your code Why are you using self.array ? just write like this. [array addObject:photoID];
self keyword is used for global variables but here in your code
"array" is a local variable .So need of self.array
[array addObject:photoID];
Before adding check that photoID is nil or not
if (photoID.length > 0) {
[array addObject:photoID];
}
I observe that in your code. you declare mutable array in local scope.
So just use
[array addObject:photoID];
Instead of
[self.array addObject:photoID];
May be you are create property for this array with same name, then you need to alloc it.
If you create a property for this then remove local declaration and alloc array like this.
self.array=[[NSMutableArray alloc]init];
and then use
[self.array addObject:photoID];

Objective-C: Relating NSStrings to each other?

I am very new to code in general and I'm in the process of learning Objective-C, so I apologize in advance if I phrased this question incorrectly.
I have created several arrays with this goal in mind:
Show how many people there are and tell their names
Show how many cities there are and tell their names
Assign people to cities and calculate the populations
I have completed that task, but I have assigned each person individually to the array for each city. Is there a way that I can simply relate the NSStrings? For example "blockCity = bill, bob, jim" instead of creating a new array for populations?
// My people
NSString *bill = (#"Bill");
NSString *bob = (#"Bob");
NSString *jim = (#"Jim");
NSString *kevin = (#"kevin");
NSString *stacy = (#"stacy");
NSString *cooper = (#"Cooper");
NSMutableArray *people = [NSMutableArray arrayWithObjects: bill, bob, jim, kevin, stacy, cooper, nil];
NSLog(#"Here are my people: %#", people);
NSLog(#"I have %lu people", [people count]);
// My places
NSString *blockCity = (#"BlockCity");
NSString *hyperCity = (#"HyperCity");
NSString *pixelTown = (#"PixelTown");
NSString *nowhere = (#"Nowhere");
NSMutableArray *cities = [NSMutableArray arrayWithObjects: blockCity, hyperCity, pixelTown, nowhere, nil];
NSLog(#"Cities: %#", cities);
NSLog(#"There are %lu cities", [cities count]);
//populations
// BlockCity population
NSMutableArray *bcpop = [NSMutableArray arrayWithObjects: bill, bob, jim, nil];
if ([bcpop count] == 0) {
NSLog(#"%# is abandoned.", blockCity);
} else {
NSLog(#"%# has a population of %lu", blockCity, [bcpop count]);
}
//HyperCity population
NSMutableArray *hcpop = [NSMutableArray arrayWithObjects: nil];
if ([hcpop count] == 0) {
NSLog(#"%# is abandoned.", hyperCity);
} else {
NSLog(#"%# has a population of %lu", hyperCity, [hcpop count]);
}
//PixelTown population
NSMutableArray *ptpop = [NSMutableArray arrayWithObjects: kevin, stacy, cooper, nil];
if ([ptpop count] == 0) {
NSLog(#"%# is abandoned.", pixelTown);
} else {
NSLog(#"%# has a population of %lu", pixelTown, [ptpop count]);
}
//Nowhere population
NSMutableArray *npop = [NSMutableArray arrayWithObjects: nil];
if ([npop count] == 0) {
NSLog(#"%# is abandoned.", nowhere);
} else {
NSLog(#"%# has a population of %lu", nowhere, [npop count]);
}
No, there is no built-in way you can just “relate the NSStrings”.1
The appropriate thing to do here is to start thinking about your design in an object-oriented way.
What are the different sorts of objects in your program? There are people, so a Person class is appropriate. Give the Person class a name property (an NSString).
There are cities, so a City class is appropriate. Give the City class a name property (an NSString), an inhabitants property (an NSArray or NSMutableArray to be filled with references to Person objects), and a population method (returning unsigned long) that returns the number of inhabitants.
Once you have all that, you can even give City a populationDescription method that returns the string description of its population, like this:
- (NSString *)populationDescription {
if (self.population == 0) {
return [NSString stringWithFormat:#"%# is abandoned.", self.name];
} else {
return [NSString stringWithFormat:#"%# has a population of %lu", self.name, self.population);
}
}
Footnote 1. For the pedants: associated objects don't count. They are not appropriate for this and they are too advanced for a learner at this level.
I will recommend to use NSMutableDictionary in this case to relate people with their city. Something like:
NSMutableArray *people = [NSMutableArray arrayWithObjects: bill, bob, jim, kevin, stacy, cooper, nil];
NSMutableDictionary *cityWithPeople = [[NSMutableDictionary alloc] init];
[cityWithPeople setObject:people forKey:#"CityName"]; // to add people related to that city
NSArray peopleInCity=[cityWithPeople objectForKey:key]; // return array of people in city
Also read here to know how to use NSDictionary

Split NSArray into sub-arrays based on NSDictionary key values

We have an app that calls a SOAP web service and retrieves a long list of XML, which the app then parses into an NSArray of NSDictionary objects. The NSArray contains a list of Rental Apartment information, each of which is stored into an NSDictionary.
The entire list may contain 10 different types of Apartments (i.e. 2-room, 3-room), and we need to split the NSArray into smaller NSArrays based on Room-Type, which has the key "roomType" in the NSDictionary objects.
Currently our algorithm is
Use [NSArray valueForKeyPath:#"#distinctUnionofObjects.room-type"]
to obtain a list of unique room-type values.
Loop through the list of unique room-type values
For each unique room-type value, use NSPredicate to retrieve matching items from the Original list
Our code is below (renamed for clarity):
NSArray *arrOriginal = ... ...; // Contains the Parsed XML list
NSMutableArray *marrApartmentsByRoomType = [NSMutableArray arrayWithCapacity:10];
NSMutableArray *arrRoomTypes = [arrOriginal valueForKeyPath:#"distinctUnionOfObjects.roomType"];
for(NSString *strRoomType in arrRoomTypes) {
NSPredicate *predicateRoomType = [NSPredicate predicateWithFormat:#"roomType=%#", strRoomType];
NSArray *arrApartmentsThatMatchRoomType = [arrOriginal filteredArrayUsingPredicate:predicateRoomType]; // TAKES A LONG TIME EACH LOOP-ROUND
[marrApartmentsByRoomType addObject:arrApartmentsThatMatchRoomType];
}
However, step 3 is taking a long time as the original list may contain large amount (>100,000) of items. It seems that NSPredicate goes through the entire list for each key value. Is there a more efficient way of splitting a large NSArray into smaller NSArrays, based on NSDictionary keys?
If the order of your splited Arrays is not important, i have a solution for you:
NSArray *arrOriginal;
NSMutableDictionary *grouped = [[NSMutableDictionary alloc] initWithCapacity:arrOriginal.count];
for (NSDictionary *dict in arrOriginal) {
id key = [dict valueForKey:#"roomType"];
NSMutableArray *tmp = [grouped objectForKey:key];
if (tmp == nil) {
tmp = [[NSMutableArray alloc] init];
[grouped setObject:tmp forKey:key];
}
[tmp addObject:dict];
}
NSMutableArray *marrApartmentsByRoomType = [grouped allValues];
This is quite performant
- (NSDictionary *)groupObjectsInArray:(NSArray *)array byKey:(id <NSCopying> (^)(id item))keyForItemBlock
{
NSMutableDictionary *groupedItems = [NSMutableDictionary new];
for (id item in array) {
id <NSCopying> key = keyForItemBlock(item);
NSParameterAssert(key);
NSMutableArray *arrayForKey = groupedItems[key];
if (arrayForKey == nil) {
arrayForKey = [NSMutableArray new];
groupedItems[key] = arrayForKey;
}
[arrayForKey addObject:item];
}
return groupedItems;
}
Improving #Jonathan answer
Converting array to dictionary
Maintaining the same order as it was in original array
//only to a take unique keys. (key order should be maintained)
NSMutableArray *aMutableArray = [[NSMutableArray alloc]init];
NSMutableDictionary *dictFromArray = [NSMutableDictionary dictionary];
for (NSDictionary *eachDict in arrOriginal) {
//Collecting all unique key in order of initial array
NSString *eachKey = [eachDict objectForKey:#"roomType"];
if (![aMutableArray containsObject:eachKey]) {
[aMutableArray addObject:eachKey];
}
NSMutableArray *tmp = [grouped objectForKey:key];
tmp = [dictFromArray objectForKey:eachKey];
if (!tmp) {
tmp = [NSMutableArray array];
[dictFromArray setObject:tmp forKey:eachKey];
}
[tmp addObject:eachDict];
}
//NSLog(#"dictFromArray %#",dictFromArray);
//NSLog(#"Unique Keys :: %#",aMutableArray);
//Converting from dictionary to array again...
self.finalArray = [[NSMutableArray alloc]init];
for (NSString *uniqueKey in aMutableArray) {
NSDictionary *aUniqueKeyDict = #{#"groupKey":uniqueKey,#"featureValues":[dictFromArray objectForKey:uniqueKey]};
[self.finalArray addObject:aUniqueKeyDict];
}
Hope, It will help when client wants final array in same order as input array.

iOS Memory Management/Persistence Issue

I am using a helper class in my app, to access a database and return an array of 5 objects, which I then assign to a property in my view controller.
In my view controller I call it like so:
- (void)viewDidLoad
{
[super viewDidLoad];
DatabaseHelper *helper = [[DatabaseHelper alloc] init];
self.trailersArray = [helper retrieveTrailers];
// Set trailer for first round
self.trailer = [self.trailersArray objectAtIndex:0];
// Prepare audio player
[self prepareToPlay];
// Swoop film screen in
[self swoopFilmScreenInAndAddPlayButton];
// Fade title in
[self fadeInTitleScreen];
// Initialise button array and set buttons to hidden
self.buttonOutlets = [NSArray arrayWithObjects:self.button1, self.button2, self.button3, self.button4, nil];
[self hideButtons];
// Initialise rounds
self.round = [NSNumber numberWithInt:-1];
// Initialise score which will also update graphics
[self initialiseScore:self.round];
self.scoreInteger = 0;
// Start first round
self.round = [NSNumber numberWithInt:0];
NSLog([self.trailersArray description]);
// User will trigger playing with Play IBAction
// User will trigger stop playing with Stop Playing IBaction
}
My problem is that once viewDidLoad is finished, the helper object seemingly disappears, as do its objects, and my self.trailersArray ends up pointing at nothing.
How do I fix this? Have tried deep copying, and using a retain attribute on the property but not working.
I can't use a class method because it ruins my helper object database methods but I am intrigued as to how class methods get around this memory problem?
Thanks for any help.
Alan
EDIT: As requested, code for retrieveTrailers below:
-(NSArray *)retrieveTrailers
{
// Get list of mp3 files in bundle and put in array
NSString *bundleRoot = [[NSBundle mainBundle] bundlePath];
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *dirContents = [fm contentsOfDirectoryAtPath:bundleRoot error:nil];
NSPredicate *fltr = [NSPredicate predicateWithFormat:#"self ENDSWITH '.mp3'"];
NSArray *onlyMP3s = [dirContents filteredArrayUsingPredicate:fltr];
NSMutableArray *arrayOfTrailersWithMP3s = [[NSMutableArray alloc] init];
// Query database for objects where (unique id) = (mp3 file title)
for(NSString *string in onlyMP3s)
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Trailer"];
NSString *stringWithNoFileExtension = [string stringByReplacingOccurrencesOfString:#".mp3" withString:#""];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", stringWithNoFileExtension];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *array = [[self managedObjectContext] executeFetchRequest:request error:nil];
Trailer *trailer = [array lastObject];
NSLog([NSString stringWithFormat:#"Trailer Available:%#", trailer.title]);
if(trailer)
{
[arrayOfTrailersWithMP3s addObject:trailer];
}
}
// Choose five of the trailers at random and return
NSMutableArray *fiveRandomSelectedTrailers = [[NSMutableArray alloc] init];
int numberOfAvailableTrailers = [arrayOfTrailersWithMP3s count];
for(int i=0;i<5;i++)
{
int rand = (arc4random() % (numberOfAvailableTrailers));
Trailer *trailer = [arrayOfTrailersWithMP3s objectAtIndex:rand];
NSLog([NSString stringWithFormat:#"Trailer Chosen:%#", trailer.title]);
[fiveRandomSelectedTrailers addObject:trailer];
[arrayOfTrailersWithMP3s removeObject:trailer];
numberOfAvailableTrailers --;
}
return fiveRandomSelectedTrailers;
}
If that really is you viewDidLoad code what you are doing is creating a local object of your helper class which is then out of scope once the function has completed.
If you have a retained property of the helper class, you don't need the declaration: try doing it this way
In your .h file have a line like this:
#property(retain, nonatomic) DatabaseHelper *helper;
In your .m file make sure you have:
#synthesize helper;
In your viewDidLoad:
self.helper = [[DatabaseHelper alloc] init];
self.trailersArray = [self.helper retrieveTrailers];
This way you are creating an object of your helper class and assigning it to property, instead of creating a local variable. And, as you can see, you can use the property object of your helper class when you want to send it messages.
I'm assuming you are using MRC instead of ARC.

Resources