NSMutableString作为保留/复制

tes*_*dtv 18 iphone cocoa-touch copy objective-c

我的应用程序中有很多NSMutableString(差不多10-11); 全部定义为ivar/property

@property (nonatomic, retain) NSMutableString *str1;
Run Code Online (Sandbox Code Playgroud)

我在某处读到,最好对字符串使用"copy".真的吗?如果是,我可以在我的应用程序中替换retain复制并删除dealloc中的版本吗?

我还需要考虑其他一些事情吗?

此外,在1个应用程序中拥有10-11 NSMutableString是正常的吗?我的意思是从内存使用角度来看?我的应用程序中也有4-5个NSMutableDictionary.如果没问题,请告诉我.

Abi*_*ern 27

虽然Taskinoor的答案是正确的,但我想补充一点解释.

建议使用副本作为具有可变/不可变对的类集群的一部分; 比如NSString/ NSMutableString NSArray/ NSMutableArray NSDictionary/ NSMutableDictionary NSSet/NSMutableSet

这样做的原因是可以将一个属性声明为不可变类型(如NSString),然后将其传递给一个可变类型(如NSMutableString).在这种情况下,可以像Taskinoor描述的那样在类外部更改属性.

使用copy建议,因为它与类簇理智的行为.发送copy到一个可变类返回对象(即发送的不可变拷贝copy消息到NSMutableString返回一个NSString).但是,向copy不可变对应方发送相当于向其发送retain消息.

  • @rakeshNS你所做的是将`mutableCopy`消息发送到一个可变字符串,该字符串返回一个可变字符串的可变副本. (2认同)

jus*_*tin 27

(注意:NSMutableString特定的响应是一种方式)

我的应用程序中有很多NSMutableString(差不多10-11); 全部定义为ivar/property

@property (nonatomic, retain) NSMutableString *str1; 我在某处读到,最好对字符串使用"copy".真的吗?

是的,复制NSString(还没有谈到NSMutableString)应该是默认值.原因(如其他海报所述)是不可变实例的实现可以简单地retain在其实现中实现copyWithZone:.另外,copy给你预期的行为.换句话说,如果字符串实际上是不可变的,则没有用于保留字符串的用法.为了保证您处理不可变的字符串,您只需在创建时进行复制并进行设置.

如果是,我可以在我的应用程序中替换retain复制并删除dealloc中的版本吗?

copy,就像retain返回一个必须在非垃圾收集环境中明确释放(例如,在dealloc中)的对象.不可变字符串仍然是引用计数的.这有例外,特别是:

  • CF/NS-String文字存在于程序的持续时间内

  • 您可以使用CF-API创建从未发布的字符串,或者管理超出传统保留/释放机制范围的字符串

  • NS-Type子类可以实现另一种方案(虽然通常考虑这个有缺陷)

我还需要考虑其他一些事情吗?

现在我们来看看NSMutable类型的细节.属性语法的实现调用copy.我们知道,-[NSMutableString copy]返回一个不可变的NSString.这是一个等待天真实现的问题,因为你的NSMutableString ivar现在是一个NSString,使用默认合成的setter实现.

@property (nonatomic, retain) NSMutableString *str1;`
// or
@property (nonatomic, copy) NSMutableString *str1;`
Run Code Online (Sandbox Code Playgroud)

宣布时:

 @property (nonatomic, retain) NSMutableString *str1;`
Run Code Online (Sandbox Code Playgroud)

您可以使用默认的合成实现.

但声明时有一个转折:

 @property (nonatomic, copy) NSMutableString *str1;`
Run Code Online (Sandbox Code Playgroud)

你应该使用默认的合成的实现.相反,你应该写自己的:

- (void)setStr1:(NSMutableString *)arg
{
    /* locking/observing/undo/etc omitted */
    NSMutableString * copy = [arg mutableCopy];
    NSMutableString * prev = str1;
    str1 = copy;
    [prev release];
}

- (NSMutableString *)str1
{
  /* locking/observing/etc omitted */
  /* don't return something the clients thinks they may
     possibly modify, and don't return something you may
     modify behind their back
  */
    return [[str1 mutableCopy] autorelease];
  // -- or???
    return [[str1 retain] autorelease];
}
Run Code Online (Sandbox Code Playgroud)

嗯......这不是很清楚,也不是很贴心客户.它还意味着许多不必要的东西,并引入了额外的复制开销.让我们重新评估一下.

此时的其他注意事项/问题:

  • default-setter要求客户端生成字符串的mutableCopy,如果它们只包含一个不可变的字符串.你可以在setter中立即将它变成一个独特的副本,因为你不能指望客户只为你创建一个独特的mutableCopy - 此外,这是类实现的责任.所以NSMutableString是setter的一个坏参数.NSString是合适的setter,除非你保留字符串.

  • NSMutableString也是一个模糊的getter - 请参阅实现.它复制+自动释放还是保留+自动释放?一般形式没有标准期望.此外,客户可能依赖于一种特定的行为.这真的是类的实现细节,除非你正在处理紧耦合的对象.紧密耦合的对象是基本上相互依赖的对象,并且通常在其他情况下不可重用.这个概念很好,你可能只是因为几个原因而使用私人课程.在任何一种情况下,对外部客户端和子类的复制/写入语义都不是(隐式)清除,并且实现是危险的.

  • 共享NSMutableString通常是一个糟糕的设计.NSMutableString不是线程安全的.客户端访问和改变字符串是没有意义的 - 这很危险.任何非线程安全共享对象的设计都应谨慎考虑.在这种情况下共享也扩展到子类使用(所以@private尽可能使它)

  • retain通常也是一个糟糕的设计 - 客户端不会知道字符串何时(或正在)被外部修改,除非您添加等效的外部代码来支持它.

  • 根据接口的使用方式,这也会引入大量不必要的复制.嘘.

好的 - 现在我们非常确信在大多数情况下我们的'改进'仍然是一个坏主意=)我们如何改进设计?

除了紧密耦合对象的特殊情况之外,您应该声明/实现您的属性,如下所示:

@interface MONThing : NSObject
{
@private
    NSMutableString * str1;
}

/* note: passes/returns NSString */
@property (nonatomic, copy) NSString * str1;

@end

@implementation MONThing

// no need for clients to create a mutableCopy
- (void)setStr1:(NSString *)arg
{
  /* locking/observing/undo/etc omitted */
    NSMutableString * copy = [arg mutableCopy];
    NSMutableString * prev = str1;
    str1 = copy;
    [prev release];
}

// the result is clearly defined. return a copy for
// thread-safety, expected behavior, and to minimize
// further copies when handling the result.
- (NSString *)str1
{
  /* locking/observing/etc omitted */
  /* don't return something the clients thinks they
     may possibly modify, and don't return something
     you may modify behind their back
  */
    return [[str1 copy] autorelease];
}

@end
Run Code Online (Sandbox Code Playgroud)

您的界面现在得到了改进,更加正确,需要的文档更少,性能更好.

如果您不需要,也可以删除公开可见的访问者.

我们可以在需要的地方使用该界面.

但还有更多!事实证明,界面比许多人想象的更少.在大多数情况下,我们可以通过简单地避免在可能的情况下使用NSMutableString作为ivar来避免许多问题.

如前所述,NSMutableString不是线程安全的,在你的字符串很小或不经常更改的情况下,在许多情况下使用(复制的)NSString ivar会更容易和更有效.通过将可变字符串添加到接口(如上所述),您必须手动保证线程安全.在许多情况下,沿着这些方向做某事是最佳的:

@interface MONThing : NSObject
{
    NSString * str1;
}

@property (nonatomic, copy) NSString * str1;

@end

@implementation MONThing

@synthesize str1;

- (void)updateTimeElapsed:(NSTimeInterval)seconds
{
    NSMutableString * s = [NSMutableString stringWithFormat:@"%f seconds", seconds];
    /* ...some mutations... */
    self.str1 = s;
}

@end
Run Code Online (Sandbox Code Playgroud)

当然,会有一些例外 - 你需要一个可变的ivar,但最好尽可能使用不可变的ivars,如果有疑问,而不是引入大量的线程复杂性.在大多数情况下,您可以从接口中删除可变字符串的使用,并在必要时构建字符串(如上所示).

另一方面,集合(NSMutableArray,NSMutableDictionary,NSMutableSet等)在实现中更经常需要,并且通常更复杂.

祝好运!

  • +1用于突出显示不可变性的偏好. (3认同)

tas*_*oor 12

对于可变字符串复制和保留有不同的结果.如果你将str1的副本复制到str2,那么对str1的任何更改都不会反映在str2中,并且str2中的任何更改都不会反映在str1中,因为它们是单独的对象.但是如果str1保留为str2,则两者都指向相同的可变字符串对象,并且通过str1的更改将反映在str2中.但NSString不可变.因此,对于简单的字符串(即,不是可变字符串),复制只是增加了引用计数,这意味着它在内部被视为保留(我不是100%确定处理副本,如保留非可变对象,但看到这个所以在一些不同的问题.谷歌搜索,我会编辑,如果我能找到链接.).

如果你使用副本,那么你仍然需要在dealloc中释放它.

具有10 - 11正常大小的字符串应该对内存使用没有负面影响.典型的应用程序可能包含更多字符串.按正常大小我的意思是你没有在字符串中附加100万个字符.那将是一个严重的问题.字符串占用的内存将随着字符串长度的增加而增加.字典也一样.字典占用的内存取决于您在字典中存储的内容.字典本身没有太多开销.您始终可以使用仪器检查应用的内存使用情况.