【问题标题】:How to avoid temp objects when returning NSString under ARCARC下返回NSString时如何避免临时对象
【发布时间】:2012-04-20 16:05:29
【问题描述】:

我有一个具有两个属性的类:

@interface Contact : NSObject {
    NSString *lastname;
    NSString *lastNameUpper;
}

我已经将 lastname 声明为一个属性(并在 .m 文件中合成它):

@property (nonatomic, retain) NSString *lastname;

但是,我想编写自己的方法来访问lastNameUpper,所以我声明了一个方法:

- (NSString *) lastNameUpper;

并像这样实现它:

- (NSString *) lastNameUpper {
    if (!lastNameUpper) {
        lastNameUpper = [lastname uppercaseString];
    }
    return lastNameUpper;
}

这很好用,但是由于它经常被调用,所以会调用很多临时对象。有趣的是,Instruments 显示很多“Malloc (4k)”,每次访问lastNameUpper 时数量都会增加。我还可以看到内存分配在objc_retailAutoreleaseReturnValue

由于在我将项目转换为 ARC 之前它工作正常,我假设我必须对方法签名进行一些 ARC 特定的添加,但我似乎无法使其工作。

有什么建议吗?

【问题讨论】:

  • 这段代码没有任何问题(除了你应该设置属性copy,并在设置姓氏字符串时设置大写字符串)。你在说什么临时对象?它造成了哪些实际问题?
  • @jrturton 用更多细节更新了问题。

标签: objective-c ios nsstring automatic-ref-counting


【解决方案1】:

0:你应该复制你的 NSString 属性:

 @property (nonatomic, copy) NSString * lastname;

我猜返回字符串是通过复制来实现的。

不。 不可变 字符串的副本是保留操作。只需在分析器中运行它,看看这在时间和内存上的成本是多少。此外,在这种情况下没有隐式副本。

更新

我在 Lion-64 上对此进行了测试。 uppercaseString 可能会返回一个 可变 字符串。

为了安全起见,您可以考虑分配uppercaseString 的结果副本:lastNameUpper = [[lastname uppercaseString] copy];。这可能会导致更多或更少的分配,具体取决于您在实现中使用字符串的方式。如果您的属性副本,则每次分配时都会制作一个副本。简单的概括是分配一个副本,其余的通常会自己处理。

测试程序

// ARC enabled
#import <Foundation/Foundation.h>

@interface Contact : NSObject
{
    NSString * lastname;
    NSString * lastNameUpper;
}

@property (nonatomic, copy) NSString *lastname;

@end

@implementation Contact

@synthesize lastname;

- (NSString *) lastNameUpper {
    if (!lastNameUpper) {
        lastNameUpper = [lastname uppercaseString];
    }
    return lastNameUpper;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int n = 0;
        while (n++ < 100000) {
            Contact * c = [Contact new];
            c.lastname = @"skjdhskjdhaksjhadi";
            NSString * lastNameUpper = c.lastNameUpper;
        }
    }

    return 0;
}

【讨论】:

  • 贾斯汀:我更改了代码,但行为仍然相同。我已经更新了我的问题,因为我现在可以看到没有创建临时字符串,只是一个大 malloc。
  • lastname 是否被复制与问题无关。访问lastNameUpper时出现问题,未定义为只读属性。
  • @Thorsten 已更新 - 您可以为此发布您的测试吗?我想知道您是否只是一遍又一遍地将相同的对象添加到自动释放池中……在这种情况下,除非偶尔耗尽,否则池只会不断增长。如果您正在创建新对象并排空,那么您会看到很多不同的结果。
  • @Justin:即使在我的方法中添加副本(在大写字符串之后和/或返回语句中),我也看不到任何更改。
  • 这是我“测试”的方式:我正在使用工具运行我的应用程序进行分配。在那里我可以看到分配了很多内存(使用 malloc 的内存只有 4k 块)并且使用“调用树”我可以看到内存的分配位置。它在我提到的方法中,它要么在 objc_autorelease 中,要么在 objc_retainAutoreleaseReturnValue 系统调用中。我不确定发生了什么事......
【解决方案2】:

覆盖- (void)setLastname:(NSString*)aLastname 方法(由@synthesize lastname 自动创建,并将lastNameUpper 设置为现有方法。

现在创建一个 lastNameUpper 属性(并合成它):

@property (nonatomic, readonly) NSString *lastNameUpper;

因为这将返回 lastNameUpper 实例变量的指针,所以无论何时访问它都不应复制。

【讨论】:

  • 这似乎有效(我正在运行更多测试以确认)。任何人都可以正确解释这种行为吗?
  • 不确定 ARC 如何定义被视为属性的函数(在未定义 @property 时调用 obj.methodName),但它可能会检查返回的对象是否符合 NSCopying 协议并作为副本实现。通过指定 @property 并实现 getter,它现在知道如何具体实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-30
  • 2015-12-14
  • 1970-01-01
相关资源
最近更新 更多