I am writing an app for iphone (iOS 7, Xcode5), and found some strange behaviour from one of the viewController classes (TrackDetailsViewController):
When this view is getting called i pass a Track item with some information i deed through the prepareForSegue method. Now When i check for the Track Item in the viewDidLoad method it exists and has all the data i expect it to have. However, when I check it again in the viewDidAppear the Track Item has become zero (see code below):
TrackDetailsViewController.h:
#interface TrackDetailsViewController : UIViewController <UIScrollViewDelegate>
#property (weak, nonatomic) Track *track;
#end
TrackDetailsViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
//check for track
if (_track !=nil) {
NSLog(#"track not nil");
}
else{
NSLog(#"no track");
}
// Do any additional setup after loading the view.
}
-(void) viewDidAppear: (BOOL) animated{
//check for track
if (track != nil) {
NSLog(#"track not nil");
}
else{
NSLog(#"no track");
}
}
Now the log after running this code (after showing the view in question) looks like:
2013-09-28 16:25:31.877 I-Sail[1388:c07] track not nil
2013-09-28 16:25:32.235 I-Sail[1388:c07] no track
So, somewhere in-between viewDidLoad and viewDidAppear, the value of the track property was changed to null.
After that the whole thing becomes even weirder to me, since the track property keeps its value when i add a variable track2 to the .m file, and assign the value of the track property to the track2 variable like shown in the code below:
Track *track2;
- (void)viewDidLoad
{
[super viewDidLoad];
//check for track
if (_track !=nil) {
NSLog(#"track not nil");
}
else{
NSLog(#"no track");
}
//assign the value of _track to the track2 variable
track2 = _track;
}
-(void) viewDidAppear: (BOOL) animated{
//check for track
if (_track !=nil) {
NSLog(#"track not nil");
}
else{
NSLog(#"no track");
}
}
This time the output becomes:
2013-09-28 16:35:57.929 I-Sail[1452:c07] track not nil
2013-09-28 16:35:58.286 I-Sail[1452:c07] track not nil
So the property track keeps its value and i can use it in other methods of the viewController class as well.
Has anybody experienced issues like this before, some explanation for this behaviour? or found a more elegant workaround than creating a dummy variable?
Cheers!
This is because your track property is specified as weak which makes it a zeroing weak reference. Presumably there are no other strong references to track. Adding the track2 variable creates a strong reference to it, which makes it stick around. You probably don't want this property to be weak.
Related
I've got a BOOL that seems to change value without being assigned too! However, it only happens when switching between view controllers. Basically I have a TableViewController going to a DetailViewController, the BOOL is on the DetailViewController. It is used to determine whether a timer is currently running. When just staying on the DetailViewController the BOOL works as expected, however when switching between DetailViewController and TableViewController, that's when the problem occurs. In no place on TableViewController is it being assigned to.
I have NSLogged all over the place, I've used lots of breakpoints and also watchpoints on the BOOL itself, however I still can't figure out what's making it change!
Okay, on the viewDidLoad of DetailViewController, the BOOL is updated from a Core Data entity.
- (void)viewDidLoad {
[super viewDidLoad];
// MORE CODE
_running = [[_timerObject valueForKey:#"running"] boolValue];
****_running is NO on initial load**** - expected
****_running is YES when returning to DetailViewController**** - expected
// MORE CODE
}
I then have a button to start/cancel the timer and use the BOOL to toggle the button.
- (IBAction)start:(id)sender {
****_running is NO on first press**** - expected
****_running is YES on second press**** - NOT EXPECTED!?!?!?!
// if timer is running
if (_running) {
// CANCEL TIMER CODE
}
else {
// START TIMER CODE
}
// Update BOOL
_running = !_running;
****_running is YES**** - expected
}
When the timer completes, this method is called.
- (void)timerComplete {
****_running is YES**** - expected
// OTHER CODE HERE
// Cancel timer
[_appDelegate.timerManager stopTimer:_timerID];
// Update BOOL
_running = NO;
****_running is NO**** - expected
// Save to ensure booleans are correct
[self save];
****_running is NO**** - expected
}
My save method looks like this. It is called at the end of timerComplete and also in the viewDidDisappear method.
- (void)save {
// Create variable of managed object context AppDelegate
NSManagedObjectContext *context = [self managedObjectContext];
if (_timerObject) {
// Update existing timer
if ([_nameTextField.text isEqualToString:#""]) {
[_timerObject setValue:#"Timer" forKey:#"name"];
}
else {
[_timerObject setValue:_nameTextField.text forKey:#"name"];
}
[_timerObject setValue:_date forKey:#"timeStart"];
[_timerObject setValue:[NSNumber numberWithBool:_running] forKey:#"running"];
[_timerObject setValue:[NSNumber numberWithBool:_paused] forKey:#"paused"];
//----------------
// Update title of view
self.title = [_timerObject valueForKey:#"name"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
****_running is NO**** - expected
}
The behaviour required to break it is:
- Load DetailViewContoller
- Press Start Timer
- Go back to TableViewController
- Go to DetailViewController
- Wait for Timer to Complete
- Press Start Timer again and nothing happens because _running is YES, not NO!
I've annotated the code segments with the values of "_running" in various different places.
As far as I can tell, nothing happens between timerComplete and pressing start again so I don't know why the BOOL changes from NO to YES in that time! Any help would be greatly appreciated! :)
Just an extra note
NSLog(#"timerObject: %#", [_timerObject valueForKey:#"running"]);
outputs a 0 when placed in the same placed as where _running returns YES in the start timer method! So the core data value appears to be correct, just not _running!
I am new to objective-c and trying to understand better why the following is occurring... in my view controller, I have this in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self createProjectData];
}
And then:
- (void)createProjectData
{
if(!self.projectData) {
self.projectData = [[NSMutableArray alloc] initWithObjects:tempProjectInfo1, tempProjectInfo2, nil];
}
projectData is a public property:
#property (nonatomic, strong) NSMutableArray *projectData;
My problem is that when I navigate to a different View Controller and return to this one, projectData is null even though I had initialized previously with the above values... so I'm really hoping someone can explain how I can retain the property value so when I return it has all of the items that I had added to the mutable array.
Check if somewhere inside viewDidDisappear your code is niling your array.
You may also have code for dealing with memory warnings doing something similar
I am working on an app and I got stuck at the point where I can't seem to retrieve the value of a BOOL set in a class.
I spent too much time already on it, been through all the questions I found that seem to cover the matter.
The bad thing here is that I get something, but not what I need (I get a 0, which means, I guess, that the value wasn't retrieved correctly as it should be 1).
The things I tried are :
pass a pointer to my first class and access to my BOOL like this:
//in some method
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
by declaring it (talking of the pointer) as a property in my second class (and importing the h. file from my first class, where my BOOL is declared as property too):
#property FirstClass *pointerFirstClass;
But I got 0 using this.
The other shot I gave was add my BOOL in the first class and create an instance of the class in my second class
//in some method
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
But I got 0 too.
As Booleans are primitive types, like in C, I get a bit confused since I am new to object-oriented programming, I don't know how I could like create a getter for this, for example.
I also tried to do a - (BOOL)getBOOLValue method in my first class, and call this method in my second class and assign it to a BOOL in that second class.
But the result wasn't better.
Am I missing something?
Is there a way to get my value that I didn't think of or didn't know about yet?
I am running low on thoughts on how to get around this, it shouldn't be that hard IMO so I hope it is something simple that I just left aside.
EDIT :
Some actual code. I am working between 2 files called AppDelegate (yes, the actual one) and WelcomeViewController (so a VC).
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
BOOL inRegion; //thought of this making my BOOL as a property of AppDelegate
}
#property (strong, nonatomic) UIWindow *window;
#property BOOL inRegion; //Declaring my BOOL here to make it accessible for another class
- (BOOL)getBOOLValue; //An attempt to pass my BOOL value
AppDelegate.m
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
self.inRegion = YES; //Set my BOOL to TRUE
}
else if (state == CLRegionStateOutside)
{
self.inRegion = NO; //Else set it to False
}
- (BOOL)getBOOLValue
{
return inRegion; //Tried to create a custome "getter"
}
WelcomeViewControler.m (I changed nothing in the .h file)
I said I tried many things, right now, this is the last version of my code.
//Simply trying to do a Segue on a condition...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
As said, I want to retrieve the BOOL value of the AppDelegate.
Thank you.
This code doesn't make sense:
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
The first line doesn't do anything. You're not assigning anything to the property, and you're not doing anything with the value. Furthermore, the second line doesn't relate to the first line in any way that we can see from the code you've provided. Try this instead:
self.pointerFirstClass = [[FirstClass alloc] init];
self.pointerFirstClass.myBOOL = YES;
NSLog(#"myBOOL = %d", self.pointerFirstClass.myBOOL);
In other words, you need to be sure that self.pointerFirstClass points to a valid object. And then you need to make sure that you've assigned the value you want to the myBOOL property of that object.
Update: This looks like a case where you're talking to the wrong object. Look at this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
This is surely not what you really want. The application object is a single object -- a real singleton, in fact, meaning that there is and can be only one application object. That object has a delegate object, and that's a specific instance of your AppDelegate class. In this code, though, you're creating a new instance of AppDelegate, one that's different from the one that the application is using. Any changes that are made to the actual application delegate in response to messages from the application will not be reflected in the new object that you've created.
What I think you want is to get the actual application delegate object, and you can do that using:
[[UIApplication sharedApplication] delegate];
So, change your code to look like this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
// note: you don't need an else clause if it doesn't do anything
}
That way, you'll be talking to the same object that the app uses, which is the one that has the inRegion property set in response to the location manager call.
UPDATE - Now we can see your code the problem is obvious, you are trying to access the appDelegate by creating a new one...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
Instead you should be doing this....
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]
--
Not sure if you are posting your actual code? but the first example you give...
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
Shouldn't the second line be
NSLog(#"%d", self.pointerFirstClass.myBOOL);
Also this property...
#property FirstClass *pointerFirstClass;
Won't retain it once you've set it, it needs to be
#property (nonatomic,strong) FirstClass *pointerFirstClass;
In the second example...
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
You allocate and initialise a new FirstClass object and then check the property straight away, if you are not setting this to YES in the init then it will be false
Like I say, maybe you're not posting your actual code?
I guess what you want is initializing myBOOL to 1.
If so, you need do something as following
#implement FirstClass
- (id)init
{
self = [super init];
if(self) {
_myBOOL = 1;
}
return self;
}
// Other methods
#end
EDIT:
The comments is why you get 0.
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init]; // this is the problem.
// you create a new appdelegate,
// and never call locationManager:didDetermineState:forRegion:
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
rewrite your code as following:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
There's a little bit uncommon situation in my app, that is,
I have to reload some retain properties everytime when the view is going to appear,
the code looks like this:
// .h
#property (nonatomic, retain) NSArray *myData;
// .m
#synthesize myData;
- (void)viewWillAppear:(BOOL)animated {
... // get FetchRequest and so on
self.myData = [self.context executeFetchRequest:request error:&error]; // Line 1
[super viewWillAppear:animated];
}
- (void)viewDidUnload {
self.myData = nil;
[super viewDidUnload];
}
- (void)dealloc {
[myData release]; // Line 2
[super dealloc];
}
there are several points:
1st. as you see, the property "myData" is retain, so I think every I set some object for it, it would automatically retain that object?
2nd. I have to reload "myData" everytime the view will appear, just like the code of Line 1 above.
3rd. Since it is a retain property, I have to release it myself correctly.
Now, question is, do I correctly managed the memory without any leaking of "myData" using the codes above?
If the view would appear many times before it is dealloc, (like push in a further view in a UINavigationController and pop out for several times),
then myData would retain some object more than once, but I only release it in the dealloc for 1 once in Line 2, so is that ok?
But if I add this method the to viewController,which I think is more safe for avoiding memory leaks:
- (void)viewWillDisappear:(BOOL)animated {
self.myData = nil;
[myData release];
[super viewWillDisappear:animated];
}
- (void)dealloc {
// [myData release]; // don't release it here.
[super dealloc];
}
my app would crash after one or two times I push in and pop out the view,
So which one is really wrong?
Thanks a lot!
You are not only releasing it in Line 2, it will be also released in Line 1 when replaced as well as in viewDidUnload, so your code on top is just fine. The key is that
self.myData = anything;
is expanded to
[self->myData release];
self->myData = [anything retain];
so by assigning anything (including nil) you are already calling release implicitly. You could in fact replace Line 2 with self.myData = nil; to have never to call release since you don't have any explicit retain.
.h
#property (nonatomic, retain) NSArray *myData;
.m
#synthesize myData;
By including these lines in your code a setter and getter is created for your property myData. The setter generated at run time for objects looks something like this,
- (void)setMyData: (id)newValue
{
if (myData != newValue)
{
[myData release];
myData = newValue;
[myData retain];
}
}
The total effect is that whenever you access the property by appending self in front you are actually calling the setters and getters. So the following two lines are the exact same.
self.myData = nil;
[self setMyData:nil];
So your original code was already correct.
I'm writing a game where a MainGameplay class keeps track of whose turn it is by setting an ivar to an enum value:
typedef enum {
GAME_NOT_STARTED,
PLAYER_1_TO_MOVE,
PLAYER_2_TO_MOVE
} WhoseTurnIsIt;
Then the GameBoard class checks whether a move attempt is valid, and calls either turnEnded:(WhoseTurnIsIt)turn or reportTurnFailure:(WhoseTurnIsIt)turn in MainGameplay.m.
I get EXC_BAD_ACCESS as soon as I try to access this returned value back in MainGameplay, in the receiving methods. Seems like I should retain something, but you can't retain an enum. In the debugger the values are there, so I don't understand what's being accessed improperly.
This is the code doing the calling in GameBoard:
-(void)buttonPressed:(id)sender {
CCArray *kids = [[[CCDirector sharedDirector] runningScene] children];
if (!mainScene) { // mainScene is an ivar on each the GameBoard's buttons.
for (CCScene *s in kids) {
// this looks crazy because the "main" scene is actually a controlling layer that has as a child the main gameplay layer:
if ([s isKindOfClass:[ControlLayer class]]) {
self->mainScene = (MainGameplay *)((ControlLayer *) s).gameLayer;
}
}
}
if (MOVE_NO_ERROR == [self checkMove:mainScene.turn]) {
[self setMove:mainScene.turn];
[mainScene turnEnded:mainScene.turn]; // This line and the next are the ones causing the EXC_BAD_ACCESS
} else [mainScene reportTurnFailure:mainScene.turn]; // This line too.
}
EDIT Functions in mainScene being called go like this:
-(void) reportTurnFailure:(WhoseTurnIsIt)_turn {
NSLog(#"MainScene still valid"); // This line works fine
NSLog(#"Bzzzzzt. Player %#, try again", _turn); // This line crashes BUT _turn shows up with a proper value in the debugger.
}
_turn is not an object, but the %# format specifier says that the argument is an object. Use %i instead.
-(void) reportTurnFailure:(WhoseTurnIsIt)_turn {
NSLog(#"MainScene still valid"); // This line works fine
NSLog(#"Bzzzzzt. Player %i, try again", _turn); // This line crashes BUT _turn shows up with a proper value in the debugger.
}