3

我使用 ARC 构建了一个简单的琐事游戏。在 Xcode 中使用 Allocations 分析工具分析其内存使用情况时,我发现内存并不总是被释放。对于问题的一个示例,我有一个 ActivePlayer 对象的类:

活动播放器.h:

@interface ActivePlayer : NSObject

@property (nonatomic, strong) NSString * name;
@property (nonatomic) NSInteger overallScore;
@property (nonatomic) NSInteger questionScore;

- (id) initWithName:(NSString *)name;

@end

活动播放器.m:

#import "ActivePlayer.h"

@interface ActivePlayer ()

@end


@implementation ActivePlayer

- (id) initWithName:(NSString *)name
{
    self = [self init];
    if (self) {
        self.name = name;
        self.overallScore = 0;
    }
    return self;
}

/*
- (void)dealloc
{
    self.name = nil;
}
*/
@end

ActivePlayer 是在 ActiveGame 类的 createPlayer 方法中创建的:

[[ActivePlayer alloc] initWithName:name]

我正在执行以下测试用例:我开始一个新游戏(分配一个 ActivePlayer),我回答一个问题,然后游戏结束(此时 ActivePlayer 被释放)。然后我可以开始另一个游戏并重复这个循环(每个循环都是一个“游戏”,如下所述)。在使用 Allocations 分析工具时,我期望看到的是内存已在游戏中间分配,但在游戏结束后已被释放(无论我玩游戏多少次)。但我发现情况并非总是如此:

顺便说一句:下面的每个项目符号行描述了分配工具的对象列表选项卡中的一行;该站点不允许我发布屏幕截图,因此是文字说明。所有行都是实时的;我只查看 Created 和 Still Living 分配。

当游戏 #1 正在进行时,我看到了以下分配。

  • 类别=ActivePlayer;尺寸=16;负责任的调用者=-[ActiveGame createPlayer:]
  • 类别=Malloc 48 字节;大小=48;负责任的调用者=-[ActivePlayer initWithName:]

游戏 #1 完成后,我看到以下内容。ActivePlayer 对象已被释放,但 48 字节仍然是 Live 的。

  • 类别=Malloc 48 字节;大小=48;负责任的调用者=-[ActivePlayer initWithName:]

如果我开始游戏 #2,我会在游戏进行时看到以下内容。除了游戏 #1 中的分配之外,还有两种新分配。

  • 类别=Malloc 48 字节;大小=48;负责任的调用者=-[ActivePlayer initWithName:]
  • 类别=ActivePlayer;尺寸=16;负责任的调用者=-[ActiveGame createPlayer:]
  • 类别=Malloc 144 字节;大小=144;负责任的调用者=-[ActivePlayer initWithName:]

在游戏 #2 完成后,我看到以下内容。同样,ActivePlayer 对象已被释放,但“Malloc X Bytes”分配仍然存在。

  • 类别=Malloc 48 字节;大小=48;负责任的调用者=-[ActivePlayer initWithName:]
  • 类别=Malloc 144 字节;大小=144;负责任的调用者=-[ActivePlayer initWithName:]

在那之后,我得到了不寻常的结果——如果我玩游戏 #3、#4 和 #5,我再也看不到 Category="Malloc X Bytes" 的游戏行,只有 Category=ActivePlayer 的新行,即游戏结束后释放。如上所示,前两个“Malloc”行继续存在。我还看到了其他奇怪的行为——昨天在使用 iPhone 6.0 模拟器进行测试时,只有在游戏 #2 和 #3 之后才留下实时内存,而不是游戏 #1、#4 和 #5。因此,虽然内存仍然分配,但它发生的时间似乎因我的设备和不同版本的模拟器而异。

还有我的问题:

  • 我的理解是否正确,在游戏结束并且 ActivePlayer 对象已被释放后,我不应该从调用 initWithPlayer 中看到任何实时内存?
  • 如果是,是什么原因造成的,我该如何解除分配?
  • 还是我根本不需要担心?

笔记:

  • 这些屏幕截图来自在运行 iOS 6.1 的 iPhone 4 上运行我的应用程序。但是我在 5.1、6.0 和 6.1 的 iPhone 模拟器上看到了类似的行为,而且我在升级之前在运行 iOS 6.0 的 iPhone 上看到了它。
  • 在 ActivePlayer.m 中,dealloc 方法目前已被注释掉,尽管我已经在它未被注释的情况下进行了测试,并验证了它正在被调用(由系统调用;我不直接在任何地方调用 dealloc)。无论哪种方式,行为都是相同的。
  • 对于它的价值,Leaks 分析工具没有报告任何内容。
  • 虽然这是一个导致我认为应该释放 192 字节的活动内存的示例,但我在我的许多课程中都看到了这一点,即内存分配似乎随着时间的推移而增长,我认为这是一个问题。
4

3 回答 3

3

您列出的代码很好。看起来您仍在代码中的其他位置维护对原始 ActivePlayer 的引用。

作为旁注,您创建 ActivePlayer 的模式不是规范 - 通常一个类不会从 init 方法中调用 alloc 。相反,调用者应该执行:

[[ActivePlayer alloc] initWithName:@"Bob"];

并且您的 init 方法应该使用的返回值

[super init];
于 2013-02-13T00:56:39.900 回答
2

我觉得您的构造函数是静态的(+ 号)很奇怪。命名约定要求名称前缀为 as 的方法init将返回托管内存对象。我怀疑内部方法的结果是根据它们的方法名称来评估的。

于 2013-02-13T00:59:17.053 回答
1

我认为实例计数测试确定您的代码没有泄漏 ActivePlayers。顺便说一句,构造函数和初始化的更好形式是这样的:

// .h

@interface ActivePlayer : NSObject

+ (id)activePlayerWithName:(NSString *)name;

@end

// .m

+ (id)activePlayerWithName:(NSString *)name {
    return [[self alloc] initWithName:name];
}

// if you want to make this public (include in .h interface) you can
// the callers will have the choice of alloc init pattern, or the factory
//
- (id)initWithName:(NSString *)name {
    self = [self init];
    if (self) {
        _name = name;
    }
    return self;
}

然后调用者这样做:

ActivePlayer *activePlayer = [ActivePlayer activePlayerWithName:@"Charlie"];
于 2013-02-13T01:06:06.613 回答