سؤال

I think I'm getting a retain cycle. It happens as I push and dismiss a VC. Every time I push and dismiss, similar objects build up.

enter image description here

Can someone help me understand this profiler output. (photo above)

Question 1 + 1.5: It seems like an event triggers a GCD dispatch block that never gets released. Admitably I don't fully understand everything that is going on. What is this object (1.31mb highlighted object) and the the series of events in its life-cycle? Any idea why it is not being deallocated?

Question 2: Do you see any opportunities for Retain Cycles in the code below?

iOS: 6

xcode: 4.6

tested: iphone 4 device

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
هل كانت مفيدة؟

المحلول

It was a bug in the library that I was using.

See answer here: https://stackoverflow.com/a/20060710/2865234

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top