【问题标题】:Objective-c - NSMutableString setString vs NSStringObjective-c - NSMutableString setString vs NSString
【发布时间】:2013-05-26 03:15:21
【问题描述】:

在 setter 中按如下方式保留和释放 NSString 是否更好:

-(void) setName:(NSString *)newName
{
    if(newName != nil)
    {
         [newName retain]:
         [m_Name release];
         m_Name = newName; //Where m_Name is a NSString *
    }
    //I'm not sure for this code, I have difficulties understanding memory-management in ObjC
}

或者通过 NSMutableString 改变值:

-(void) setName:(NSString *)newName
{
    if(newName != nil)
        [m_Name setString:newName]; //Where m_Name is a NSMutableString *
}

如果任何一种或两种方法不正确,请告诉我。

【问题讨论】:

  • 为什么要实现自己的“setter”方法?除了您在此处发布的内容之外,除非您实际上还有其他处理要做,否则没有必要。
  • @maddy 我敢打赌 OP 正在使用过时的教程或书籍。

标签: objective-c nsstring release retain nsmutablestring


【解决方案1】:

一些想法:

  1. 最佳实践是根本不编写 setter,以利用自动合成的访问器方法)。自己编写只是一个搞乱内存管理或引入错误的机会。在编写自定义设置器之前,您应该对自定义设置器有迫切的需求。

  2. 实例变量名称的新兴约定是使用前导下划线开头的属性名称(例如,对于名为 name 的属性,ivar 是 _name)。如果您省略 @synthesize 语句,最新版本的 Xcode 中包含的编译器会自动为您执行此操作。

  3. 如果您没有说明您的属性具有哪些内存限定符,那么设置器应该是什么的问题毫无意义。我假设您将您的属性定义为retain

  4. 将属性更改为 NSMutableString 会更改您的属性的行为,除非您出于某种原因确实需要可变字符串,否则我不建议这样做。

  5. 如果有人将name 属性设置为nil,您的第一个示例将不会执行任何操作。但是如果有人想将它设置为nil,你仍然应该(a)释放旧的name值; (b) 将您的 ivar 设置为 nil。 (顺便说一句,我下面的代码利用了这样一个事实,即向nil 对象发送消息什么都不做,所以在这种情况下,我不需要检查它是否是nil。)

所以,我假设您的属性定义如下:

@property (nonatomic, retain) NSString *name;

还有一条被省略或看起来像这样的合成行:

@synthesize name = _name;

那么,我认为 setter 应该是这样的:

-(void) setName:(NSString *)name
{
    // if you want to program defensively, you might want the following assert statement:
    //
    // NSAssert(name == nil || [name isKindOfClass:[NSString class]], @"%s: name is not string", __FUNCTION__);

    if (name != _name)
    {
        [_name release];
        _name = name;
        [_name retain];
    }
}

顺便说一句,我假设您的 init 方法正确初始化了 _name 并且 dealloc 释放它。

- (id)init
{
    self = [super init];
    if (self) {
        _name = nil;
    }
    return self;
}

- (void)dealloc
{
    [_name release];
    [super dealloc];
}

正如 bblum 指出的那样,为您的 NSString 属性使用 copy 是谨慎的做法:

@property (nonatomic, copy) NSString *name;

那么,我认为 setter 应该是这样的:

-(void) setName:(NSString *)name
{
    // if you want to program defensively, you might want the following assert statement:
    //
    // NSAssert(name == nil || [name isKindOfClass:[NSString class]], @"%s: name is not string", __FUNCTION__);

    if (name != _name)
    {
        [_name release];
        _name = [name copy];
    }
}

但实际上,除非绝对需要,否则您根本不应该编写 setter。


最后,您的代码有一条关于发现内存管理令人困惑的注释。虽然您肯定需要了解它,但我提出两个最终建议:

  1. 考虑使用Automatic Reference Counting (ARC)。虽然这并不能消除理解内存管理工作原理的需要(参见Advanced Memory Management Programming Guide),但它确实使编写正确处理内存管理的代码变得更加容易。如果您编写手动引用计数 (MRC) 代码,则很容易犯简单的内存管理错误,否则 ARC 会为您处理这些错误。

  2. 特别是如果您要使用 MRC,请使用 Xcode 的静态分析器(“产品”菜单上的“分析”或按 shift+command+B)。这有助于发现困扰 MRC 代码的许多常规内存管理问题。 Instruments 用户指南Finding Memory Leaks 部分还说明了如何在调试代码时发现泄漏,但静态分析器通常可以通过检查代码来识别问题。

【讨论】:

  • NSString @property 声明应始终为copy,而不是retain。这是对foo.bar = someMutableString; 的防御。另外,我认为在这种情况下,您的 setter 实现最终会生成两个 KVO 通知,因为标准 setter 自动与 KVO 兼容。
  • @bbum 同意这两点,我已经相应地更新了我的答案。
【解决方案2】:

第一个解决方案更好。这就是retain 属性的处理方式。您保留新值,然后释放旧值。此外,如果nil 的情况并不重要,您可以依赖@synthesize 生成的默认实现。

对于第二种解决方案,确实没有必要,而且有点违反惯例。此外,在此解决方案中,您必须先初始化 m_name,然后再为其分配任何字符串。您可以在init 中执行此操作。

- (void) init {
    if (self = [super init]) {
        m_name = [[NSMutableString alloc] init];
    }
}

结论:第一种方案肯定更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多