【问题标题】:Memory Leaks - Formatting a String To Display Time, Each Second内存泄漏 - 格式化字符串以显示时间,每秒
【发布时间】:2009-06-22 01:12:56
【问题描述】:

大家好。我有一个每秒被调用的方法,我想用它来显示我的应用程序一直在工作的时间。目前我正在使用的类(我没有创建)有一个名为 progress 的属性,它存储总秒数。

我已经编写了一些代码,这些代码需要几秒钟并将其格式化为可读的字符串。我是新手,如果它不是最好的代码,请原谅我。我欢迎任何建议:

// hours, minutes, and seconds are instance variables defined as integers
int totalSeconds = (int)streamer.progress;

hours = totalSeconds / (60 * 60);

if (hours > 0)
    formattedTimeString = [NSString stringWithFormat:@"%d:", hours]; // WRONG

minutes = (totalSeconds / 60) % 60;
seconds = totalSeconds % 60;
[formattedTimeString stringByAppendingFormat:@"%d:%d", minutes, seconds]; // WRONG

基本上我希望它显示为“3:35”,例如显示 3 分 35 秒。我只想显示小时部分,如果它已经是一个小时,在这种情况下它将是“2:3:35”(谁能推荐一种更好的格式化方法?)。

问题是我实际创建/设置字符串的位置(标记为错误的行)。由于这是每秒都在完成,如果我一直要求一个新的字符串对象,我很容易得到泄漏。我想我可以通过在方法结束时释放 foramttedTimeString 来解决这个问题,但这是实现此目的的正确方法吗? NSMutableString 会以任何方式提供帮助吗?有没有更好的可可方式来做到这一点?我已经在#iphonedev@freenode 中询问过,他们说我必须自己编写这个方法,但我想我会再问一次。

提供上下文:这是一个互联网广播流媒体应用程序(我知道已经有很多,但我只是在练习)。我希望能够显示流已播放的时间量。

对不起,如果这个问题很愚蠢,呵呵,就像我说我是新手一样。

【问题讨论】:

    标签: iphone objective-c cocoa-touch memory


    【解决方案1】:

    我会这样做:

    int totalSeconds = (int)streamer.progress;
    hours = totalSeconds / (60 * 60);
    minutes = (totalSeconds / 60) % 60;
    seconds = totalSeconds % 60;
    
    if ( hours > 0 ) {
        formattedTimeString = [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds];
    } else {
        formattedTimeString = [NSString stringWithFormat:@"%d:%02d", minutes, seconds];
    }
    

    现在最后,formattedTimeString 是所需的时间,但您并不“拥有”它 - 如果您想保留它,您必须保留它,或者将其存储在“复制”属性中。

    请注意,%02d 为您提供了一个有保证的两位数,零填充的数字,这通常是您在某些时候想要的数字。

    要查看您将如何使用 stringByAppendingFormat,它看起来像这样:

    NSString* formattedTimeString = @"";
    if ( hours > 0 ) {
        formattedTimeString = [formattedTimeString stringByAppendingFormat:@"%d:", hours];
    }
    formattedTimeString = [formattedTimeString stringByAppendingFormat:@"%d:%02d", minutes, seconds];
    

    但是在这种情况下,您会得到 3:4:05 这样的时间,而不是更理想的 3:04:05。

    请注意,formattedTimeString 每次都会被覆盖,但这没关系,因为您在任何时候都不“拥有”它,因此您不负责释放它。

    最后,用可变字符串查看它,它可能看起来像这样:

    NSMutableString* formattedTimeString = [NSMutableString string];
    if ( hours > 0 ) {
        [formattedTimeString appendFormat:@"%d:", hours];
    }
    [formattedTimeString appendFormat:@"%d:%02d", minutes, seconds];
    

    同样,时间结果是不希望出现的 3:4:05,而且您最后并不拥有 formattedTimeString,因此必须保留它或使用复制属性存储它以保留它。

    【讨论】:

    • 感谢 %02d 提示。我应该使用 [NSMutableString string] 还是 [[NSMutableString alloc] initWithCapacity:8] 实例化(以 8 为例)?
    • 有一个stringWithCapacity:。使用与否可能没有区别,除了 huge 字符串。
    • 我应该补充一点,现在不是进行优化决策的时候:首先编写清晰、有效的代码,然后对其进行分析,然后消除缓慢的部分。
    • 正如 Peter Hosey 所说,除非性能分析显示出问题,否则永远不要使用 initWithCapacity,除非您分配数百万个字符串或巨大的字符串,否则这不太可能发生。当然,即使为这段代码考虑它也是浪费时间,如果您决定稍微更改格式,可能会引入一个微妙的错误。
    【解决方案2】:

    为了将增量作为时间单位,您还可以执行以下操作:

    // as part of init...
    self.gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    
    
    // in the timer or wherever you are tracking time deltas...
    static NSUInteger unitFlags = 
        NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
    
    NSDateComponents *components = [gregorian components:unitFlags
                            fromDate:myBaseTime
                              toDate:[NSDate date] options:0];
    

    然后您可以使用类似[components minute] 的内容引用这些部分。 请记住,您必须在 dealloc 中发布日历。

    【讨论】:

      【解决方案3】:

      您的代码看起来不错。您没有泄漏任何内存,因为您创建的字符串对象的保留计数为零,并且将被系统清理。但是,如果 formattedTimeString 不是函数中的局部变量,则需要在最后保留它以防止这种情况发生!为此,您需要将 [formattedTimeString retain] 添加到代码块的末尾,然后在替换字符串对象之前添加 [formattedTimeString release]。

      作为一般规则,名称包含“alloc”、“copy”、“create”和“new”的函数返回已被保留的对象(意味着它们的保留计数为 +1)。使用完这些对象后,您有责任在这些对象上调用 release 或 autorelease - 否则它们将开始堆积在内存中。

      “stringWithFormat:”、“imageNamed:”和“arrayWithCapacity:”等函数都返回保留计数为零的对象 - 因此您可以安全地丢弃它们(就像您在代码示例中一样)。如果您想保留它们,则应调用 retain 以确保在使用它们时不会清理它们。

      说了这么多,我认为主要问题是您使用 stringByAppendingFormat:。由于您使用的 NSString 不是可变的,因此该调用返回一个 new 字符串。你想说:

      formattedTimeString = [formattedTimeString stringByAppendingFormat:@"%d:%d", minutes, seconds];

      或者,您可以使用 NSMutableString。由于这是您将一遍又一遍地做的事情,我建议您这样做。不过从技术上讲,任何一种方式都很好。

      希望对您有所帮助!整个保留/释放的事情可能会让人感到困惑。请记住,每个对象都有一个“retainCount”,一旦它达到零,就无法知道对象或其数据会发生什么。

      【讨论】:

      • 自动释放对象的保留计数不为零。自动释放不影响保留计数;保留计数仅在对象确实被释放时才会下降,这发生在自动释放池耗尽时。当保留计数达到零时,对象立即消失(当不在 GC 下运行时)。
      【解决方案4】:

      嘿,谢谢大家,我很感激您的回复。

      我最终这样做了,它有效,但我想知道你们是否发现它有任何问题:

      int totalSeconds = (int)streamer.progress;
      
      [formattedTimeString setString:@""];
      
      hours = totalSeconds / (60 * 60);
      
      if (hours > 0)
          [formattedTimeString appendFormat:@"%d:", hours];
      
      minutes = (totalSeconds / 60) % 60;
      seconds = totalSeconds % 60;
      
      [formattedTimeString appendFormat:@"%02d:%02d", minutes, seconds];
      

      然后当然是在viewDidLoad 中实例化实例变量formattedTimeString

      formattedTimeString = [[NSMutableString alloc] initWithCapacity:8];
      

      我在第一个代码 sn-p 中没有做任何保留/释放,因为我认为没有必要,但我可能是错的。不过,我是在 dealloc 方法中释放的,所以我应该没问题。

      【讨论】:

      • 您通常应该使用 NSTimeInterval 来计算秒数。这使您可以跟踪几分之一秒(例如,603.5 秒)。
      • 您的所有权处理是可靠的。但是,如果此代码与也访问 formattedTimeString 变量的线程并行运行,则将其置于像这样的部分状态是在自找麻烦,您应该建立一个新字符串,然后发送 setString: 。 (如果你没有这样的线程情况,当前的代码很好,尤其是在 iPhone 上,我听说分配很昂贵。)
      • 感谢大家的回复,非常感谢。
      猜你喜欢
      • 2015-08-28
      • 1970-01-01
      • 2014-04-30
      • 2021-01-26
      • 2011-08-29
      • 2013-04-03
      • 2018-06-21
      • 1970-01-01
      • 2011-06-12
      相关资源
      最近更新 更多