为什么NSTimer在UIViewController外崩溃?

时间:2017-07-25 05:22:04

标签: ios objective-c xcode nstimer forward-declaration

我正在尝试重新排列相关方法,包括NSTimer我之前放在UIViewController中的相关方法,以使代码更具可读性。我现在需要将它们重新定位到自定义类,以便它们可以独立于ViewController工作。

但是在我尝试这样做的过程中,我已经介绍了NSTimer的一个问题,即使代码看起来正确也不存在。以下错误日志发生崩溃:

  

线程1:EXC_BAD_ACCESS(代码= 1,地址= 0x1)

当我尝试使用断点并单步执行代码时,Xcode似乎停留在此语句的循环中

    timer = [NSTimer scheduledTimerWithTimeInterval:1
                                             target:self
                                           selector:@selector(nextClock)
                                           userInfo:nil
                                            repeats:YES];

这是我的代码的瘦身版本。 NSTimer位于名为ConcertController的类中,该类在PlayViewController.h

中有前向声明

PlayViewController.h

    #import <UIKit/UIKit.h>
    #import "ConcertController.h"

    @interface PlayViewController : UIViewController

    {    
        ConcertController *concertStateMachine;    
    }

    @end

并从viewDidLoad

中的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];

    }

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

编辑#2

ConcertController.m中的

初始化已按建议标准化。

    - (id)initConcertStateMachine:(int)selectedFamily
                forPlayer:(int)selectedPlayerID
    {
        self = [super init];

        if (self) {

            [self findEntryPointsFor:(int)selectedFamily
                   andPlayer:(int)selectedPlayerID];        
            [self startClock];
    }
    return self;
}

编辑#1。这是一个请求的日志。注意:日志还会显示在已发布的代码示例中找不到的一些项目(为清楚起见,已删除)。格雷格

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) 

enter image description here

3 个答案:

答案 0 :(得分:1)

我不完全确定是否是这样,但我认为你不应该在初始化程序中调用startClock。该init方法通常不遵循合适的初始化器的正确结构,即

- (instancetype)init... {
    self = [super init];
    if (self) {
        // initialize properties (and ivars in your case)
    }
    return self;
}

我认为问题在于您正在安排计时器,该计时器在初始化程序完成之前保存对self的引用,即ConcertController实例,即没有#t真的是self。特别是因为你从未调用super的初始值设定项(除非你在findEntryPointsFor:andPlayer:方法中执行此操作,这完全取消了任何约定)。 如果您稍后再调用startClock(例如从视图控制器),它可能已经有效,但我确实建议修复init以满足约定。不要忘记,特别是在ARC下,不仅仅是编码美学,ARC还依赖于某些东西来恰当地推断出要保留和释放的内容等。

除此之外,您直接定义ivars的事实有点可疑。我想这来自MRC的迁移?我也建议在这里使用属性(这不是真正的性能损失,因为许多人似乎错误地相信)。唯一要记住的是,在初始值设定项中,您使用_ivarName(即下划线表示法)访问它们,并依赖于其他地方的getter和setter(即通常的点语法,除了一些边缘情况,你需要避免一些键值观察的东西,但我从这里看到你甚至没有那个)。这真的是更清洁,更安全的方式,特别是当你想要更换计时器和所有这些时。如果您担心保持课程的公共界面清洁,请使用类扩展,这仍然比ivars更好。

答案 1 :(得分:0)

我的猜测是你的ConcertController声明不好。

试试PlayVC标题:

#import <UIKit/UIKit.h>
#import "ConcertController.h"

@interface PlayViewController : UIViewController

{    
     // remove this line.
}

@property (nonatomic, strong)  ConcertController *concertStateMachine;  

@end

如果它不是属性,则在需要内存时,iOS通常会自由释放该对象。在您的ConcertController中,您还应该更改NSTimer *计时器;也是财产。试一试。

答案 2 :(得分:-2)

如果您不使用ARC,

然后将其更改为:

lastEventChangeTime = [NSDate date];

尝试添加保留

lastEventChangeTime = [[NSDate date] retain];

完成lastEventChangeTime后,设置

lastEventChangeTime = nil;