I am trying to make code more readable by rearranging related methods including NSTimer
which I had previously placed in a UIViewController
. I now need to relocate these to custom class so they can work independently of the ViewController
.
But in my attempts to do this, I have introduced a problem with NSTimer that wasn’t there previously even though the code looks correct. A crash happens with the following error log:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x1)
When I try using a breakpoint and stepping through the code Xcode seems to be stuck in a loop on this statement
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(nextClock)
userInfo:nil
repeats:YES];
Here is a skinny version of my code. NSTimer
is in a class called ConcertController
which has a forward declaration in PlayViewController.h
PlayViewController.h
#import <UIKit/UIKit.h>
#import "ConcertController.h"
@interface PlayViewController : UIViewController
{
ConcertController *concertStateMachine;
}
@end
and is called from viewDidLoad
in PlayViewController.m
PlayViewController.m
#import "PlayViewController.h"
@implementation PlayViewController {
}
@synthesize lastEventChangeTime;
...
...
- (void)viewDidLoad
{
[super viewDidLoad];
selectedFamily = [parent getSelectedFamily];
selectedPlayerID = [parent getSelectedPlayerID];
concertStateMachine = [[ConcertController alloc] initConcertStateMachine:(int)selectedFamily
forPlayer:(int)selectedPlayerID];
CGRect rect = [UIScreen mainScreen].bounds;
float statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height;
screenFrame = CGRectMake(0,statusBarHeight,rect.size.width,rect.size.height - statusBarHeight);
self.view = [[UIView alloc] initWithFrame: screenFrame];
}
ConcertController
has a forward declaration in PlayViewController.h
ConcertController.h
#import <UIKit/UIKit.h>
@class PlayViewController;
@interface ConcertController : NSObject
{
int currentState;
NSUInteger clockCount;
int totalMinutes;
int totalSeconds;
…
…
NSDate* lastEventChangeTime;
NSTimer* timer;
}
- (id)initConcertStateMachine:(int)selectedFamily
forPlayer:(int)selectedPlayerID;
@property (nonatomic, retain) NSDate *lastEventChangeTime;
@end
ConcertController.m
#import "PlayViewController.h"
@implementation ConcertController
@synthesize lastEventChangeTime;
- (id)initConcertStateMachine:(int)selectedFamily
forPlayer:(int)selectedPlayerID
{
[self findEntryPointsFor:(int)selectedFamily
andPlayer:(int)selectedPlayerID];
[self startClock];
return self;
}
- (void)startClock
{
lastEventChangeTime = [[NSDate alloc] init];
currentState = 0; // CLOCK_Init_CurrentState;
clockCount = 24; // number of seconds per state
totalMinutes = 0 // CLOCK_Init_totalMinutes;
totalSeconds = 0; // CLOCK_Init_totalSeconds;
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(nextClock)
userInfo:nil
repeats:YES];
}
- (void)nextClock
{
lastEventChangeTime = [NSDate date];
clockCount++;
[self masterClockReadout];
if ((clockCount % (int)24) == 0)
{
// [self nextState]; // other (i.e. non-UIView) code goes here
}
}
EDIT #2
initialisation in ConcertController.m has been standardised as recommended.
- (id)initConcertStateMachine:(int)selectedFamily
forPlayer:(int)selectedPlayerID
{
self = [super init];
if (self) {
[self findEntryPointsFor:(int)selectedFamily
andPlayer:(int)selectedPlayerID];
[self startClock];
}
return self;
}
EDIT #1. Here is a log as requested. Note: log will also show some items not found in the posted code examples (removed for the sake of clarity). Greg
2017-07-25 17:06:30.809 SatGam2[5476:1809452] FamilySelectViewController loaded
2017-07-25 17:06:34.361 SatGam2[5476:1809452] PlayerIDSelectViewController loaded
2017-07-25 17:06:36.250 SatGam2[5476:1809452] SyncViewController loaded (Family 1 PlayerID 1)
2017-07-25 17:06:38.376 SatGam2[5476:1809452] Initialising MotionListener
2017-07-25 17:06:38.674444+1000 SatGam2[5476:1811506] [aqme] 254: AQDefaultDevice (1): skipping input stream 0 0 0x0
2017-07-25 17:06:38.692 SatGam2[5476:1809452] PlayViewController init called and AudioSession active
2017-07-25 17:06:38.693 SatGam2[5476:1809452] MIDI Event [tuningTransposition: 1 assignedPitches: 1]
2017-07-25 17:06:38.694 SatGam2[5476:1809452] Selected octave is 2
2017-07-25 17:06:38.694 SatGam2[5476:1809452] Dekany : index MIDI Note Number Frequency
2017-07-25 17:06:38.694 SatGam2[5476:1809452] 52 63 662.2421
2017-07-25 17:06:38.695 SatGam2[5476:1809452] 53 64 708.2311
2017-07-25 17:06:38.695 SatGam2[5476:1809452] 54 65 772.6157
2017-07-25 17:06:38.695 SatGam2[5476:1809452] 55 66 809.407
2017-07-25 17:06:38.695 SatGam2[5476:1809452] 56 67 882.9894
2017-07-25 17:06:38.695 SatGam2[5476:1809452] 57 68 910.5828
2017-07-25 17:06:38.696 SatGam2[5476:1809452] 58 69 993.3631
2017-07-25 17:06:38.696 SatGam2[5476:1809452] 59 70 1030.154
2017-07-25 17:06:38.696 SatGam2[5476:1809452] 60 73 1158.924
2017-07-25 17:06:38.696 SatGam2[5476:1809452] 61 74 1214.11
2017-07-25 17:06:38.697 SatGam2[5476:1809452]
(
"662.2421",
"708.2311",
"772.6157",
"809.407",
"882.9894",
"910.5828",
"993.3631",
"1030.154",
"1158.924",
"1214.11"
)
2017-07-25 17:06:38.697 SatGam2[5476:1809452] concert sequence for selectedFamily 1 and selectedPlayerID 1
2017-07-25 17:06:38.697 SatGam2[5476:1809452] entryPoints
2017-07-25 17:06:38.697 SatGam2[5476:1809452] 1 1 0 0
2017-07-25 17:06:38.698 SatGam2[5476:1809452] 2 1 0 0
2017-07-25 17:06:38.698 SatGam2[5476:1809452] 3 1 0 0
2017-07-25 17:06:38.698 SatGam2[5476:1809452] 4 1 0 0
2017-07-25 17:06:38.698 SatGam2[5476:1809452] 5 0 -1 48
2017-07-25 17:06:38.698 SatGam2[5476:1809452] 6 0 0 24
2017-07-25 17:06:38.699 SatGam2[5476:1809452] 7 1 1 0
2017-07-25 17:06:38.699 SatGam2[5476:1809452] 8 1 0 0
2017-07-25 17:06:38.699 SatGam2[5476:1809452] 9 0 -1 48
2017-07-25 17:06:38.699 SatGam2[5476:1809452] 10 0 0 24
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 11 1 1 0
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 12 1 0 0
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 13 1 0 0
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 14 0 -1 96
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 15 0 0 72
2017-07-25 17:06:38.700 SatGam2[5476:1809452] 16 0 0 48
2017-07-25 17:06:38.701 SatGam2[5476:1809452] 17 0 0 24
2017-07-25 17:06:38.701 SatGam2[5476:1809452] 18 1 1 0
2017-07-25 17:06:38.701 SatGam2[5476:1809452] 19 1 0 0
2017-07-25 17:06:38.701 SatGam2[5476:1809452] 20 1 0 0
2017-07-25 17:06:38.701 SatGam2[5476:1809452] 21 1 0 0
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 22 0 -1 144
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 23 0 0 120
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 24 0 0 96
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 25 0 0 72
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 26 0 0 48
2017-07-25 17:06:38.702 SatGam2[5476:1809452] 27 0 0 24
2017-07-25 17:06:38.703 SatGam2[5476:1809452] 28 1 1 0
2017-07-25 17:06:38.703 SatGam2[5476:1809452] 29 1 0 0
2017-07-25 17:06:38.703 SatGam2[5476:1809452] 30 0 -1 48
2017-07-25 17:06:38.703 SatGam2[5476:1809452] 31 0 0 24
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x1)
frame #0: 0x03144383 libobjc.A.dylib`objc_release + 19
* frame #1: 0x00081c1c SatGam2`-[ConcertController startClock](self=0x786bb480, _cmd="startClock") at ConcertController.m:46 [opt]
frame #2: 0x00081b87 SatGam2`-[ConcertController initConcertStateMachine:forPlayer:](self=0x786bb480, _cmd="initConcertStateMachine:forPlayer:", selectedFamily=1, selectedPlayerID=1) at ConcertController.m:25 [opt]
frame #3: 0x00093a16 SatGam2`-[PlayViewController viewDidLoad](self=0x7aa49c00, _cmd="viewDidLoad") at PlayViewController.m:154 [opt]
frame #4: 0x014e2878 UIKit`-[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 38
frame #5: 0x014e7201 UIKit`-[UIViewController loadViewIfRequired] + 1434
frame #6: 0x014e776c UIKit`-[UIViewController view] + 29
frame #7: 0x00085c29 SatGam2`-[MultiviewViewController displayView:](self=<unavailable>, _cmd="displayView:", intNewView=<unavailable>) at MultiviewViewController.m:45 [opt]
frame #8: 0x000825ed SatGam2`-[MultiviewAppDelegate displayView:](self=0x793a1360, _cmd="displayView:", intNewView=4) at MultiviewAppDelegate.m:17 [opt]
frame #9: 0x0008828e SatGam2`-[SyncViewController fromSyncButton:](self=<unavailable>, _cmd="fromSyncButton:", button=0x7b67deb0) at SyncViewController.m:65 [opt]
frame #10: 0x03146220 libobjc.A.dylib`-[NSObject performSelector:withObject:withObject:] + 63
frame #11: 0x0131fca0 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 91
frame #12: 0x0131fc3a UIKit`-[UIApplication sendAction:toTarget:fromSender:forEvent:] + 41
frame #13: 0x014c7f67 UIKit`-[UIControl sendAction:to:forEvent:] + 64
frame #14: 0x014c82d1 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 469
frame #15: 0x014c7207 UIKit`-[UIControl touchesEnded:withEvent:] + 666
frame #16: 0x01396526 UIKit`-[UIWindow _sendTouchesForEvent:] + 3066
frame #17: 0x01397dea UIKit`-[UIWindow sendEvent:] + 4445
frame #18: 0x0133e1b0 UIKit`-[UIApplication sendEvent:] + 363
frame #19: 0x01bbac2f UIKit`__dispatchPreprocessedEventFromEventQueue + 2973
frame #20: 0x01bb20ff UIKit`__handleEventQueue + 1255
frame #21: 0x01bb3663 UIKit`__handleHIDEventFetcherDrain + 66
frame #22: 0x0360aa5f CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
frame #23: 0x035f01c4 CoreFoundation`__CFRunLoopDoSources0 + 500
frame #24: 0x035ef69c CoreFoundation`__CFRunLoopRun + 1084
frame #25: 0x035eefd4 CoreFoundation`CFRunLoopRunSpecific + 372
frame #26: 0x035eee4b CoreFoundation`CFRunLoopRunInMode + 123
frame #27: 0x0516aa7a GraphicsServices`GSEventRunModal + 71
frame #28: 0x0516a95f GraphicsServices`GSEventRun + 80
frame #29: 0x0131dbc9 UIKit`UIApplicationMain + 148
frame #30: 0x00080f74 SatGam2`main(argc=1, argv=0xbff82818) at main.m:12 [opt]
frame #31: 0x05f0e779 libdyld.dylib`start + 1
(lldb)
I'm not entirely sure if that's it, but I think you should not call startClock
in your initializer. That init method is in general not following the correct structure of a decent initializer, i.e.
- (instancetype)init... {
self = [super init];
if (self) {
// initialize properties (and ivars in your case)
}
return self;
}
I think the problem is that you're scheduling the timer, which holds a reference to self
, i.e. the ConcertController
instance before the initializer is done, i.e. there isn't really a self
yet. Especially because you never called super
's initializer either (unless you do that in that findEntryPointsFor:andPlayer:
method, which totally voids any convention).
It probably works already if you just call startClock
later (for example from the view controller), but I would really recommend fixing your init
to suffice the conventions. Don't forget that especially under ARC that is more than just coding aesthetics, ARC also relies on certain things to properly deduce what to retain and release etc.
Besides that, the fact you're defining ivars directly is a bit fishy. I guess that comes from the migration from MRC? I'd recommend to use properties here as well (which is not a really performance loss, as many people seem to wrongly believe). The only thing to keep in mind then is that in the initializer, you access them with _ivarName
(i.e. underscore notation) and rely on the getters and setters elsewhere (i.e. usually dot syntax, except some edge cases where you need to avoid some key-value observing stuff, but as I see it from here you don't even have that). This is really the cleaner and safer way to go, especially for when you want to replace the timer and all that. If you're concerned about keeping the class's public interface clean, use a class extension, that's still better than ivars.