我为我的iPhone游戏开发了排行榜显示类。该类具有以下实例方法。
-(void)displayScoresWithRequest:(CBLeaderboard*)request completionHandler:(void(^)())completionHandler
{
if (request_ != nil)
return;
request_ = [[CBLeaderboard alloc] init];
[request_ setCategory:[request category]];
[request_ setPlayerScope:[request playerScope]];
[request_ setTimeScope:[request timeScope]];
[request_ setRange:[request range]];
__block CBLeaderboardDisplay* blockSelf = self;
[request_ loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
{
blockSelf->request_ = nil;
NSUInteger scoresCount = [scores count];
if (scoresCount == 0 && error != nil)
return;
NSMutableArray* playerIDs = [NSMutableArray array];
for (GKScore* score in scores)
[playerIDs addObject:[score playerID]];
[GKPlayer loadPlayersForIdentifiers:playerIDs withCompletionHandler:^(NSArray* players, NSError* error)
{
if (scoresCount > [players count] && error != nil)
return;
[blockSelf displayScores:scores players:players];
completionHandler();
}];
}];
[request_ release];
}
如您所见,该方法复制排行榜请求,执行它并调用提供的完成处理程序。我游戏中的一个图层调用此方法如下。
-(void)refreshDisplay
{
CBLeaderboard* request = [[CBLeaderboard alloc] init];
[request setCategory:[[sharedGameCenterManager_ classicLeaderboard] category]];
[request setPlayerScope:GKLeaderboardPlayerScopeFriendsOnly];
[request setTimeScope:GKLeaderboardTimeScopeAllTime];
static NSRange kRequestRange = NSMakeRange(1, 3);
[request setRange:kRequestRange];
__block GJGameOver* blockSelf = self;
[display_ displayScoresWithRequest:request completionHandler:^
{
CGSize displayContentSize = [blockSelf->display_ contentSize];
displayContentSize.width = width(blockSelf) - 2.0 * kGJLabelPadding;
[blockSelf->display_ setContentSize:displayContentSize];
CGFloat displayHeight =
bottomEdge(blockSelf->multiplierLabel_) - topEdge(blockSelf->menu_) - 2.0 * kGJLabelPadding;
CGFloat displayScoreDisplaysCount = [blockSelf->display_ scoreDisplaysCount];
CGFloat displayLabelPadding =
(displayHeight - [blockSelf->display_ minContentSize].height) / displayScoreDisplaysCount;
[blockSelf->display_ setLabelPadding:MIN(floor(displayLabelPadding), kGJLabelPadding)];
static CGFloat kFadeInDuration = 2.0;
if ([blockSelf->display_ opacity] == 0)
[blockSelf->display_ runAction:[CCFadeIn actionWithDuration:kFadeInDuration]];
}];
[request release];
}
当图层和因此显示都被解除分配并且请求尚未完成时,我的游戏崩溃。请求完成后,它会尝试向已解除分配的实例发送消息,然后发生崩溃。是否可以取消排行榜请求?如果没有,有什么办法可以避免崩溃而不会导致内存泄漏吗?
答案 0 :(得分:0)
在两个块中,您使用__block
来允许块引用self
而不保留它。这是问题所在,因为您正在执行异步操作,并且如果在释放self
之后执行该块,则它正在使用悬空指针。保留捕获物体的块的整个点是让它们保持活着,以便块可以使用它。
制作块时不保留self
通常是为了避免保留周期。但是,我在这里看不到任何保留周期:
request_
中的displayScoresWithRequest
可能会保留displayScoresWithRequest
中的阻止displayScoresWithRequest
中的阻止self
,CBLeaderboardDisplay
对象displayScoresWithRequest
中的阻止来自refreshDisplay
refreshDisplay
中的阻止self
,GJGameOver
对象GJGameOver
对象保留display_
,CBLeaderboardDisplay
对象但是,CBLeaderboardDisplay
对象不保留其实例变量request_
。 (这段代码编写得非常糟糕,因为request_
在方法结束时被释放但未设置为nil
。它应该是一个局部变量或其他东西。应该使用布尔标志如果你想检查代码是否运行过一次。)