Basic IOS OOP - structure and implementation - objective-c

I am creating a basic guessing game in iOS for my kids, and I think there are some fundamental gaps in my understanding of how I should be creating and releasing objects throughout the lifecycle of the app. I have been reading up on retain and release cycles but I think my issue is more to do with the fundamental architecture of the app and how I may be poorly trying to instantiate and then kill a few key objects of the app.
The problem centers around two specific classes.
I have a game class, which I have designed to hold all the information that the game requires to run. When it is init-ed, it holds all instance variables that point to arrays that hold strings such as the various clues, etc. It's basically a container for all the data that the game requires.
I have a game view controller, that creates and an instance of the game class and queries it so as to present on screen the various elements contained with the game object.
This works perfectly the fine. When the user starts a new game, a new instance of the game class is allocated and init-ed and away they go.
The issue comes in when I come to generate a new game. This happens a number of ways. Either The user finishes the game and starts another one or the user quits the current game and then starts a new one.
In my thinking, I would just release the game object and alloc and init a new one. However, I notice running on the device and looking through the profiler, that the game object isn't released at all.It's still, there and each instantiation of the game creates a new game object with the old one still sitting there with no pointers to it.
Fiddling around with the code, I noticed that I did not implement a dealloc method in the Game class...but when I try to do that, the app crashes, I suspect because I am trying to release a previously released object.
Ideally what I am trying to do is get rid of the old Game object, or replace the old one (overwrite) with a new one each time a new game is started.
However, is this approach wrong? Should I be doing it a completely different way? Such as only ever creating a single instance of the game class and rewriting a method inside that class so as to generate a new set of clues, etc everytime a new game starts and the GameViewController tells it to?
Is there a 'best practice' way to do this?
So you've got an idea of what I am doing, code is below for the GameViewController, where an instance of the Game class is created:
#import "GameViewController.h"
#implementation GameViewController
#synthesize game = _game;
-(void)startNewGameOfLevel:(NSInteger)level
{
if(!_game)
{
Game *g = [[Game alloc]initGamewithLevel:level];
[self setGame:g];
[g release]; g = nil;
}
[self set_currentlevel:[_game _currentLevel]];
// set up popover to show the rounds goal letter
[self setUpPopOver];
}
-(void)quitTheCurrentGameAndStartNewGame
{
[_game release]; _game = nil;
[self clearGamePlayingField];
animationStepIndex = 0;
[self startNewGameOfLevel: _currentlevel];
}
Game class (abridged) with the designated initializer of the Game class:
#import "Game.h"
#implementation Game
#synthesize arrayOfLowerCaseLetters = _arrayOfLowerCaseLetters;
#synthesize arrayOfPhrases= _arrayOfPhrases;
#synthesize goalLetter = _goalLetter;
#synthesize goalPhrase = _goalPhrase;
#synthesize gameLetterPool = _gameLetterPool;
#synthesize _indexForGoalLetter, _numberOfLevelsInGame, _currentLevel, _numberOfWhackHoles, _numberOfLettersInGameLetterPool;
-(id)initGamewithLevel:(NSInteger)level
{
[super init];
//create an array of lower case letters. These will
//contain the full alphabet of all possible letters
NSArray *arrayOfLCLetters = [[NSArray alloc] initWithObjects:#"a", #"b", #"c", #"d",#"e", #"f", #"g", #"h", #"i", #"j", #"k", #"l", #"m", #"n", #"o", #"p", #"qu", #"r", #"s", #"t", #"u", #"v", #"w", #"x",#"y", #"z",#"ch", #"sh", #"th", nil];
[self setArrayOfLowerCaseLetters: arrayOfLCLetters];
[arrayOfLCLetters release];arrayOfLCLetters = nil;
//create an array of phrases.
// These must correspond with each of the letters. e.g. a = apple.
NSArray *phrases= [[NSArray alloc ] initWithObjects:
#"apple",
#"butterfly",
#"cat",
#"dog",
#"egg",
#"frog",
#"ghost",
#"horse",
#"igloo",
#"jam",
#"kite",
#"leaf",
#"moon",
#"nut",
#"orange",
#"pig",
#"queen",
#"rabbit",
#"snake",
#"tree",
#"umbrella",
#"van",
#"water",
#"x-ray",
#"yak",
#"Zebra",
#"chair",
#"shoes",
#"thumb",
nil];
[self setArrayOfPhrases:phrases];
[phrases release]; phrases = nil;
//choose a random number to be the index reference for
// each goal letter and goal phrase.
[self set_indexForGoalLetter:(arc4random()%[_arrayOfLowerCaseLetters count])];
NSLog(#"index for goal letter is:, %i", _indexForGoalLetter);
//set Goal letter and goal phrase
[self setGoalLetter: [_arrayOfLowerCaseLetters objectAtIndex: _indexForGoalLetter]];
[self setGoalPhrase: [_arrayOfPhrases objectAtIndex:_indexForGoalLetter ]];
//set current level
[self set_currentLevel: level];
//[self set_currentLevel: 2];
//set number of whackholes by level
[self set_numberOfWhackHoles: [self numberOfWhackHolesByLevel:_currentLevel]];
//generate size of Letter pool by level
[self set_numberOfLettersInGameLetterPool:[self numberOfLettersInLetterPoolbyLevel:_currentLevel]];
////////////////////////////
/// Game letter pool
///////////////////////////
//set up array ton hold the pool of letters
NSMutableArray *gp = [[NSMutableArray alloc] initWithCapacity:_numberOfLettersInGameLetterPool];
[self setGameLetterPool: gp];
[gp release];gp = nil;
//add the goal letter to this pool
[_gameLetterPool addObject:_goalLetter];
int i = 1;
while (i < _numberOfLettersInGameLetterPool) {
NSString *letter = [_arrayOfLowerCaseLetters objectAtIndex:(arc4random()%[_arrayOfLowerCaseLetters count])];
if ([_gameLetterPool containsObject:letter] == false)
{
[_gameLetterPool addObject:letter];
i++;
}
}
NSLog(#"********** Game created ***************");
NSLog(#"pool of letters is: %#", [_gameLetterPool description]);
NSLog(#"****************************************");
NSLog(#"current goal letter is: %#", _goalLetter);
NSLog(#"****************************************");
NSLog(#"current goal phrase is: %#", _goalPhrase);
NSLog(#"****************************************");
return self;
}
-(void)dealloc
{
[super dealloc];
[_arrayOfLowerCaseLetters release]; _arrayOfLowerCaseLetters = nil;
[_arrayOfPhrases release]; _arrayOfPhrases = nil;
[_goalLetter release];_goalLetter = nil;
[_goalPhrase release]; _goalPhrase = nil;
[_gameLetterPool release];_gameLetterPool = nil;
}

The number one problem is that [super dealloc] must be the absolute last thing you do in -dealloc. This is because it is the dealloc method in NSObject that actually frees the memory, so by the time you get back to it, your instance variable pointers may already be garbage.
Other issues:
In init, do self = [super init]; The super object is allowed to return a different self pointer on init.
startNewGameOfLevel: and quitTheCurrentGameAndStartNewGame should use the property, not the bare instance variable.
-(void)startNewGameOfLevel:(NSInteger)level
{
if(![self game])
{
Game *g = [[Game alloc]initGamewithLevel:level];
[self setGame:g];
[g release]; g = nil;// g = nil, not necessary when it's about to go out of scope
}
[self set_currentlevel:[[self game] _currentLevel]]; // don't use _ to start methods - Apple reserves this convention
// set up popover to show the rounds goal letter
[self setUpPopOver];
}
-(void)quitTheCurrentGameAndStartNewGame
{
[self setGame: nil];
[self clearGamePlayingField];
animationStepIndex = 0;
[self startNewGameOfLevel: _currentlevel];
}
There are probably other issues in the body of your code - make sure you build with static analysis enables - it will catch many of them.

Related

Is a value inside an NSDictionary released when removed?

I have something along the line of this :
#implementation ImageLoader
NSMutableDictionary *_tasks;
- (void) loadImageWithURL:(NSURL *)url callback:(SMLoaderCallback)callback {
NSMutableArray *taskList = [_tasks objectForKey:urlString];
if (taskList == nil) {
taskList = [[NSMutableArray alloc] initWithCapacity:5];
[taskList addObject:callback];
[_tasks setObject:taskList forKey:urlString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [[UIImage alloc] initWithData:data];
for (SMLoaderCallback cb in taskList) {
cb(image, nil);
}
[_tasks removeObjectForKey:url.absoluteString];
});
});
} else {
[taskList addObject:callback];
}
}
#end
In here, I'm trying to queue up image downloads to gain performance (as an exercise only). So I'm keeping a NSDictionary that maps an URL with an array of callbacks to be called whenever the data is downloaded.
Once the image is downloaded, I no longer need this array, so I remove it from the dictionary.
I would like to know if, in this code (with ARC enabled), my array along with the callbacks are correctly released when I call [_tasks removeObjectForKey:url.absoluteString].
If your project uses ARC and that dictionary is the only thing that is referencing to those values, yes. it will be remove permanently.
ARC keeps track of number of objects that is pointing to some other object and it will remove it as soon as the count reaches 0.
so adding to dictionary -> reference count += 1
removing from dictionary -> reference count -= 1
Somewhe

Open ears text to speech (voice) not working when getting string from another class/controller (IOS, Objective c)

I am very new to objective c and OpenEars so please forgive me if I have some messy code and if I am lost in very simple problem.
Anyhow, I have two controllers in this application. The first being the default ViewController and the second one being a new one that I made called ReplyManagerController.
The code in the ViewController basically uses the one in the tutorial with some (maybe more some) changes.
EDIT:
The app is supposed to be a basic app where a user says something and the app replies.
But the original problem was that I could not get the string to display or TTS to work when my ViewController got it's string from another class/controller.
The answer in my below mentions that it was probably because my other class was calling my ViewController without the self.fliteController initialized.
How would I initialize the ViewController with self.fliteController initialized?
ViewController.m
- (void) pocketsphinxDidReceiveHypothesis:(NSString *)hypothesis recognitionScore:(NSString *)recognitionScore utteranceID:(NSString *)utteranceID {
NSString *strResult = hypothesis; //speech to text to string
ReplyManager* replyObject = [[ReplyManager alloc] init];
[replyObject speechResult:(NSString*)strResult viewController:self];
}
- (void) getReply:(NSString*)replyStr{
[self.fliteController say:replyStr withVoice:self.slt];
[self updateText:replyStr];
}
- (IBAction)updateText:(NSString*)replyStr{
labelOne.text = replyStr;
labelOne.adjustsFontSizeToFitWidth = YES;
labelOne.minimumFontSize = 0;
}
Any help will be great! Thanks!
ReplyManager.m
- (void) speechResult:(NSString*)strResult {
NSString *replystr;
NSString *greetings = #"Hi!";
NSString *sorry = #"Sorry I didn't catch that?";
ViewController* getReply = [[ViewController alloc] init];
if ([strResult isEqualToString:#"HELLO"])
{
replystr = greetings;
[getReply getReply:(NSString*)replystr];
}
else
{
replystr = sorry;
[getReply getReply:(NSString*)replystr];
}
}
EDIT 2:
viewDidLoad Method
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.fliteController = [[OEFliteController alloc] init];
self.slt = [[Slt alloc] init];
self.openEarsEventsObserver = [[OEEventsObserver alloc] init];
[self.openEarsEventsObserver setDelegate:self];
}
ViewController* getReply = [[ViewController alloc] init];
Here you init a new ViewController which does not have self.fliteController defined most likely. You need to reuse previos controller, for example like this:
[replyObject speechResult:(NSString*)strResult viewController:self];
So you can use already initialized viewController later. Overall it is better to initialize objects like viewController or replyController beforehand, not inside callback methods.
It sounds like a timing issue where you're trying to use fliteController before it's been initialized.
In your ViewController class, where do you assign a value to the fliteController property? In an initializer? -(void)viewDidLoad?
In ReplyManagerController add:
ViewController* getReply = [[ViewController alloc] init];
// Add these lines
NSLog(getReply.fliteController); // Is this nil?
[getReply.view layoutIfNeeded];
NSLog(getReply.fliteController); // Is it still nil?
Does the above fix the problem? If so, you're probably initializing fliteController in -(void)viewDidLoad. What's the result of the two NSLog statements?

Storing a string from a text field as a Player name Objective C

I am trying to have multiple players for a card matching game. I have made a class that is a child of NSObject called Player. A Player object will have a name, high score, last score, and boolean is currently playing instance. I am trying to create a new player every time a new name is entered into a text field. I will work on storing the players later. Right now I just wish to make a new Player, have a user enter their name in the text field. And store the user's response in the text field. The following is my Player.m file
#import "Player.h"
#implementation Player
-(NSString *) nameOfPlayer:(Player *)playerName{
return self.name;
}
-(void) setPlayerName:(NSString *) nameOf{
self.name = nameOf;
self.lastScore = 0;
self.highestScore = 0;
self.playing = YES;
return;
}
-(id)init{
self = [super init];
self.name = #"";
self.lastScore = 0;
self.highestScore = 0;
self.playing = YES;
return self;
}
#end
I didn't originally have that init until I tried looking for a solution online. I am a bit new to iOS coding so I wasn't sure how to set up a constructor (or if they even have those). Below is how I tried to instantiate a Player object:
- (IBAction)handleButtonClick:(UIButton *)sender {
Player * friend = nil;
nameText = self.nameEntryField.text;
[friend setPlayerName:nameText];
NSLog(#"%#", [friend nameOfPlayer:friend]);
}
My app breaks its thread as soon as I try to setPlayerName. I am a bit stuck on this so any help would be appreciated.
- (IBAction)handleButtonClick:(UIButton *)sender {
Player * friend = [[Player alloc] init]; // Initialize this
nameText = self.nameEntryField.text;
[friend setPlayerName:nameText];
NSLog(#"%#", [friend nameOfPlayer:friend]);
}
You have done like Player * friend = nil; mean this is the nil object. And then you are trying to use method setPlayerName:on nil object. Because of this app breaks.
So you need to initialize object using [[Player alloc] init].
This is embarassing. I was unfamiliar with the xCode IDE and accidentally had selected that line of code for a breakpoint. The code is fine after previous answer.

Can't Add Object to NSMutableArray

I have a class called KCBlackjack. The class contains this code:
playerHand = [[NSMutableArray alloc] init];
dealerHand = [[NSMutableArray alloc] init];
blackjack = [[KCBlackjack alloc] initWithNumberOfDecks:6];
[self deal];
[blackjack dealTo:playerHand dealer:dealerHand];
- (void)dealTo:(NSMutableArray *)player dealer:(NSMutableArray *)dealer {
// How many cards are left
NSLog(#"Cards in Deck: %d", [_decks count]);
// Deal to player then dealer
for (int i = 0; i <= 1; i++) {
[player addObject:[_decks lastObject]];
[_decks removeLastObject];
NSLog(#"%#", player);
if(_delegate && [_delegate respondsToSelector:#selector(didDistributeCard:withValue:)]) {
KCCard *aCard = (KCCard *)[player objectAtIndex:player.count-1];
[_delegate didDistributeCard:aCard to:player withValue:[aCard value]];
}
[dealer addObject:[_decks lastObject]];
[_decks removeLastObject];
NSLog(#"%#", dealer);
if(_delegate && [_delegate respondsToSelector:#selector(didDistributeCard:withValue:)]) {
KCCard *aCard = (KCCard *)[dealer objectAtIndex:dealer.count-1];
[_delegate didDistributeCard:aCard to:dealer withValue:[aCard value]];
}
}
NSLog(#"Done Dealing");
NSLog(#"Cards Remaining in Deck: %d", [_decks count]);
NSLog(#"Player: %#\n\n", player);
NSLog(#"Dealer: %#\n\n", dealer);
}
Inside of my game controller, I set player to my player array as well as the dealer his. When this is run however, it doesn't work. No objects are added to the player array that player or dealer is assigned.
When the code is in the game controller, it works, but not in this class. I figure something is not initializing, but in the game controller, the player and dealer are both initialized.
If you think of it from a OO perspective, dealer and player are really objects that should receive a message such as addCardToHand:(Card )aCard. The way you are doing it, Lucas has exactly right, you get a copy of his array which is not mutable. I'd much rather see some dot notation such as player.hand addCard:(Card)aCard if you don't want the player to handle the card himself (watch for card sharps! :-) ).
I think if you refactor with objects you can have clean code that works, and probably get rid of that delegation stuff (which is a bit confusing just reading).
Good luck - blackjack is fun!
Damien

Memory Leak when using for(object in array) with iPhone SDK

I am running into some serious memory leaks in one of my applications I am building. I have a UINavigatonController that is inside a UITabBarview. Inside the NavView is a MKMap view. When you click an accessory button on a callout a detail view is loaded. In that detail view I am trying to populate a table from a plist using a for(object in array) loop. The plist is an array of dictionaries. I am running though the dictionaries to find one with a key that is the title of the callout and then get an array from inside that dictionary. It all works fine in the simulaor but I am getting massive memory leaks doing it the way I am. Any Idea whats going on?
- (void)viewDidLoad {
self.title = #"Route Details";
NSString *path = [[NSBundle mainBundle] pathForResource:#"stopLocation" ofType:#"plist"];
holderArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
[self getRouteArray];
routeDetails.delegate = self;
routeDetails.dataSource = self;
}
-(void)getRouteArray{
for (NSMutableDictionary *dictionary in holderArray) {
//NSString *stopName = [dictionary objectForKey:#"StopName"];
//NSString *stopName = [[NSString alloc] initWithString:[dictionary objectForKey:#"StopName"]];
BOOL testString = [currentRoute isEqualToString:[dictionary objectForKey:#"StopName"]];
if (testString) {
routeArray = [[NSMutableArray alloc] initWithArray:[dictionary objectForKey:#"RouteService"]];
}
}
}
- (void)dealloc {
[routeArray release];
[routeDetails release];
[super dealloc];
}
holderArray is an ivar and so is route array. As you can see I have tried a few ways of allocating the nstrings and arrays but all seem to yield the same leaks. According to the performance tool I am leaking from NSCFString, NSCFDictionary, and the NSCFArry. I released the routeArray in the dealloc and it works fine, but if I release holderArray it crashes whenever I go back to my map from the detail view. I guess I am just really unsure as to how to deal with the strings and dictionary used in the for loop.
Just to add the detail view is being created like so:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control{
NSString *selectedRouteName = [NSString stringWithFormat:#"%#",view.annotation.title];
RouteDetailView *rdc = [[RouteDetailView alloc] initWithNibName:#"RouteDetailView" bundle:nil];
[rdc setCurrentRoute:selectedRouteName];
[self.navigationController pushViewController:rdc animated:YES];
[rdc release];
}
Sorry if any of the above is unclear. Let me know and I can try to rephrase it.
Will testString be true for at most one key in holderArray? If so, you should probably break out of the loop after setting routeArray. If not, then you may be setting routeArray multiple times, and all but the last array you assigned to it would be leaked.
Also, I don't see you releasing holderArray.