ARC 已启用但有内存泄漏(目标 C) [英] ARC is enabled but having Memory Leak (Objective C)

查看:16
本文介绍了ARC 已启用但有内存泄漏(目标 C)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如您所看到的,下面的代码并没有做太多(全部注释掉)而不是枚举一组文件,但是,在运行下面启动的函数 40 秒后,我的内存使用量增长到超过 2 GB按下 UI 上的按钮.

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

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

删除原始代码,因为下面的编辑没有区别.

尝试了 @autoreleasepool{ dispatch_asyny ... } 以及在 while 和 while 循环内的排列,但没有效果.

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

-(空)搜索{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 *文件名;while ((filename = [NSString stringWithFormat:@"/%@", [direnum nextObject]] ) && !self.exit) {@autoreleasepool {NSString *ext = 文件名.pathExtension;if ([ext hasSuffix:@"so"] || [ext hasSuffix:@"dylib"] ) {if (filename == nil || [NSURL URLWithString:filename] == nil) {继续;}NSData *nsData = [NSData dataWithContentsOfFile:filename];如果(nsData != 零){NSString *str = [nsData MD5];nsData = 零;[self writeToLogFile:[NSString stringWithFormat:@"%@ - %@", [filename lastPathComponent], str]];}}分机 = 零;}//结束自动释放池}[文件句柄关闭文件];[self ControlButtonAction:nil];});}

解决方案

内存并没有完全泄漏:它已经准备好释放了,但它永远没有机会释放.

ARC 建立在 Objective-C 的手动内存管理规则之上.基本规则是调用 init 的对象/函数拥有新实例",所有者必须在不再需要对象时释放对象.>

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

为了解决这个问题,Cocoa 引入了autorelease 方法.此方法将对象的所有权转移到上次设置的自动释放池.自动释放池被耗尽"当你退出他们的范围时.

Cocoa/AppKit/UIKit 会自动围绕事件处理程序设置自动释放池,因此您通常无需担心这一点.但是,如果您有一个长时间运行的方法,这就会成为一个问题.

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

@autoreleasepool{//代码在这里}

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

所以你需要在这个语句中包装你的循环体.

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

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

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

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

在循环条件中创建对象对于长循环来说很笨拙,因为内部 @autoreleasepool 不会选择这些对象.您还需要在 @autoreleasepool 范围内获取这些内容.

返回

每当我们返回一个对象(可能是Swift),我们需要注册到最近的@autoreleasepool块(通过调用autorelease 防止内存泄漏的方法,根据 所有权规则),但现在ARC会自动为我们做这些;

每当 ARC 禁用时;使用alloc和/或init后,手动调用autorelease,如:

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

As you can see, the code below isnt doing much (all commented out) more than enumerating over a set of files, however, my memory usage is growing to over 2 GB after 40 seconds of running the function below which is launched by pressing a button on the UI.

I can run the UI for hours, and before pressing the button, the memory usage does not exceed 8MB.

Given that ARC is turned on, what is holding on to the memory?

removed original code as the edit below made no differance.

EDIT:

Attempted @autoreleasepool{ dispatch_asyny ... } and permutations of that around the while and inside the while loop which had no effect.

Here is the code with autorelasepool added and cleaned up

-(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];


    });
}

解决方案

The memory is not exactly leaked: it is very much ready to be released, but it never has a chance to be.

ARC builds upon the manual memory management rules of Objective-C. The base rule is that "the object/function that calls init owns the new instance", and the owner must release the object when it no longer needs it.

This is a problem for convenience methods that create objects, like [NSData dataWithContentsOfFile:]. The rule means that the NSData class owns the instance, because it called init on it. Once the value will be returned, the class will no longer need the object, and it would need to release it. However, if this happens before the callee gets a chance to retain the instance, it will be gone before anything had a chance to happen.

To solve this problem, Cocoa introduces the autorelease method. This method transfers the ownership of the object to the last autorelease pool that was set up. Autorelease pools are "drained" when you exit their scope.

Cocoa/AppKit/UIKit automatically set up autorelease pools around event handlers, so you generally do not need to worry about that. However, if you have a long-running method, this becomes an issue.

You can declare an autorelease pool using the @autoreleasepool statement:

@autoreleasepool
{
    // code here
}

At the closing bracket, the objects collected by the autorelease pool are released (and possibly deallocated, if no one else has a reference to them).

So you would need to wrap the body of your loop in this statement.

Here's an example. This code "leaks" about 10 megabytes every second on my computer, because the execution never leaves the @autoreleasepool scope:

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

On the other hand, with this, the memory usage stays stable, because execution leaves the @autoreleasepool scope at the end of every loop iteration:

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

Creating objects in the loop condition is awkward for long loops because these are not picked up by the inner @autoreleasepool. You will need to get these inside the @autoreleasepool scope as well.

Returning

Whenever we return an object (maybe to Swift), we need to register into nearest @autoreleasepool block (by calling autorelease method to prevent memory-leak, according to ownership-rules), but nowadays ARC does that automatically for us;

Whenever ARC disabled; after using alloc and/or init, call autorelease manually, like:

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

这篇关于ARC 已启用但有内存泄漏(目标 C)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆