简体   繁体   中英

Annotation dealloc method never called in ARC, underlying viewcontroller dealloc is called

I learned that I have problem with memory when my app started crashing in my custom annotation method. I was sure managing viewcontroller of the map 100% should have been popped from the view stack.

Here is the code for the annotation, TaxiArrivingAnnotation.h :

#import <Foundation/Foundation.h>
@import MapKit;

@interface TaxiArrivingAnnotation : NSObject<MKAnnotation>
@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic) int minutesToTaxiArrival;

-(void) startTimer;
@end

and TaxiArrivingAnnotation.m :

#import "TaxiArrivingAnnotation.h"

#define SECONDS_IN_A_MINUTE 60

@interface TaxiArrivingAnnotation ()
@property (nonatomic) NSTimer * timer;
@property (nonatomic) NSDate * timeOfArrival;
@property (nonatomic, weak) id token1;
@property (nonatomic, weak) id token2;
@end

@implementation TaxiArrivingAnnotation

- (id)init
{
    self = [super init];
    if (self) {
        __weak TaxiArrivingAnnotation * this = self;

        self.token1 = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note)
        {
            NSLog(@"DID BECOME ACTIVE");
            NSTimeInterval secondsLeft = [this.timeOfArrival timeIntervalSinceNow];
            if (secondsLeft < 0) {
                self.minutesToTaxiArrival = 0;
                return;
            }

            this.minutesToTaxiArrival = secondsLeft / SECONDS_IN_A_MINUTE;

            [this startTimer];
        }];

        self.token2 = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note)
       {
           NSLog(@"WILL RESIGN ACTIVE");
           [this.timer invalidate];
           this.timer = nil;
       }];

    }
    return self;
}


-(void) setMinutesToTaxiArrival:(int)newMinutes {
    self->_minutesToTaxiArrival = newMinutes;
    self->_timeOfArrival = [NSDate dateWithTimeIntervalSinceNow:SECONDS_IN_A_MINUTE * newMinutes];
    if (newMinutes < 0) {
        [self.timer invalidate];
    }
}

-(void) startTimer {
    self.timer = [NSTimer timerWithTimeInterval:SECONDS_IN_A_MINUTE target:self selector:@selector(aMinutedPassed) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}

-(void) aMinutedPassed {
    self.minutesToTaxiArrival--;
}

-(void) dealloc {
   NSLog(@"DEALLOC");
    if (self.timer != nil && [self.timer isValid])
        [self.timer invalidate];
    [[NSNotificationCenter defaultCenter] removeObserver:self.token1];
    [[NSNotificationCenter defaultCenter] removeObserver:self.token2];
}
@end

I am adding the annotation on viewDidAppear and removing it on viewDidDisappear . Not only removing it but nil -ing the reference. The dealloc method still not called when managing viewcontroller dealloc is called.

The real problem is that the timer and notifications fire and app crashes because the annotation has been deallocated.

You are attempting to invalidate your repeating timer in dealloc . The problem is that the timer keeps a strong reference to the target (your annotation), which will prevent dealloc from ever getting called (because it's only called when there are no more strong references). It's analogous to a strong reference cycle (aka retain cycle).

You have to invalidate the timer when your view controller is deallocated (or whatever the logical event is that initiates the dismissal of the view controller). And since the timer will be invalidated by the time you get to dealloc , you can remove the invalidate code from the dealloc method of the annotation, obviously.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM