简体   繁体   English

在DetailViewController中保留周期-启用ARC

[英]Retain Cycle in DetailViewController - ARC Enabled

I am getting a retain cycle on presenting/dismissing my DetailViewController. 我在展示/关闭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 iOS:6

xcode: 4.6 xcode:4.6

tested: iphone 4 device 已测试:iphone 4设备

ARC Enabled 启用ARC

Edit: added instruments photo. 编辑:添加了乐器照片。

DetailViewController.h 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 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. 您需要在某个时候设置self.retryTimer = nil或使计时器无效。 This is weirdly written ( timer is just a local variable): 这是很奇怪的( timer只是一个局部变量):

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

On second thought, invalidate should break the retain cycle. 再三考虑, invalidate应该打破保留周期。 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. 第三,您可能不小心创建了多个计时器 -每个[self startRetryTimer]为您提供了一个新计时器 If that happens, you'll invalidate the last one, and the rest will stay in stopped state. 如果发生这种情况,您将使最后一个无效,其余的将保持停止状态。 That could be your problem. 那可能是你的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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