ARC 已启用但存在内存泄漏 (Objective C)

ARC is enabled but having Memory Leak (Objective C)

如您所见,下面的代码除了枚举一组文件外并没有做太多(全部注释掉),但是,在 运行 40 秒后,我的内存使用量增长到超过 2 GB通过按 UI.

上的按钮启动下面的功能

我可以运行UI几个小时,在按下按钮之前,内存使用不超过8MB。

鉴于 ARC 已打开,内存中保存的是什么?

removed original code as the edit below made no differance.

编辑:

尝试 @autoreleasepool{ dispatch_asyny ... } 并在 while 循环周围和 while 循环内进行排列,但没有效果。

这是添加并清理了 autorelasepool 的代码

-(void) search{

    self.dict = [[NSMutableDictionary alloc] init];
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/SeaWall.log"];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString *bundleRoot = @"/";
        NSFileManager *manager = [NSFileManager defaultManager];
        NSDirectoryEnumerator *direnum = [manager enumeratorAtPath:bundleRoot];
        NSString *filename;

        while ((filename =  [NSString stringWithFormat:@"/%@", [direnum nextObject]] ) && !self.exit) {
            @autoreleasepool {

                NSString *ext = filename.pathExtension;

                if ([ext hasSuffix:@"so"] || [ext hasSuffix:@"dylib"] ) {
                    if (filename == nil || [NSURL URLWithString:filename] == nil) {
                        continue;
                    }

                    NSData *nsData = [NSData dataWithContentsOfFile:filename];
                    if (nsData != nil){
                        NSString *str = [nsData MD5];
                        nsData = nil;

                        [self writeToLogFile:[NSString stringWithFormat:@"%@ - %@", [filename lastPathComponent], str]];

                    }
                }
                ext = nil;
            } // end autoreleasepool
        }
        [fileHandle closeFile];
        [self ControlButtonAction:nil];


    });
}

内存并没有完全泄漏:它非常适合释放,但从来没有机会释放。

ARC 建立在 Objective-C 的手动内存管理规则之上。基本规则是“调用 init 的 object/function 拥有新实例”,所有者必须 release 对象不再需要它时。

这是创建对象的便捷方法的问题,例如 [NSData dataWithContentsOfFile:]。该规则意味着 NSData class 拥有该实例,因为它调用了 init。一旦返回值,class 将不再需要该对象,并且需要释放它。但是,如果这发生在被调用者有机会保留实例之前,它就会在任何事情有机会发生之前消失。

为了解决这个问题,Cocoa引入了autorelease方法。此方法将对象的所有权转移到最后设置的自动释放池。当您退出其范围时,自动释放池将被“耗尽”。

Cocoa/AppKit/UIKit 会围绕事件处理程序自动设置自动释放池,因此您通常不需要为此担心。然而,如果你有一个 long-运行 方法,这就会成为一个问题。

您可以使用 @autoreleasepool 语句声明一个自动释放池:

@autoreleasepool
{
    // code here
}

在右括号处,自动释放池收集的对象被释放(如果没有其他人引用它们,则可能被释放)。

因此您需要在此语句中包装循环体。

这是一个例子。此代码在我的计算机上每秒“泄漏”大约 10 兆字节,因为执行永远不会离开 @autoreleasepool 范围:

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        while (true)
        {
            NSString* path = [NSString stringWithFormat:@"%s", argv[0]];
            [NSData dataWithContentsOfFile:path];
        }
    }
}

另一方面,内存使用保持稳定,因为执行在每次循环迭代结束时离开 @autoreleasepool 范围:

int main(int argc, const char * argv[])
{
    while (true)
    {
        @autoreleasepool
        {
            NSString* path = [NSString stringWithFormat:@"%s", argv[0]];
            [NSData dataWithContentsOfFile:path];
        }
    }
}

在循环条件中创建对象对于长循环来说很尴尬,因为它们不会被内部 @autoreleasepool 拾取。您还需要将它们放在 @autoreleasepool 范围内。

返回

每当我们return一个对象(可能是Swift),我们需要注册到最近的@autoreleasepool块(通过调用autorelease方法来防止内存泄漏,根据 ownership-rules),但现在 ARC 会自动为我们完成;

每当 ARC 禁用时;使用 alloc and/or init 后,手动调用 autorelease,例如:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

内存需要由自动释放池释放。 否则它会像你正在经历的那样被锁住并且它会泄漏。

在你的循环中输入:

@autoreleasepool { /* BODY */ }