ios - 为什么这个带有 ARC 的简单应用程序会泄漏?

标签 ios objective-c memory-leaks automatic-ref-counting

所以我对 objC 编程比较陌生。但不是 C。在一个更复杂的应用程序中,我认为我有内存泄漏。我编写了这个程序只是为了进行一些测试。该应用程序非常简单:它将一系列整数存储在 MutableArray 中,这些整数表示已安排的计时器。该应用程序在当前运行循环中有一个 NSTimer,它每秒检查一次是否是将计数器与 MutableArray 的正确元素进行比较的正确时间。一切正常,但调试面板中的内存增长,增长,增长......
我已经尝试了一些变体,但我仍然缺少关于 ARC 的一些东西。我只是不明白,因为 ARC 不是垃圾收集器,为什么内存会增长以及我做错了什么。
这是代码:

-(id)initWithLabel:(UILabel *)label {
    self = [super init];
    self.list = [[mtAllarmList alloc]init];
    self.label = label;
    return self;
}

我的类初始化函数。我将标签引用(弱,因为它是 View Controller 拥有的)传递给我的类(class)。我还分配和初始化包含 MutableArray 和其他信息(在原始应用程序中,要播放的文件,卷,eccetera)的类 mtAllarmList。
-(void)ClockRun { 
    NSMethodSignature * signature = [mtClockController instanceMethodSignatureForSelector:@selector(check)];
    NSInvocation * selector = [NSInvocation invocationWithMethodSignature: signature];
    [selector setTarget:self];
    [selector setSelector:@selector(check)];

    [[NSRunLoop currentRunLoop] addTimer: self.time = [NSTimer scheduledTimerWithTimeInterval:1
                                                                                   invocation:selector
                                                                                      repeats:YES]
                                 forMode:NSDefaultRunLoopMode];

    [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc]initWithTimeIntervalSinceNow: 30]];
}

ClockRun:是应用程序调用以启动一切的方法。它只是启动每秒触发的计时器来检查:
-(void)check {
    self.counter++;
    int i = [self.list check:self.counter];
    if(i == 1) {
        [self writeAllarmToLabel:self.label isPlayingAllarmNumber:self.counter];
    }
    else if (i == 2) {
        [self writeAllarmToLabel:self.label theString: @"Stop"];
        [self.time invalidate];
        self.counter = 0;
    }
    else {
        [self writeAllarmToLabel:self.label theString:[[NSString alloc]initWithFormat:@"controllo, %d", self.counter]];
    }
    NSLog(@"controllo %d", self.counter);
}

检查:简单地对 mtAllarmList 的 [list check: int] 方法的返回值使用react。如果定时器必须振铃,则返回 1,否则返回 0,如果序列结束则返回 2。在这种情况下 self.counter 将被设置为 0 并且 NSTimer 将无效。
-(id)init {
    self = [super init];
    self.arrayOfAllarms = [[NSMutableArray alloc]initWithCapacity:0];
    int i;
    for(i=1;i<=30;++i) {
        [self.arrayOfAllarms addObject: [[NSNumber alloc]initWithInt:i*1]];
    }

    for(NSNumber * elemento in self.arrayOfAllarms)
        NSLog(@"ho creato un array con elemento %d", [elemento intValue]);
    return self;
}

在 mtAllarmList init 方法中模拟构造一个数组(我尝试了多种模式)并记录所有元素。
-(int)check:(int)second {
    int maxValue = [[self.arrayOfAllarms lastObject] intValue];
    if(maxValue == second){
        self.index = 0;
        return 2;
    } else {
        if ([[self.arrayOfAllarms objectAtIndex:self.index] intValue] == second) {
            self.index++;
            return 1;
        } else {
            return 0;
        }
    }
}

检查方法是非常基本的,我认为不需要解释。

那么,为什么这个简单非常愚蠢的应用程序会泄漏?

最佳答案

由于您是在主运行循环中执行此操作,因此您可以(并且应该)简化 ClockRun方法:

- (void)ClockRun {
    self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(check) userInfo:nil repeats:YES];
}

那个NSInvocation代码是不必要的,NSRunLoop代码只能引入问题。

话虽如此,这不太可能是您内存消耗的来源。提供的代码片段中没有任何其他内容看起来像是明显的内存问题。如果您 100% 确信计时器已失效,则计时器不是问题。我想知道这个 mtClockController 处的 View Controller 之间的对象图.或者 View Controller 中的一些循环引用(例如,从 A 推送到 B 并再次推送到 A)。根据迄今为止提供的内容,很难说。

可悲的是,除了常规诊断之外,我们没有太多其他建议。首先,我将通过静态分析器运行应用程序(通过在 Xcode 中按 shift+command+B,或从 Xcode“产品”菜单中选择“配置文件”)。

其次,您应该通过 Leaks 和 Allocations 工具运行您的应用程序,以识别每次迭代中确切泄漏的内容。你有额外的 View Controller 实例吗?或者只是 mtClockController ?

除非您确定未释放的内容,否则很难对其进行补救。仪器是识别未发布内容的最佳工具。在 WWDC 2012 视频中 iOS App Performance: Memory视频的演示部分给出了使用 Instruments 的实用演示(以及大量关于内存管理的良好背景信息)。

第三,当我不确定事情是否在应该被释放的时候被释放时,我有时会包含 dealloc告诉我对象何时被释放的方法,例如:
- (void)dealloc {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

我建议这不仅适用于您的关键模型对象,也适用于您的 View Controller 。 (有时我们为我们的模型对象苦恼,只是为了意识到它是 View Controller 本身,它被其他东西保留。)

显然,Instruments 是一个更丰富的工具,但这可用于快速识别解除分配的失败(并向您展示什么是维护强引用)。

我通过 Instruments 运行你的应用程序,观察你的自定义对象,一切都被正确释放。下面,我标记了 A 代,按下按钮,让计时器到期,标记 B 代,再次按下按钮,等等。我做了四次,然后我模拟了一个内存警告,最后做了一次。一切看起来都很好(这是六个屏幕快照合二为一的汇编,显示了六代中每一代的总分配):

generations

我检查了你的世代,以及分配本身,没有你的对象在那里。一切都得到很好的释放。唯一与 UIKit 相关联的 Cocoa 内部对象和 NSString . Cocoa Touch 会在幕后缓存我们无法控制的各种内容。我做最后的“模拟器内存​​警告”的原因是让 Cocoa 有机会清除它所能清除的东西(你会看到,尽管 Generations 报告了什么,总分配量下降了一点)。

最重要的是,您的代码很好,这里没有什么可担心的。将来,不要担心在几代人中偶然出现的东西,而是专注于(a)您的类(class); (b) 任何相当大的东西。但这些都不适用于这里。

事实上,如果您使用 mt 限制 Instruments 仅记录您的类的信息。前缀(您可以通过停止记录仪器并点击分配图上的“i”按钮并配置“记录类型”来完成此操作),您将看到您期望的图表/世代类型:

mt classes only

关于ios - 为什么这个带有 ARC 的简单应用程序会泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23202727/

相关文章:

iOS:获取推送通知的所有消息

ios - block 中的 block 不执行

ios - UITableView (Swift 4) 中奇怪的缓存问题

ios - 尽管编码,NSURL 始终为零

ios - ios中SocketIO的实现

node.js - 为什么 Node.js heapdump 显示编译后的代码?

node.js - 使用 custom-react-scripts 库构建会导致内存泄漏

c# - 将 C# 项目文件转换为 Objective C 类的最佳方法是什么

ios - 水平条的 CorePlot 垂直滚动

java - 处理 JFrame 会导致内存泄漏吗?