简体   繁体   中英

Retain Cycle in DetailViewController - ARC Enabled

I am getting a retain cycle on presenting/dismissing my DetailViewController. Any help finding an opportunity for retain cycles in the code below would be greatly appreciated.

Question: Is there a retain cycle in the code posted below?

在此处输入图片说明

iOS: 6

xcode: 4.6

tested: iphone 4 device

ARC Enabled

Edit: added instruments photo.

DetailViewController.h

@interface SRDetailViewController : UIViewController 

@property (weak, nonatomic) IBOutlet UILabel *roomTitle;
@property (weak, nonatomic) IBOutlet UIView *userScreenContainer;
@property (weak, nonatomic) IBOutlet UIView *opponentScreenContainer;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (weak, nonatomic) IBOutlet UIButton *retryButton; 
@property (weak, nonatomic) IBOutlet UIProgressView *progressBar;
@property (strong, nonatomic) NSTimer *progressTimer;
@property (strong, nonatomic) NSTimer *retryTimer;
@property (weak, nonatomic) IBOutlet UIView *bottomViewContainer;


@property (strong, nonatomic) SRRoom *room;

@property (strong, nonatomic) SROpenTokVideoHandler *openTokHandler;

DetailViewController.m

@interface SRDetailViewController ()

@property (strong, nonatomic) NSString* kApiKey;
@property (strong, nonatomic) NSString* kSessionId;
@property (strong, nonatomic) NSString* kToken;


@end

@implementation SRDetailViewController


- (void)viewDidLoad
{
    [super viewDidLoad];
    [self configOpentTok];
    [self performGetRoomRequest];
    [self configNavBar];
    [self configNotifcations];
    [self configProgressBar];

}

-(void)configSocialSharing
{
    //check if it already exists
    for(UIView *subview in self.view.subviews){
        if([subview isKindOfClass:[SRSocialSharing class]]){
            return;
        }
    }

    //add off screen
    CGRect frame = CGRectMake(0, [[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen] bounds].size.width, 44);
    SRSocialSharing *share = [[SRSocialSharing alloc] initWithFrame:frame];
    [self.view addSubview:share];

    share.sharingURL = [self createUrlForSharing];
    //animate in
    frame = CGRectMake(0, [[UIScreen mainScreen] bounds].size.height-100, [[UIScreen mainScreen] bounds].size.width, 44);
    [UIView animateWithDuration:3 delay:2 options:UIViewAnimationOptionCurveEaseOut animations:^{
        share.frame = frame;
    } completion:nil];
}

-(NSURL *)createUrlForSharing
{
    NSRange range = NSMakeRange(self.room.sessionId.length-7, 6);
    NSString *shortSessionId = [self.room.sessionId substringWithRange:range];
    NSString *urlString = [NSString stringWithFormat:@"url/invites/%@/%@?sessionId=%@",self.room.topicId, [self opposingPosition:self.room.position],shortSessionId];
    return [NSURL URLWithString:urlString];
}
-(NSString *)opposingPosition:(NSString*)position
{
    return ([position isEqualToString:@"agree"])? @"disagree" : @"agree";
}


-(void) configOpentTok{   
    [self.openTokHandler registerUserVideoStreamContainer:self.userScreenContainer];
    self.openTokHandler.userVideoStreamConatinerName = self.room.position;

    [self.openTokHandler registerOpponentOneVideoStreamContainer:self.opponentScreenContainer];
    self.openTokHandler.opponentOneVideoStreamConatinerName = [self opposingPosition:self.room.position];

    self.openTokHandler.shouldPublish = YES;
    self.openTokHandler.isObserving = NO;
}

-(void)configNavBar
{

    UIImage *backButtonImage = [UIImage imageWithContentsOfFile:@"backButton"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
    [backButton setFrame:CGRectMake(0, 0, 47, 32)];
    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    [backButton addTarget:self action:@selector(pressBackButton) forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *navBackButton = [[UIBarButtonItem alloc] initWithCustomView:backButton];

    [self.navigationItem setLeftBarButtonItem:navBackButton];

    self.title = [self.room.position stringByReplacingCharactersInRange:NSMakeRange(0,1)
                                                   withString:[[self.room.position  substringToIndex:1] capitalizedString]];
}

-(void)pressBackButton{
    self.navigationItem.leftBarButtonItem.enabled = NO;
    [self manageSafeClose];
    double delayInSeconds = 3;
    //[self updateStatusLabel:@"Disconnecting" withColor:[UIColor grayColor]];

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [self.navigationController popViewControllerAnimated:YES];
    });
}

-(void)configNotifcations
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(recieveNotifications:)
                                                 name:kSROpenTokVideoHandlerNotifcations
                                               object:nil
     ];
}

-(void)recieveNotifications:(NSNotification *)notification
{
    if ([[notification name] isEqualToString:kSROpenTokVideoHandlerNotifcations]){
        NSDictionary *userInfo = notification.userInfo;
        NSNumber *message = [userInfo objectForKey:@"message"];
        [self statusMessage: message];
    }
}

-(void)statusMessage:(NSNumber*)message{

        NSString *result = nil;

        switch([message intValue]) {
            case 0:
                result = @"Disconnected";
                break;
            case 1:
                result = @"Connecting...";
                [self startRetryTimer];
                break;
            case 2:
                result = @"Publishing Your Video...";
                break;
            case 3:
                result = @"Searching for Idiots...";
                break;
            case 4:
                result = @"Start!";
                [self startProgressBar];
                [self stopTimer:self.retryTimer];
                break;
            case 5:
                [self stopTimer:self.progressTimer];
                result = @"Stopped!";
                break;
            case 6:
                [self stopTimer:self.progressTimer];
                result = @"Disconnecting...";
                break;
            case 7:
                result = @"Opponent failed to join. Retrying...";
                [self performSelector:@selector(retry) withObject:nil afterDelay:4];
                break;
            default:
                result = @"Retry";
        }

    [self updateStatusLabel:result withColor:[self statusLabelColorPicker:message] animated:YES];
    NSLog(@"STATUS LABEL UPDATE: %@", message);

}

-(UIColor*)statusLabelColorPicker:(NSString *)Message{
    return [UIColor whiteColor];
}

-(void)performGetRoomRequest{
    __weak typeof(self) weakSelf = self;
    [[RKObjectManager sharedManager] getObject:weakSelf.room
                                          path:nil
                                    parameters:nil
        success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
            weakSelf.openTokHandler.kToken = weakSelf.room.token;
            weakSelf.openTokHandler.kSessionId = weakSelf.room.sessionId;
            weakSelf.roomTitle.text = weakSelf.room.title;
            weakSelf.navigationController.title = weakSelf.room.position;
            [weakSelf configSocialSharing];
            [weakSelf.openTokHandler doConnectToRoomWithSession];
        }failure:^(RKObjectRequestOperation *operation, NSError *error){

    }];
}

-(void)dealloc
{
    self.room = nil;
}

-(void)manageSafeClose{
    [self stopTimer:self.retryTimer];
    [self stopTimer:self.progressTimer];
    [self.openTokHandler safetlyCloseSession];
    [[RKObjectManager sharedManager].operationQueue cancelAllOperations];
    self.openTokHandler = nil;
    self.title = nil;
    self.navigationItem.leftBarButtonItem = nil;
    self.kApiKey = nil;
    self.kSessionId= nil;
    self.kToken= nil;

    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self doCloseRoom];
}

-(void)doCloseRoom
{
    __weak typeof(self) weakSelf = self;
    [[RKObjectManager sharedManager] deleteObject:weakSelf.room
                                             path:nil
                                       parameters:nil
                                          success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult){
                                              //NSLog(@"Mapping result %@", mappingResult);
                                          }
                                          failure:nil
     ];
}

-(void)startRetryTimer
{
    NSLog(@"Timer Started");
    self.retryTimer  = [NSTimer scheduledTimerWithTimeInterval:(60*5)
                                                        target:self
                                                      selector:@selector(retry)
                                                      userInfo:nil
                                                       repeats:YES];
}

-(void)retry
{
    [self doCloseRoom];
    [self performSelector:@selector(performGetRoomRequest) withObject:nil afterDelay:4];
}

#pragma mark - label
- (void)updateStatusLabel:(NSString *) message withColor:(UIColor*) color animated:(bool) animated
{
    self.statusLabel.text = message;
    if (animated) {
        [self fadeOutFadeInAnimation:self.statusLabel andColor:color];
    } else{
        [SRAnimationHelper stopAnimations:self.statusLabel];
    }

}

- (void)fadeOutFadeInAnimation:(UILabel *)label andColor:(UIColor*)color
{

    //add animation
    [label.layer addAnimation:[SRAnimationHelper fadeOfRoomStatusLabel] forKey:nil];

    //change label color
    label.textColor = color;
}




#pragma mark - Progress Bar
-(void)configProgressBar
{
    self.progressBar.progressTintColor = [UIColor orangeColor];
}

-(void)startProgressBar
{
    self.progressBar.hidden = NO;
    self.progressBar.progress = 0;
    self.progressTimer  = [NSTimer scheduledTimerWithTimeInterval:.5
                                                           target:self
                                                         selector:@selector(changeProgressValue)
                                                         userInfo:nil
                                                          repeats:YES];
}

-(void)stopTimer: (NSTimer*)timer
{
    [timer invalidate];
    timer = nil;
}

- (void)changeProgressValue
{

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        float progressValue         = self.progressBar.progress;
        progressValue               += .00834;
        if (progressValue > .99)
        {
            progressValue = 1;
            [self stopTimer:self.progressTimer];
            return;
        }

        NSString* time =[NSString stringWithFormat:@"%.0f", 60 - ceil(progressValue*60)];
        NSLog(@"Progress Value %f Time %@", progressValue, time);


        NSString *message = [NSString stringWithFormat:@"Time Left: %@",  time];

        dispatch_async(dispatch_get_main_queue(), ^(void) {
            self.progressBar.progress      = progressValue;
            [self updateStatusLabel:message withColor:[UIColor whiteColor] animated:NO];

        });
    });


}

@end

With the possible exception of the timer retention mentioned above, there are no retain cycles within the posted code. I didn't trace the logic of the timers, but it's very easy to debug.

As an added note, that is some of the best code as I've seen for avoiding common mistakes. You should be commended for doing great work.

It seems like you have retain cycle with timers:

@property (strong, nonatomic) NSTimer *retryTimer;

self.retryTimer  = [NSTimer scheduledTimerWithTimeInterval:(60*5)
                                                    target:self
                                                  selector:@selector(retry)
                                                  userInfo:nil
                                                   repeats:YES];

You need to set self.retryTimer = nil at some point or invalidate the timer. This is weirdly written ( timer is just a local variable):

-(void)stopTimer: (NSTimer*)timer
{
    [timer invalidate];
    timer = nil;
}

On second thought, invalidate should break the retain cycle. Although are you certain you're calling this method?

On third thought, you might have accidentally created several timers - each [self startRetryTimer] gives you a new one. If that happens, you'll invalidate the last one, and the rest will stay in stopped state. That could be your problem.

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