Instance variable does not retain its value - objective-c

I'm learning Objective-C right now and in order to practice I wrote a simple random maze generator for OS X, which works fine. Next I tried to add some more interaction with buttons, but I'm having trouble with the instance variables as they don't retain the value I assign them. I have come across multiple questions about the same problem, but the solutions to those haven't solved my problem. I also tested if the same problem persists in a simplified version of the program, which it does.
I guess I'm doing something wrong, but I don't know what. Here's what I did:
Created a new project
Added a subclass of NSView called "TestClass"
Added a view with class TestClass in the window in MainMenu.xib
Added an object for TestClass in MainMenu.xib
Added a button to the view and set its tag to 1
Added the following code to TestClass.h and TestClass.m and connected the button to it:
TestClass.h:
#import
#interface TestClass : NSView
{
NSNumber *number;
NSButton *test;
}
#property (nonatomic, retain) NSNumber *number;
#property (assign) IBOutlet NSButton *test;
- (IBAction)testing:(id)sender;
#end
TestClass.m:
#import "TestClass.h"
#implementation TestClass
#synthesize number;
#synthesize test;
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (IBAction)testing:(id)sender
{
self.number = [[NSNumber numberWithLong:[sender tag]] retain];
}
- (void) drawRect:(NSRect)dirtyRect
{
NSLog(#"%#", number);
}
#end
Whenever I press the button, NSLog just returns null several times.
I normally figure out everything by myself (eventually...), but this time it's really driving me insane, so is there anyone who can help me?

Put the NSLog in testing:, or just put a breakpoint there and see what's stored in number.
Note that self.number = [[NSNumber numberWithLong:[sender tag]] retain]; is double-retaining the NSNumber object (which is wrong), but that shouldn't cause any immediate error.

Related

Cannot access public variable of another class

I know this question is asked often, I've read so much on it but I still cant get it to work. Lets say I have two classes, FirstClass and SecondClass. FirstClass has a label and SecondClass wants to get the text of that label. Here is what I've done:
//FirstClass
#interface FirstClass : UIViewController
{
#public
UILabel *theLabel;
}
#property (strong, nonatomic) UILabel *theLabel;
#implementation FirstClass
#synthesize theLabel;
//SecondClass
#import "MainGameDisplay.h"
#interface SecondClass : UIViewController
{
MainGameDisplay *mainGame;
}
#property (strong, nonatomic) UILabel *theSecondLabel;
#implementation SecondClass
-(void) thisMethodIsCalled {
mainGame = [[FirstClass alloc] init];
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text); //Output is '(Null)'
}
theLabel.Text is not nil as it's being changed every second and is also displaying the label on the other controller which is running in the background whilst the SecondClass view is loaded. Could someone please point me to the write direction if I'm completely wrong, or show me some kind of example as to how this would be done. Thank you.
EDIT:
#Implementation FirstClass
#synthesize theLabel;
- (void)viewDidLoad {
[self superview];
[self startTickCount];
}
-(void) startTickCount {
timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(timeChanger) userInfo:nil repeats:YES];
}
-(void) timeChanger {
theDay++;
NSLog(#"%#",self.theLabel.text);
if (theDay <= 9)
self.theLabel.text = [NSString stringWithFormat: #"0%i", theDay];
else
self.theLabel.text = [NSString stringWithFormat: #"%i", theDay];
if (theDay > 27)
[self monthChanger];
}
That's pretty much it. The NSLog outputs the day as expected.
I assume MainGameDisplay is your FirstClass. Then in order to update theSecondLabel.text in you SecondClass object you need to pass an object of FirstClass and not to instantiate it in method call.
I guess you need to do something like this (this is a very simple example)
Add a property to your SecondClass
#property (nonatomic, strong) FirstClass *firstClass;
After that:
1) create instance of FirstClass, let it have name firstClass.
2) create instance of SecondClass. SecondClass *secondClass = [[SecondClass alloc] init];
3) set property of Second class to instance of FirstClass
secondClass.firstClass = firstClass;
4) now you have a reference to actual object of FirstClass and can access its properties.
-(void) thisMethodIsCalled {
self.theSecondLabel.text = self.firstClasss.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text);
}
I hope this will help.
if you didnt leave out LOTS of code,
-(void) thisMethodIsCalled {
mainGame = [[MainGameDisplay alloc] init];
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text); //Output is '(Null)'
}
will not work.. nobody can modify mainGame in between the alloc init and the getting of .text variable....
[#all I know this is not an answer, but the formatting of comments sucks. Ill edit or delete it as needed]
If this is your code exactly, you have two problems. First, Text is unnecessarily capitalized. And, secondly, TheLabel is unnecessarily capitalized.
Edited Code:
-(void) thisMethodIsCalled {
mainGame = [[MainGameDisplay alloc] init];
// 'text' shouldn't be capitalized
// 'theLabel' shouldn't be capitalized
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text);
}
The method name is text, not Text. The case matters, and using text with a lower T will cause errors.

Objective-c: Singleton - passing variables

I have a singleton that I'd like to use to manage the onscreen animation of my views. Here's my.
#import <Foundation/Foundation.h>
#interface OAI_AnimationManager : NSObject {
NSMutableDictionary* sectionData;
}
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
.m file
#import "OAI_AnimationManager.h"
#implementation OAI_AnimationManager
#synthesize sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
static OAI_AnimationManager* sharedAnimationManager;
#synchronized(self) {
if (!sharedAnimationManager)
sharedAnimationManager = [[OAI_AnimationManager alloc] init];
return sharedAnimationManager;
}
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", sectionData);
}
#end
You'll see in the .h file I added a NSMutableDictionary and am using #property/#synthesize for it's getter and setter.
In my ViewController I instantiate the animation manager as well as a series of subclasses of UIView called Section. With each one I store the data (x/y w/h, title, etc.) in a dictionary and pass that to the dictionary delcared in animation manager. In the Section class I also instantiate animation manager and add a UITapGestureRecognizer which calls a method, which passes along which section was tapped to a method (checkToggleStatus) in animation manager.
As you can I see in the method I am just logging sectionData. Problem is I am getting null for the value.
Maybe my understanding of singletons is wrong. My assumption was the class would only be instantiated once, if it was already instantiated then that existing object would be returned.
I do need all the other Section classes data as if one animates others animate in response and I can get around it by passing the tapped Section to the animation manager and doing [[Section superview] subviews] and then looping and getting the data from each that way but it seems redundant since that data is available in the ViewController when they are created.
Am I doing something wrong in trying to transfer that data? Is there a better solution? I am open to suggestions and criticisms.
Thanks
h file
#interface OAI_AnimationManager : NSObject
#property (nonatomic, retain) NSMutableDictionary* sectionData;
+(OAI_AnimationManager* )sharedAnimationManager;
- (void) checkToggleStatus : (UIView* ) thisSection;
#end
m file
static OAI_AnimationManager* _sharedAnimationManager;
#implementation OAI_AnimationManager
#synthesize sectionData = _sectionData;
+(OAI_AnimationManager *)sharedAnimationManager {
#synchronized(self) {
if (!_sharedAnimationManager) {
_sharedAnimationManager = [[OAI_AnimationManager alloc] init];
}
}
return _sharedAnimationManager;
}
- (void) checkToggleStatus : (UIView* ) thisSection {
//get the section data dictionary
NSLog(#"%#", _sectionData);
}
#end
Notice I moved your sectionData variable from the header and moved it to the implementation file. A while back, they changed it to where you can synthesize properties and specify their instance variable names along side it... hence:
sectionData = _sectionData;
I also added and underscore to the instance variable... this is a universal convention for private variables and it also will throw a compile error now if you try to type just sectionData as you did in the return statement of checkToggleStatus:. Now you either have to type self.sectionData or _sectionData.
You didn't include the code that creates an instance of your dictionary but I bet you didn't set it as self.sectionData = [[NSDictionary alloc] init] which means it would not retain the value and you would get null the next time you called it. Classic memory management mistake... I know it well because I learned the hard way hehehe

Can't get an objective-c object to respond to a message. What am I missing?

I'm trying to teach myself Objective-C and as an exercise, I'm trying to write an app with one button and one label. When I click on the button, I want to trigger a calculation then see the results in the label. The following code compiles and runs with no errors or warnings but as far as I can tell, the [object method] 'call' doesn't do anything. I've spent hours on this and just don't see what's wrong. Can anyone explain the problem? Thanks.
*** testMethodViewController.h ****
#import <UIKit/UIKit.h>
#import "testBrain.h"
#interface testMethodViewController : UIViewController
{
IBOutlet UILabel *display;
testBrain *model;
}
- (IBAction)cellPressed:(UIButton *)sender;
#end
*** testMethodViewController.m ****
#import "testMethodViewController.h"
#implementation testMethodViewController
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
- (IBAction)cellPressed:(UIButton *)sender
{
int x = [model check:3]; //This method call doesn't work. But gets no errors.
NSLog(#"Results from model: %i", x); //Says x = 0, but I expect 6
NSString *xAsString = [NSString stringWithFormat: #"testBrain: %i", x];
display.text = xAsString; //Label is updated and displays: testBrain: 0
} //I expect: testBrain: 6
#end
*** testBrain.h ****
#import <Foundation/Foundation.h>
#interface testBrain : NSObject {}
- (int) check:(int) anInteger;
#end
*** testBrain.m ****
#import "testBrain.h"
#implementation testBrain
- (int) check:(int) anInteger //3 passed as the parameter.
{
int r = anInteger + anInteger;
NSLog(#"inside check %i", r); //Debugging line: doesn't print.
return r;
}
#end
When this code runs:
int x = [model check:3];
model is nil. In Objective-C, messages sent to nil silently do nothing, and return 0. So, as you see, x is 0 and -check: is never called.
Apparently you were expecting this method to be called automatically:
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
However, that method will be called only if you do it yourself, by saying [self model] or self.model. So, this line would fix it:
int x = [[self model] check:3];
Try it and see.
Going a little further: It would be clearer to remove the model method entirely, and create the instance variable model when the UIViewController is created. That way, we can guarantee that model is valid anytime any code in the testMethodViewController class runs.
You would do that by overriding UIViewController's designated initializer:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Now you can initialize your instance variables
model = [[testBrain alloc] init];
}
return self;
}
With your model method, you are halfway towards Lazy Instantiation, however to properly achieve this, you must always acess the lazily instantiated object through its accessor method. You aren't doing this in your button action, so your messages are going to nil, which is silently ignored.
This is one of the reasons you often see instance variables in objective-c declared with a leading or trailing underscore. If you then typed model anywhere in the rest of your class, it would be a compiler error, forcing you to use the accessor. Typically this is implemented with properties and the synthesize statement:
In your interface:
#property (nonatomic, strong) TestBrain* model;
In your implementation:
#synthesize model = model_;
Your model method would be:
-(TestBrain*)model
{
if (!model_)
model_ = [[TestBrain alloc] init];
return model_;
}
You would then use self.model instead of model throughout the rest of the class.
If you are just starting out, the Stanford iOS course on iTunes U is an excellent resource, a lot of this sort of material is covered.
int x = [model check:3];
This line should be:
int x = [self.model check:3];
you are almost there. You need to use #property and #synthesize in order to complete this. The #synthesize directive will direct the compiler to create the setters and getters for a particular property. The #synthesize directive tells the compiler that variable is a property. Properties allow you to use the dot syntax. i.e. self.model which will automatically the call the getter or setter method, depending on the context.
In your testMethodViewController.h file change it to look like this:
#interface testMethodViewController : UIViewController
{
IBOutlet UILabel *display;
testBrain *model;
}
#property (nonatomic,retain) testBrain *model;
- (IBAction)cellPressed:(UIButton *)sender;
#end
then in the .m implementation you need to use #synthesize after the #implementation. Like this:
#implementation testMethodViewController
#synthesize model; // tells the compiler to synthesize the setter and getter for you
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
then in your cellPressed: method, you need to use self.model in order for the getter to be called:
- (IBAction)cellPressed:(UIButton *)sender
{
int x = [self.model check:3]; //This method call doesn't work. But gets no errors.
NSLog(#"Results from model: %i", x); //Says x = 0, but I expect 6
NSString *xAsString = [NSString stringWithFormat: #"testBrain: %i", x];
display.text = xAsString; //Label is updated and displays: testBrain: 0
}
Hope this helps.
I dont see anywhere in the testMethodViewController.h file
IBOutlet UIButton *button;
Also check if u have properly connected all IBOutlet, IBAction & delegate, datasource.

Strange ARC issue not releasing ivar in UIView subclass [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why is object not dealloc'ed when using ARC + NSZombieEnabled
I've got a very strange issue I'm seeing at the moment in a project. Put simply I have ViewA which owns ViewB (strong property). ViewA creates its ViewB in its initialiser. Both objects are subclasses of UIView.
I have overridden dealloc in both and put a log line and a break point to see if they get hit. It seems that ViewA's dealloc is being hit but not ViewB's. However if I put in a self.viewB = nil in the dealloc of ViewA then it is hit.
So basically it's something like this:
#interface ViewA : UIView
#property (nonatomic, strong) ViewB *viewB;
#end
#implementation ViewA
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.viewB = [[ViewB alloc] initWithFrame:self.bounds];
[self addSubview:self.viewB];
}
return self;
}
- (void)dealloc {
//self.viewB = nil; ///< Toggling this commented/uncommented changes if ViewB's dealloc gets called.
NSLog(#"ViewA dealloc");
}
#end
What I can't understand is why nil-ing viewB out makes a difference. If something else is holding onto viewB then it should make absolutely no difference if I nil it out or not here. And it shouldn't make a difference to the number of releases that ARC adds in either.
I can't seem to reproduce it in a minimal test case as yet, but I'm working on it. And I can't post the actual code I'm seeing this in unfortunately. I don't see that being an issue though because it's more the point that nil-ing it out shouldn't make a difference that I am confused by.
Can anyone see anything I am overlooking or give advice about where to look for debugging this problem?
Update:
I've found the problem. It appears that it's only a problem when NSZombieEnabled is set to YES. Well that is entirely mad and has to be a bug surely. Zombies should not affect how this works as far as I know. The objects should still go through the dealloc method. And what's more, it's just mad that it works if I nil out viewB in ViewA's dealloc.
I've found that this appears to be a bug in the iOS implementation of zombies. Consider the following code:
#import <Foundation/Foundation.h>
#interface ClassB : NSObject
#end
#implementation ClassB
- (id)init {
if ((self = [super init])) {
}
return self;
}
- (void)dealloc {
NSLog(#"ClassB dealloc");
}
#end
#interface ClassA : NSObject
#property (nonatomic, strong) ClassB *b;
#end
#implementation ClassA
#synthesize b;
- (id)init {
if ((self = [super init])) {
b = [[ClassB alloc] init];
}
return self;
}
- (void)dealloc {
NSLog(#"ClassA dealloc");
}
#end
int main() {
ClassA *a = [[ClassA alloc] init];
return 0;
}
That should output:
ClassA dealloc
ClassB dealloc
But with NSZombieEnabled set to YES, it outputs:
ClassA dealloc
As far as I can tell, this is a bug. It seems to only happen with iOS (both simulator and device) and does not happen when built and run for Mac OS X. I've filed a radar with Apple.
Edit: It turns out this has already been answered here - Why is object not dealloc'ed when using ARC + NSZombieEnabled . Managed to find it after I found out what the real problem was. It's nothing to do with ARC by the way.

Variables in separate class coming back null

Ok, I think the question I had here was long-winded and difficult to get through. I'll simplify my question:
I have a class called InController.
InController has a method called nextPage that tells an int variable, inPageNumber, to add one onto itself and to call on another InController method called updateTable.
updateTable clears a table, inTable, of its current data and fills it with data relevant to the page number it retrieves from inPageNumber.
The table, inTable, is contained inside an NSBox with specific printing requirements.
I subclassed NSBox into a class called CustomViewPagination to meet these printing requirements, overriding its paginations methods. Basically, when a new printing page is required, it attempts to print the same area again, but calls on nextPage to fill the table with the data of the sequential page.
With me so far?
One of the pagination methods I overrided in CustomViewPagination, beginPageInRect, is automatically called for each printed page by default. Because of this, I placed a call to my InController method of nextPage, to change the inTable data for the current printing page.
My problem is when I call nextPage (which is a method in InController) from my CustomViewPagination class. It does nothing and when I debug it I find that all the variables required in the method are nil. However, they are the correct values when I call nextPage from inside InController.
File Extracts:
InController.h:
#import <Cocoa/Cocoa.h>
#import "CustomViewPagination.h"
#interface InController : NSObject {
IBOutlet NSWindow *inPreview;
IBOutlet CustomViewPagination *inSheet;
NSArray *iSelectedIn;
NSMutableArray *records;
int inPageNumber;
}
#property (nonatomic, retain) NSArray *iSelectedIn;
#property (nonatomic, retain) NSMutableArray *records;
InController.m:
#import "InController.h"
#implementation InController
#synthesize iSelectedIn, records;
- (IBAction) inNextPage:(id)sender {
inPageNumber = inPageNumber + 1;
NSLog(#"inPageNumber called ok");
[self updateIn];
}
- (IBAction)updateInvoice:(id)sender {
//wipe all current records and refresh empty table
[records removeAllObjects];
[inPreviewTable reloadData];
for (NSArray *s in [[iSelectedIn valueForKey:#"inJobList"] lastObject]) {
NSString *jLT = [s valueForKey:#"inJT"];
NSString *jLH = [s valueForKey:#"inJHo"];
NSString *jLC = [s valueForKey:#"inJC"];
// etc.
// if CustomViewPagination called this, records is nil, so nothing
// is cleared, and there's no *s for iSelectedIn as iSelectedIn
// is found to be nil. If InController called this, it works fine.
CustomViewPagination.h:
#import <Cocoa/Cocoa.h>
#class InController;
#interface CustomViewPagination : NSBox {
InController *inControllerInstance;
}
#end
CustomViewPagination.m:
#import "CustomViewPagination.h"
#import "InController.h"
#implementation CustomViewPagination
- (void) awakeFromNib {
inControllerInstance = [[InController alloc] init];
}
- (void)beginPageInRect:(NSRect)aRect atPlacement:(NSPoint)location {
int pageCounter = [[NSPrintOperation currentOperation] currentPage];
if (pageCounter == 1) {
// Don't respond to 1st page, do nothing.
} else {
[inControllerInstance inNextPage:self];
}
[super beginPageInRect:aRect atPlacement:location];
}
#end
You are using 2 IBOutlets in InController (inPreview & inSheet), but InController is created programmatically in CustomViewPagination's awakeFromNib.
How are the Outlets connected? (Can't be from within IB, as you are creating the InController instance programmatically). This would be an explanation why both are nil.