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.
@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;
@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.