iOS Objective-C: Strange BOOL behaviour - ios

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!

Related

Perform call to server in viewDidLoad

I'm pretty new on iOS development and I'm having the following problem. I need to fill a UITableView with data fetched from a server.
I'm doing this call using AFNetworking. I want to show a "loading view" using SVProgressHUD and I'm doing the following:
-(void) viewDidLoad{
[super viewDidLoad];
[SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
[[MyServer sharedManager] fetchFromServerwithCompletionHandler:^(NSArray *elements, BOOL error) {
_elements = elements;
[_elementsTableView reloadData];
[SVProgressHUD dismiss];
}];
}
I'm getting the correct answer from the server but the progress hud is not being displayed. The call to the server does take some seconds so there is time for the progress hud view to be loaded but nothing happens.
I'm performing the call in the viewDidLoad because I need the call to be made as soon as the view is loaded and I want to call this just once.
If I move this block of code to the viewDidAppear the progress hud loads, but then this method is going to be called every-time the view is shown and I don't want this...
Thanks a lot!
The problem is not that the network call doesn't take place if done in viewDidLoad (because that is the right place to do it), but rather that the attempt to add the SVProgressHUD in viewDidLoad won't work, because this is too early in the process of constructing the view for SVProgressHUD to figure out where to put the HUD.
One simple solution would be to have viewDidLoad defer the invocation of this, to give the UI a chance to get to a state that SVProgressHUD can correctly place itself in the view hierarchy. As silly as it seems, you can just dispatch the code back to the main loop, which gives the UI a chance to catch up:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
[[MyServer sharedManager] fetchFromServerwithCompletionHandler:^(NSArray *elements, BOOL error) {
_elements = elements;
[_elementsTableView reloadData];
[SVProgressHUD dismiss];
}];
});
}
A trick can be use NSUserDefaults to save a value like #"serverAlreadyCalled" and check on viewDidAppear if you called the server before.
example:
-(viewDidAppear){
NSUserDefaults* defaults = [NSUserDefaults standardDefaults];
if ([defaults valueForKey:#"serverAlreadyCalled"] == nil) {
[SVProgressHUD showWithMaskType:SVProgressHUDMaskTypeGradient];
[[MyServer sharedManager] fetchFromServerwithCompletionHandler:^(NSArray *elements, BOOL error) {
_elements = elements;
[defaults setValue:#"YES" forKey:#"serverAlreadyCalled"];
[_elementsTableView reloadData];
[SVProgressHUD dismiss];
}];
}
}
Don't forget to set nil to your key #"serverAlreadyCalled" before your app finishes run.
Maybe it's not the best solution, but can make the trick.
Hope it helps.

A property of viewcontroller disappears after viewDidLoad has been called

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.

Getting current values in delegated class

I have done a delegate , so in the class delegated, which is a UIPopover, there is a method that received an object of my PreguntasViewController. If i print the variables , there is not anything. Why could it be? Thanks.
// -- PreguntasViewController
- (void)getRespuestasDelegate {
[delegate getRespuestas:self];
}
//Delegated class
- (void)viewDidLoad
{
[super viewDidLoad];
PreguntasViewController *preguntasViewCotroller = [[PreguntasViewController alloc] init];
preguntasViewCotroller.delegate = self;
[preguntasViewCotroller getRespuestasDelegate];
}
- (void)getRespuestas:(PreguntasViewController *)preguntasViewController{
for(NSString *respuesta in preguntasViewController.preguntasArray) {
NSLog(#"%#", respuesta);
}
NSLog(#"%d", [preguntasViewController.preguntasArray count]);
NSLog(#"%#", preguntasViewController.ayudapreguntas);
}
It looks to me from your example like you are allocating an instance of PreguntasViewController and then immediately calling its getRespuestasDelegate method. I don't see anywhere that preguntasArray gets populated. Is it populated as part of the PreguntasViewController's init method? If not, I wouldn't expect there to be anything in the array.

Confusing about memory management on IOS

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.

Returning an enum ivar causes EXC_BAD_ACCESS

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.
}

Resources