2

我正在实现键值验证,如下所示:

- (BOOL)validateValue:(__autoreleasing id *)value forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
    NSAttributeDescription *attribute = [[self.entity attributesByName] objectForKey:key];
    if (attribute && attribute.attributeType == NSStringAttributeType) {
        *value = [NSString stringWithFormat:@"%@ modified", *value];
        return YES;
    }
    return [super validateValue:value forKey:key error:error];
}

当它运行时,内存会不断增加,直到应用程序收到内存警告并在 250 MB 标记处崩溃。

有趣的是,通过以下变化,总内存占用保持稳定在 5 MB 左右。

*value = [NSString stringWithFormat:@"%@", *value];
*value = [NSString stringWithFormat:@"%@ modified", NSStringFromClass([self class])];

对于第一个变体,我怀疑 +stringWithFormat 被优化为简单地返回 *value 不变。

第二个变体只是我试图确保编译器或目标 c 运行时不会返回已经存在的对象而不分配任何新对象。

只有当我创建一个包含传入值的新字符串对象时,内存才会增加。

我试过做

NSString * __unsafe_unretained tmp = *value;
*value = [NSString stringWithFormat:@"%@ modified", tmp];

CFStringRef tmp = (__bridge_retained CFStringRef)*value;
*value = [NSString stringWithFormat:@"%@ modified", tmp];
CFRelease(tmp);

无济于事。

我究竟做错了什么?

4

1 回答 1

0

顺便提一下,注意你不需要写(__autoreleasing id *); 光写(id *)就够了。ARC 假定__autoreleasing默认情况下指向对象指针的指针。事实上,Apple 的 Key-Value Validation 文档中就只有(id *)这个参数。但这绝对与您的内存泄漏无关。

你能发布一个完整的、可编译的例子吗?(Pastebin 链接可能是执行此操作的适当方式。)

  • 如果您将 Cocoa 退出循环并直接调用您的-validateValue:forKey:error:方法几百万次,内存泄漏是否仍然会重现?

  • 如果替换为 ,内存泄漏是否仍会return [super ...]重现return YES;

对于第一个变体,我怀疑 +stringWithFormat 被优化为简单地返回 *value 不变。

这很容易检查,你知道吗?做吧

id oldValue = *value;
*value = [NSString stringWithFormat:@"%@", *value];
assert(*value == oldValue);  // TESTING -- is the value unchanged?

事实上,Apple 对非可变NSString实例的实习(重复数据删除)意味着您的猜测是正确的。包含相同字符数据的非可变NSStrings 通常会比较指针相等。(当然你不应该依赖这个怪癖来编写代码!)

就个人而言,我的钱完全没有泄漏,但只是很多(递归?)调用 -validateValue:forKey:error:. 看,每次调用该函数时,它都会采用任何字符串作为值并将其扩展为strlen(" modified")字节。因此,第一次验证@"foo"时,您会得到@"foo modified";当你确认得到了@"foo modified modified";当你确认得到了@"foo modified modified modified";最终你的程序内存不足并崩溃。

尝试这个:

- (BOOL)validateValue:(id *)value forKey:(NSString *)key error:(NSError **)error
{
    NSAttributeDescription *attribute = [[self.entity attributesByName] objectForKey:key];
    if (attribute && attribute.attributeType == NSStringAttributeType) {
        if ([*value hasSuffix: @" modified"]) {
            NSLog(@"Uh-oh! I refuse to modify an already modified value!");
        } else {
            *value = [NSString stringWithFormat:@"%@ modified", *value];
        }
        return YES;
    }
    return [super validateValue:value forKey:key error:error];
}

这是否解决了您的内存泄漏?

于 2013-06-19T01:10:32.663 回答