objective-c/iphone sdk newcomer question - iphone

At present I'm building a basic application to learn Objective-C and the iPhone SDK.
I'm creating NSObject with getters and setters to get to grips with how these works. I've successfully added a property and getters and setters to my main controller, currently I'm trying to create a separate class which I can create a new instance of within my controller but it crashes when I try and use the setter.
Thank you in advance for your time, sorry if this question is as stupid as I'm sure it is.
Here's the header for my class
QuizQuestion.h
#import <Foundation/Foundation.h>
#interface QuizQuestion : NSObject {
NSString *question;
}
#property (retain) NSString* question;
#end
QuizQuestion.m
#import "QuizQuestion.h"
#implementation QuizQuestion
#synthesize question;
- (void) dealloc
{
[question release];
[super dealloc];
}
#end
And here is my controller code (i've cut some out)
#implementation Quiz2ViewController
#class QuizQuestion; // Is this correct?
- (void)viewDidLoad {
QuizQuestion *aQuestion;
//gets here fine, but crashes (the app closes) when I set question.
[aQuestion setQuestion:#"hello world"];
[super viewDidLoad];
}
#end
As well as #class I tried import "QuizQuestion.h" and I get the same issue.

You're not actually allocating an instance of the QuizQuestion class—your aQuestion variable isn’t pointing to anything in particular, so trying to send it a message, like -setQuestion:, is sending that message to... well, there’s no telling where, and sending things messages that aren’t meant for them is a surefire way to crash your app. What you need to do is this:
QuizQuestion *aQuestion = [[QuizQuestion alloc] init];
[aQuestion setQuestion:#"hello world"];
You also need to call [aQuestion release] at some point, or you’ll leak the memory associated with it.

You need to allocate space and initialize the QuizQuestion.
QuizQuestion *aQuestion = [[QuizQuestion alloc] init];
before setting the question.

I think you need to read up on some documentation before you try any more coding. Apple has several intro programming guides that are very good. The problem you are having is addressed in this section:
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocAllocInit.html

QuizQuestion *aQuestion = [[QuizQuestion alloc] init];
[aQuestion setQuestion:#"hello world"];
[aQuestion release];

Related

Sharing data/string with a singleton between views

I'm trying to share a string between two views on my iPhone project. It currently works if I use the actual #"something here" for the string, but if I want to use something like label.text, it doesn't even though it is still a string.
I'll show you what I have to make it clearer.
First View: Info_ViewController.h
#import <UIKit/UIKit.h>
#interface Info_ViewController : UIViewController {
IBOutlet UITextField *locationField;
}
#property (nonatomic, retain) NSString *locationString;
+ (id)sharedInfoVC;
#end
First View: Info_ViewController.m
#import "Info_ViewController.h"
static Info_ViewController *sharedInfoVC = nil;
#implementation Info_ViewController
#synthesize locationString;
#pragma mark Singleton Methods
+ (id)sharedInfoVC {
#synchronized(self) {
if (sharedInfoVC == nil)
sharedInfoVC = [[self alloc] init];
}
return sharedInfoVC;
}
- (id)init {
if (self = [super init]) {
locationString = [[NSString alloc] initWithString:locationField.text]; //This is there part I mentioned earlier, when using #"something" instead of locationField.text works.
}
return self;
}
Second View: Confirm_ViewController.m
#import "Confirm_ViewController.h"
#import "Info_ViewController.h"
#implementation Confirm_ViewController
- (IBAction)buttonZ:(id)sender
{
Info_ViewController *infoVCmanager = [Info_ViewController sharedInfoVC];
locationLabel.text = infoVCmanager.locationString;
}
I put it under a button for now, but it will eventually be under viewDidLoad.
If you replace locationField.text with a string (#"blahblahblah") it won't crash and works.
When it crashes I get the error: Program received signal: "SIGABRT"
EDIT: I tried changing
initWithString:locationField.text
to
initWithFormat:#"%#",locationField.text
and now it my label in the second view prints "(NULL)"
Thanks for taking the time to give advice, I really appreciate it.
It is an error to pass nil as the format string to -[NSString initWithString].
So how are you passing nil? You actually have two instances of Info_ViewController. You have the one instance which is the normal part of your app, and then you also have a second instance which is your "singleton" (which really isn't a singleton any more).
So in your "singleton" instance, the UITextField is nil (and will always be nil) and so locationField.text is nil and you are passing that to initWithString:, which is a crash. In fact the "singleton" isn't even fully baked as view controller's go.
If you want a singleton to share data elsewhere in your app, it really should not be a Info_ViewController or any type of view controller. It should be of some other class that you use to manage your data. I would create another class and implement that as a singleton.
Hope that helps you understand what's happening here.
Pre-pend "self." to your location string.
self.locationString = [[NSString alloc] initWithString:locationField.text];
From what I understand of your code, you have got the value for locationString when you from the textfield when you initialize the viewController. At this point of time, your textfield would not be visible. After it becomes visible and you enter something, you don't have the code to store it to locationString.
What you should do is wait for Info_ViewController object to be initialized and displayed. Then on the press of some button or some other event, assign locationLabel.text from the locationString or even directly from locationField.text.
I would provide code, but I have no clue as to how you are structuring this. If you still need help, please provide the details.

where do I instantiate my class so I can access it throughout my app?

I've been working through some objective-c/ iOS dev books and I've hit a stumbling block. I get the feeling that I'm missing something dumb here, so I'm sure you guys can help, because you're all so damn smart :-).
I've got a very simple app that consists of 1 button and 1 label. Pushing the button puts a message in the label. I've created a class that includes a method to create said message. Here is the problem:
#import "classTestViewController.h"
#implementation classTestViewController
#synthesize myLabel;
- (void)viewDidLoad
{
}
-(IBAction) pressGo:(id)sender{
MyClass * classTester = [[MyClass alloc] init];
classTester.count = 15;
NSString *newText = [classTester makeString ];
myLabel.text = newText;
}
- (void)dealloc
{
[classTester release];
[myLabel release];
[super dealloc];
}
The output of this app, in my label, is "Yay 15". So you can see the problem, the only way I can get this to work is to instantiate the class right there, in the "pressGo" method. This isn't desirable because another method can't access or change the class variable count. Also I get a warning that local declaration of classTester hides instance variable. If I move the class instantiation to the viewDidLoad method, which seems right, the other methods can't access it anymore.
#import "classTestViewController.h"
#implementation classTestViewController
#synthesize myLabel;
- (void)viewDidLoad
{
MyClass * classTester = [[MyClass alloc] init];
}
-(IBAction) pressGo:(id)sender{
classTester.count = 15;
NSString *newText = [classTester makeString ];
myLabel.text = newText;
}
- (void)dealloc
{
[classTester release];
[myLabel release];
[super dealloc];
}
The output of that is nada. If I try to access just one variable, classTester.count, for example, even after setting it, I get a 0 value. I also get the override warning here as well.
So my question is, how can i get access to that class instance throughout my app and not just in one method? I'm using a view based application.
Declare classTester in your interface file with:
#class MyClass
#interface classTestViewController : UIViewController
{
MyClass *classTester;
}
// Any other custom stuff here
#end
Then instantiate it in your viewDidLoad method with:
classTester = [[MyClass alloc] init];
And you should be able to access the ivar from any method within this class. If you want it to be accessible to your entire app, #Waqas link will point you in the right direction.
You need to create a singleton class which instantiate once and is available inside whole project
Have a look
http://projectcocoa.com/2009/10/26/objective-c-singleton-class-template/

NSMutableArray count keeps changing

I have too much code to know which i need to quote here, but in my app delegate I have an NSMutableArray. Then in another class, it creates a new entry to the NSMutableArray but upon passing back to another class which should use that to display something on screen, it doesn't display anything. Putting an NSLog for the NSMutableArray count at the end of the class creating it displays the number 1, and then putting the same NSLog code at the start of the class which is meant to use that returns 0.
Any ideas why this is?
EDIT: Ok, i'll try and include all related code..
app delegate.h:
#interface palettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (assign, readwrite) NSMutableArray *colourPalettesContainer;
#end
app delegate.m:
#import "palettesAppDelegate.h"
#implementation palettesAppDelegate
#synthesize colourPalettesContainer;
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
Homeview.h:
#import <UIKit/UIKit.h>
#import "HandlingPalettes.h"
#interface HomeView : UIViewController {
HandlingPalettes *handlingPalettes;
}
#end
Homeview.m:
#import "HomeView.h"
#import <QuartzCore/QuartzCore.h>
#implementation HomeView
- (void)viewDidLoad {
[super viewDidLoad];
handlingPalettes = [[HandlingPalettes alloc] init];
[handlingPalettes newPalette];
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(#"view will appear: %i", [dataCenter.colourPalettesContainer count]);
int numberOfExisting = [dataCenter.colourPalettesContainer count];
}
- (void)dealloc {
[handlingPalettes release];
[super dealloc];
}
#end
HandlingPalettes.h:
#import <UIKit/UIKit.h>
#interface HandlingPalettes : UIViewController {
}
-(void)newPalette;
#end
HandlingPalettes.m:
#import "HandlingPalettes.h"
#import "HomeView.h"
#import "palettesAppDelegate.h"
#implementation HandlingPalettes
-(void)newPalette {
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
//If this is the first palette
if (dataCenter.colourPalettesContainer == nil) {
dataCenter.colourPalettesContainer = [[NSMutableArray alloc] init];
}
//Add a new palette
[dataCenter.colourPalettesContainer addObject:#"Test1", #"Test2", nil];
NSLog(#"Handling: %i", [dataCenter.colourPalettesContainer count]);
}- (void)dealloc {
[super dealloc];
}
#end
Your main mutablearray is in your app delegate. So, see what happens if in EVERY METHOD that you want to access the array you have the line to set up the app delegate relationship
palettesAppDelegate *dataCenter = (palettesAppDelegate *)[[UIApplication sharedApplication] delegate];
Now, when you call the dataCenter object you will be referencing the App Delegate and your program will find the array.
You may also find that you will need to have an #import "palettesAppDelegate.h" in each object that is going to reference the App Delegate.
Note, just adding the app delegate code is not necessarily the proper way to deal with this issue from an architectural standpoint. But if it works you at least know the answer to your original question.
I suspect the problem is ultimately related to confused memory management of the colourPalettesContainer member. You release it in the app delegate's dealloc method, but that class never retains it! It would be much cleaner if you'd follow Apple's memory management guidelines: your classes should only release objects that they own (i.e., that they themselves retained earlier). For example, you can do this by declaring the array's property retain:
#property (retain) NSMutableArray *colourPalettesContainer;
(To prevent leaking the array, you'll also need to release or autorelease it in the newPalette method. Retain and release should always come in close pairs.)
But even better, why not simply create the array in the app delegate's init method, or in its accessor (if for some reason you want to continue creating it only on its first use)? Unless you want to replace all palettes at once, there is no reason to let the array be assigned to from outside the app delegate.
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
#private
NSMutableArray *colourPalettesContainer;
}
#property (readonly) NSMutableArray *colourPalettesContainer;
#end
#implementation PalettesAppDelegate
- (NSMutableArray *)colourPalettesContainer {
if (colourPalettesContainer == nil) {
colourPalettesContainer = [[NSMutableArray alloc] init];
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
To make the design even cleaner, change the type of the colourPalettesContainer property to NSArray * and add an -addPalette: method to the app delegate. (It is rarely a good idea to publicly expose a mutable array inside a class.) You can then simply get rid of -newPalette in HandlingPalettes. (If you want to have all your palette-handling methods in HandlingPalettes, then simply move the array there. If you need to access the palettes from random places in your app, then you can simply put a retained reference to your HandlingPalettes object in the app delegate.)
Once you clean up the object ownership mess, the count mismatch will either resolve itself "by magic" or the cause will likely become much more obvious. In the latter case, check that the HomeView's dataCenter is actually the same object as the one in HandlingPalettes. (You omitted how HomeView gets its reference — are you sure you aren't creating another instance of the app delegate by accident?)
(By the way, you probably meant to use -addObjects:, not -addObject: in newPalette. Note also that all class names should be capitalized, with no exceptions: i.e., always use PalettesAppDelegate, never palettesAppDelegate. If for some reason Xcode's project template created it like that, simply rename the class. Lowercase class names are much too easy to confuse with variable names. Also, try to find better names in general: e.g., instead of HandlingPalettes, I'd use PalettesViewController (to reflect the fact that it is a subclass of UIViewController); and instead of dataCenter, I'd rather just choose appDelegate.)
I would be inclined to get rid of the newPalette method, and instead create a getter method for colourPalettesContainer in your app delegate.
ie:
appdelegate.h
#interface PalettesAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *colourPalettesContainer;
}
#property (non-atomic, retain) NSMutableArray *colourPalettesContainer;
#end
#implementation palettesAppDelegate
appdelegate.m
#import "appdelegate.h"
#synthesize colourPalettesContainer;
- (NSMutableArray *) colourPalettesContainer{
if(colourPalettesContainer==nil){
colourPalettesContainer=[[NSMutableArray alloc] init];
}
return colourPalettesContainer;
}
- (void)dealloc {
[colourPalettesContainer release];
[super dealloc];
}
#end
you should then be able to add items by calling
[appDelegate.colourPalettesContainer addObject:object];

Confused why one piece of code works and another doesn't in Objective C

Sorry about the title being extremely vague, I'm new to Objective C and struggling a little with it. Basically I have the following section of code:
Graph *graph1 = [[Graph alloc] init];
[graph1 addNode:#"TEST"];
which is working to a degree. But I want to change it because the above code happens on a button press, and therefore I assume I am creating a new "*graph1" every time I do this. I thought I could simply change it to this:
if(self = [super init])
{
[self setGraph: [[Graph alloc] init]];
}
return self;
Where the above is in the init method, and below is the modified function:
[graph addNode:#"TEST"];
However when debugging I've found addNode method is never called when it's like this.
Thanks
Zac
This is testViewController.h
#import <UIKit/UIKit.h>
#class Graph;
#class Node;
#interface testViewController : UIViewController {
Graph *graph;
UILabel *label;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#property (nonatomic, retain) Graph *graph;
- (IBAction) buttonPressed:(id)sender;
#end
This is textViewController.m
#import "testViewController.h"
#import "Graph.h"
#implementation testViewController
#synthesize label, graph;
- (id)init
{
if(self = [super init])
{
[self setGraph: [[Graph alloc] init]];
}
return self;
}
- (IBAction)buttonPressed:(id)sender
{
//Graph *graph1 = [[Graph alloc] init];
[graph addNode:#"TEST"];
Node *node1 = [[Node alloc] initWithLabel: #"LABEL"];
label.text = node1.label;
}
The first thing that comes to mind is that graph is nil and thus invoking a method (sending a message) to it will result in nothing. An unwanted release will cause an EXC_BAD_ACCESS, and this seems to be not the case.
I suppose you are calling all of this in a UIViewController subclass, right? Are you sure the right init is called? If you are using a NIB you should override the -(id)initWithNibName:bundle: and place you code there. I guess the code is probably in the plain -(id)init, since you are calling [super init] and not [super initWithNibName:nameOrNil bundle:bundleOrNil], but this way, if initialize the controller with the NIB you custom code is never called and thus graph is nil.
By the way, if the graph property is (retain) you are also causing a memory leak in the init.
I'm sure you're through this problem now, but I agree that the reason "add" is "not being called" is that your graph object is nil at that moment
I'd say, first put a test message around your "addNode" call
NSLog(#"We tried to add here");
[graph addNode:#"TEST"];
That will show you that the add is being called -- I bet it is.
Then, where you had your previous call to initialize "graph" right before your add call, try conditionally initializing it:
if(graph == nil) graph = [[Graph alloc] init];
[graph addNode:#"TEST"];
Note, this is all just to find the problem. Finally I'd say you have some challenges in here with how you are dealing with memory. You may not have reference issues, but later leaks. And depending upon how often this code is executed it could be an issue.
But at least you may get to your issue easier with the above tests.
Have you declared the graph variable in the header? ie: Graph *graph; and the corresponding #property (nonatomic, retain) Graph *graph;
Then I would do this:
-(id) init {
graph = [[Graph alloc] init];
[graph retain];
}
that might help (the only reason I think it wouldn't would be because a) if your variable wasn't declared then you would get a warning like graph may not respond to addNode and if it wasn't retained then your app would crash when it runs)... other than that, I can't see what would be the problem. If that doesn't work, can you please post all your code from your .h and .m files?
Then I would do this:
-(id) init {
graph = [[Graph alloc] init];
[graph retain];
}
that might help
This would result in a memory leak. The retain count of the object pointed to by graph will have a retain count of 2. Not ideal. If you declare the property with the retain attribute then
[self setGraph:[[[Graph alloc] init] autorelease]];
should do it. I go with -
self.graph = [[[Graph alloc] init] autorelease];
There could be many reasons the addNode: method is not being called. Put break points in the enclosing method and see if everything is working as you expect it to.

Problem with dealloc method - 'xmlEntity' is not an Objective-C class name or alias

I am in the process of cleaning my code and testing for bugs, when I came across this build error: ['xmlEntity' is not an Objective-C class name or alias]. Here is a shorten version of my class .h file.
#interface PMXMLParser : NSXMLParser {
NSMutableDictionary *xmlEntity;
NSMutableDictionary *collectionDict;
}
#property (nonatomic, retain) NSMutableDictionary *xmlEntity;
#property (nonatomic, retain) NSMutableDictionary *collectionDict;
#end
Here is the .m file.
#implementation PMXMLParser
#synthesize xmlEntity, collectionDict
- (void) dealloc
{
// this builds correctly, with no issues.
[collectionDict release];
// 1. This works
//self.xmlEntity = nil;
// 2. This causes the build error: 'xmlEntity' is not an Objective-C class name or alias
//[xmlEntity release];
[super dealloc];
}
#end
Now to me these example 1 and 2 do the same thing, just with a little bit more work is done for number 1.
Does anyone know why I am getting this build error for number 2?
Edit: 07/30/2010 - The code presented here will compile correctly, this is just a shorten version of my whole class. But my current class does not compile. I will post the whole class later when I taken out private code.
Thanks.
Is that really your implementation? You need to have a separate #implementation section in a ".m" source file, and you need to #synthesize those properties before you can use them. Also, your dealloc function needs to go in the #implementation section. But, those issues aside, if you can use the expression self.xmlEntity but not xmlEntity it means there is a scoping issue (probably because you aren't in the #implementation section) which can be fixed by using:
[self.xmlEntity release];
That said, I would address the aforementioned issues properly.
I found out that the program I was working on included the framework libxml2.dylib. Which had an structure called xmlEntity. What lead me to the problem was the syntax color for the item xmlEntity, which was a light purple color.