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.
Related
I have an NSMutableArray of custom objects. Each contains an ID string that is unique, and each contains an downloadedDate property.
Objects could get added twice so I need to check the ID for duplicates, and if I find a duplicate I need to keep the one that has the newest date.
Currently I am doing the following to remove duplicates, but it doesn't take into account keeping the object with the newest date.
NSArray *originalArray = [NSArray arrayWithArray:mutableItems];
NSMutableArray *uniqueArray = [NSMutableArray array];
NSMutableSet *names = [NSMutableSet set];
for (ZSSObject *g in originalArray) {
NSString *destinationName = g.code;
if (![names containsObject:destinationName]) {
[uniqueArray addObject:g];
[names addObject:destinationName];
}
}
NSArray *uniqueObjects = uniqueArray;
Objects get created like this:
ZSSObject *obj = [ZSSObject alloc] init];
obj.code = #"12345";
obj.downloadedDate = [NSDate date];
Is there an easier way to do that I want than having a bunch of copies of my array and nested loops?
Using the suggestion to use an NSDictionary instead, I came up with this solution:
NSMutableDictionary *objDict = [[NSMutableDictionary alloc] init];
for (ZSSObject *g in mutableItems) {
ZSSObject *addedObj = objDict[g.code];
if (addedObj) {
// Compare dates
if ([addedObj respondsToSelector:#selector(dateDownloaded)]) {
if ([g.dateDownloaded compare:addedObj.dateDownloaded] == NSOrderedDescending) {
[objDict setObject:g forKey:g.code];
}
}
} else {
[objDict setObject:g forKey:g.code];
}
}
NSArray *uniqueObj = objDict.allValues;
I've a commun data in my application, and in some view I've to update those value only inside this view.
So I've created a local variable inside this view, then I set the value of those variable equal to the global variable and finally I've updated those global variable. This is my code :
if (_isCitySelector){
_dataArray = [[NSMutableArray alloc] initWithArray:[[Commun sharedInstance] stateArray]];
_subDataArray = [[NSMutableDictionary alloc] initWithDictionary:[[Commun sharedInstance] cityDictionary]];
} else {
_dataArray = [[NSMutableArray alloc] initWithArray:[[Commun sharedInstance] categoriesArray]];
_subDataArray = [[NSMutableDictionary alloc] initWithDictionary:[[Commun sharedInstance] subCategoriesDictionary]];
}
if (_activateParentSelection){
for (PFObject *object in _dataArray) {
NSMutableArray *tempArray = (NSMutableArray *)[_subDataArray valueForKey:object.objectId];
if ([[tempArray objectAtIndex:0][#"titre"] isEqualToString: #"الكل"])
continue;
PFObject *tempObject = [PFObject objectWithClassName:[object parseClassName]];
tempObject[#"titre"] = #"الكل";
if (object[#"nbrAnnonce"]){
tempObject[#"categorie_id"] = object;
tempObject[#"nbrAnnonce"] = #0;
}else
tempObject[#"region_id"] = object;
[tempArray insertObject:tempObject atIndex:0];
[_subDataArray setObject:tempArray forKey:object.objectId];
}
}
This code work's fine, but the problem this will update the global variable also ? what's wrong in my code !!!
Update
I can't use copyWithZone because my data type is PFObject and parse.com object doesn't support this function
You should try this init method for the array:
NSArray *dataArray = [[NSMutableArray alloc] initWithArray:[[Commun sharedInstance] stateArray] copyItems:YES];
This will create a separate copy of your data.
Can someone help me out on this:
Im creating a property in my TableVC.m file :
#property NSMutableArray *savingBeaconSpecs;
In my Viewdidload I instantiate the array:
NSMutableArray *savingBeaconSpecs = [[NSMutableArray alloc]init];
Now I do requests to the server, and I want to save the returned JSON into objects and save these each time in the array. So I did the following in the ConnectionDidFinishLaunching:
self.artworkArray = [NSJSONSerialization JSONObjectWithData:self.data options:0 error:&err];
NSLog(#"Log ArtworkArray in ConnectionDidFinishLoading%#", self.artworkArray);
And:
Artwork *artwork = [[Artwork alloc]init];
artwork.title = [self.artworkArray valueForKey:#"name"];
artwork.artist = [[self.artworkArray objectForKey:#"artist"] valueForKey:#"name"];
artwork.CreationYear = [self.artworkArray valueForKey:#"creationYear"];
artwork.categorie = [[self.artworkArray objectForKey:#"exposition"] valueForKey:#"name"];
Now I want to save this object into the savingBeaconSpecs NSMutableArray
[self.savingBeaconSpecs addObject:artwork];
But the NSMUtableArray savingBeaconSpecs always returns 0 when i try log his content
Anyone please?
Because you declare it locally in your viewDidLoad :
NSMutableArray *savingBeaconSpecs = [[NSMutableArray alloc]init];
you should use
self.savingBeaconSpecs = [[NSMutableArray alloc]init];
and
[self.savingBeaconSpecs addObject:artwork];
and declare your property as (without the first capital S)
#property NSMutableArray *savingBeaconSpecs;
To instantiate the array, you should do:
self.savingBeaconSpecs = [[NSMutableArray alloc] init];
or equally good:
self.savingBeaconSpecs = [NSMutableArray array];
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];
I have the following method in my UITableViewController subclass:
-(void)populateDataStorage{
NSString *path = [[NSBundle mainBundle] pathForResource:#"FakeData" ofType:#"plist"];
if(path){
NSArray *plistData = [[NSArray alloc] initWithContentsOfFile:path];
NSEnumerator *enumerator = [plistData objectEnumerator];
NSArray *personResults;
Photo *photo;
Person *person;
id currItem = [enumerator nextObject];
while (currItem != nil) {
photo = (Photo *)[NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext: [[FlickrFetcher sharedInstance] managedObjectContext]];
photo.name = [currItem objectForKey:#"name"];
photo.path = [currItem objectForKey:#"path"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name = %#", [currItem objectForKey:#"user"]];
personResults = [[FlickrFetcher sharedInstance] fetchManagedObjectsForEntity:#"Person" withPredicate:predicate];
if ([personResults count] > 0) {
person = [personResults objectAtIndex:0];
}
else {
person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:[[FlickrFetcher sharedInstance] managedObjectContext]];
person.name = [currItem objectForKey:#"user"];
}
photo.person = person;
[person addPhotosObject:photo];
NSLog(#"Photo %# added for user %#", photo.name, person.name);
currItem = [enumerator nextObject];
}
[plistData release];
}
}
And I call it in my apps didFinishLaunchingWithOptions method in my AppDelegate, the method is also in the same AppDelegate, when it's called I get the error it says there when debugging, if I don't debug the line it will run the method and load with no problem. If I don't debug at all it will not call the method.
EDIT: Changed the code according to the answer the problem still remains the same, if I just run nothing happens but when debugging I get the error. When I debug the whole method no error is shown.
Your call to NSLog is trying to access the name property of your person variable. However, when you declared your person variable, you didn't initialize it, so it points to garbage. You only give it a valid value in your else clause, so sometimes your NSLog is accessing an uninitialized object.