Ios 为什么这个简单的应用程序会出现电弧泄漏?

Ios 为什么这个简单的应用程序会出现电弧泄漏?,ios,objective-c,memory-leaks,automatic-ref-counting,Ios,Objective C,Memory Leaks,Automatic Ref Counting,所以我对objC编程比较陌生。但不是C。在一个更复杂的应用程序中,我想我有一个内存泄漏。我把它编好只是为了做一些测试。这个应用程序非常简单:它在一个可变数组中存储一系列整数,这些整数由当前计时器调度。该应用程序在当前运行循环中有一个NSTimer,该NSTimer每秒检查是否是将计数器与MutableArray的正确元素进行比较的正确时间。一切正常,但调试面板中的内存在增长,增长,增长… 我尝试了一些变体,但关于ARC,我仍然缺少一些东西。我只是不明白,既然ARC不是垃圾收集器,为什么内存会增长

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

-(id)initWithLabel:(UILabel *)label {
    self = [super init];
    self.list = [[mtAllarmList alloc]init];
    self.label = label;
    return self;
}
我的类初始化函数。我将标签引用(因为它是viewcontroller自己的)传递给我的类。我还分配并初始化包含可变数组和其他信息(在原始应用程序、要播放的文件、卷、eccetera中)的类mtAllarmList

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);
}
Check:只对mtAllarmList的[list Check:int]方法的返回值作出反应。如果计时器必须鸣响,则返回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;
}
相反,检查方法非常简单,我认为不需要解释


那么,为什么这个简单而愚蠢的应用程序会泄密呢?

有几个观察结果:

  • 不要使用
    scheduledTimerWithInterval
    的调用形式,而是直接尝试使用选择器形式,在这种情况下,它更简单、更清晰

  • 由于您直接调用
    runUntilDate
    ,因此我认为不会创建/耗尽任何自动释放池,这会导致内存泄漏,特别是在check函数中。不要调用runUntilDate并允许正常的运行循环处理来处理事情(正常的首选机制),或者在
    @autoreleasepool
    块中包装检查


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

    - (void)ClockRun {
        self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(check) userInfo:nil repeats:YES];
    }
    
    NSInvocation
    代码是不必要的,而
    nsrunlop
    代码只会带来问题

    话虽如此,这不太可能是内存消耗的来源。所提供的代码片段中没有任何其他内容看起来像是明显的内存问题。如果您100%确信计时器正在失效,那么计时器不是问题所在。我想知道这个
    mtClockController
    的视图控制器之间的对象图。或者视图控制器中的某些循环引用(例如,从A推到B再推到A)。根据目前提供的情况很难说

    遗憾的是,除了常规诊断之外,我们没有什么其他建议。首先,我通过静态分析器运行应用程序(在Xcode中按shift+command+B,或从Xcode的“产品”菜单中选择“Profile”)

    其次,您应该通过泄漏和分配工具运行应用程序,以确定每次迭代中泄漏的内容。是否有视图控制器的额外实例?还是仅使用
    mtClockController

    在你确定哪些东西没有被分配之前,很难补救。而仪器是识别未发布内容的最佳工具。在WWDC 2012视频中,视频的演示部分给出了使用仪器的实用演示(以及大量有关内存管理的良好背景信息)

    第三,当我不确定事情是否应该被解除分配时,我有时会包括
    dealloc
    方法,告诉我对象何时被解除分配,例如:

    - (void)dealloc {
        NSLog(@"%s", __PRETTY_FUNCTION__);
    }
    
    我建议这不仅适用于关键模型对象,也适用于视图控制器。(有时,我们为模型对象感到苦恼,却发现它是视图控制器本身,由其他对象保留。)

    显然,Instruments是一个更丰富的工具,但它可以用来快速识别取消分配的失败(并向您展示如何维护强引用)


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

    我检查了你们的几代人,以及分配本身,你们的对象都不在其中。一切都很顺利。只有与
    UIKit
    NSString
    关联的内部Cocoa对象。Cocoa Touch在幕后对我们无法控制的东西进行各种缓存。我做最后一个“模拟器内存警告”的原因是给Cocoa一个机会来清除它所能清除的内容(你会看到,不管几代人报告了什么,总分配量还是下降了一点)

    总之,您的代码很好,这里没有什么可担心的。在未来,不要担心在几代人中偶然出现的东西,而是专注于(a)你的课程;和(b)任何大的东西。但这些都不适用于这里

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


    您是否尝试过分配和泄漏安装
    - (void)ClockRun {
        self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(check) userInfo:nil repeats:YES];
    }
    
    - (void)dealloc {
        NSLog(@"%s", __PRETTY_FUNCTION__);
    }