Ios 如何正确地解除锁定并释放while循环中引用的对象

Ios 如何正确地解除锁定并释放while循环中引用的对象,ios,objective-c,memory-leaks,Ios,Objective C,Memory Leaks,以下代码创建内存泄漏。一个异步后台进程下载tmp_pack_文件夹中的图像,另一个后台线程正在检查图像计数是否与预期的总计数匹配,然后在下载完成后将图像提供给用户 问题是,如果将图像下载到tmp\u pack\u文件夹的后台进程由于某种原因失败,那么下面的代码将变成一个无限循环。这是一种罕见的情况,但一旦出现这种情况,就会出现内存泄漏getAllFileNamesinFolder方法实际上正在调用NSFileManager的目录AtPath:bundleRoot的内容,并且会重复调用它。在这种情

以下代码创建内存泄漏。一个异步后台进程下载
tmp_pack_文件夹中的图像
,另一个后台线程正在检查图像计数是否与预期的总计数匹配,然后在下载完成后将图像提供给用户

问题是,如果将图像下载到
tmp\u pack\u文件夹
的后台进程由于某种原因失败,那么下面的代码将变成一个无限循环。这是一种罕见的情况,但一旦出现这种情况,就会出现内存泄漏
getAllFileNamesinFolder
方法实际上正在调用
NSFileManager
的目录AtPath:bundleRoot的
内容,并且会重复调用它。在这种情况下,如何正确释放内存(除了防止无限循环)


你说你将修改它以“防止无限循环”。你应该更进一步,完全消除循环。如果您发现自己有循环、轮询某些状态的代码,那么肯定会有另一种更有效的设计。总之,你的记忆状况不是真正的问题:它只是一个更广泛的设计问题的症状

我建议您采用事件驱动的方法。因此,与重复执行“我完成了吗”逻辑的方法不同,您应该只在由适当的事件触发时检查此状态(即仅当下载完成/失败时,而不是之前)。这个循环可能会导致内存问题,所以不要修复内存问题,而是完全消除这个循环

在回答您的问题时,内存问题的一个可能来源是自动释放对象。这些对象的分配方式是,当您使用完这些对象后,它们不会立即释放,而是仅当自动释放池耗尽时才会释放(这通常在您返回应用程序的运行循环时自动发生)。但是,如果您有一些重复调用的大型循环,那么最终会将大量对象添加到自动释放池中,而这些对象并没有及时地被释放

在某些特殊情况下,如果您确实需要一些循环(明确地说,这里不是这种情况;在这种情况下,您既不需要也不想要循环),您可以使用自己的自定义
@autoreleasepool
,通过它您可以有效地控制池的排水频率


但是,冒着让人厌烦的风险,这根本不是那种情况。不要使用自己的自动释放池。摆脱循环。只有在下载完成/失败时才触发“我完成了吗”逻辑,您的问题应该会消失。

太糟糕了,Objective-C没有给我们类似javascript的承诺。解决此问题的方法是为异步任务提供调用方接口,如下所示:

- (void)doAsynchThingWithParams:(id)params completion:(void (^)(id))completion;
params参数化任务,完成处理程序获取任务的结果

让我们把几个并发任务当作一个待办事项列表来处理,一个完成处理程序在所有结果到达后被调用

// array is an array of params for each task e.g. urls for making url requests
// completion is called when all are complete with an array of results

- (void)doManyThingsWithParams:(NSArray *)array completion:(void (^)(NSArray *))completion {

    NSMutableArray *todoList = [array mutableCopy];
    NSMutableArray *results = [NSMutableArray array];

    // results will always have N elements, one for each task
    // nulls can be replaced by either good results or NSErrors
    for (int i=0; i<array.count; ++i) results[i] = [NSNull null];

    for (id params in array) {
        [self doAsynchThingWithParams:params completion:^(id result) {
            if (result) {
                NSInteger index = [array indexOfObject:params];
                [results replaceObjectAtIndex:index withObject:result];
            }
            [todoList removeObject:params];
            if (!todoList.count) completion(results);
        }];
    }
}
//数组是每个任务的参数数组,例如用于发出url请求的url
//当所有结果都已完成时,将调用completion
-(void)doManyThingsWithParams:(NSArray*)数组完成:(void(^)(NSArray*)完成{
NSMutableArray*todoList=[array mutableCopy];
NSMutableArray*结果=[NSMutableArray];
//结果总是有N个元素,每个任务一个元素
//空值可以替换为良好结果或N错误

对于(int i=0;i为什么不首先阻止无限循环?整个模式似乎设计得很糟糕。为什么不在后台线程中进行所有下载,然后在完成/失败时调用一个块。然后主线程可以坐在那里不做任何事情。它们已经是一个后台线程,一次下载一个图像成功下载后,它会增加视图控制器本地作用域的计数器部分。一旦用户单击按钮,应用程序不会启动新的下载,但会检查以静默方式下载图像的后台过程是否完成(通过计数器)。除了我可以在第一个后台进程中保存的计数器外,我没有办法知道所有的图像何时下载。有意义吗?我会阻止无限循环,但作为一些想了解iOS内存管理的人。我还想知道什么是处理包的正确方法。按照标准惯例,第上面的e不应该造成永久丢失存储的实际泄漏。但是,“规则”没有指定
getAllFileNamesInFolder
是返回一个自动删除的对象,还是仅仅返回一个“外部拥有”的对象——我们只知道它没有被强烈保留。如果它被自动删除,那么多个副本将“累积”在循环中,直到循环退出并控制达到自动释放池边界。对于小循环,这不是什么大问题,但在您的情况下,它会累积起来。在“guts”周围添加一个
@autorelease{…}
我已经开始将应用程序修改为事件驱动,但是上面的内容帮助我理解内存泄漏背后的原因。谢谢Rob。
// array is an array of params for each task e.g. urls for making url requests
// completion is called when all are complete with an array of results

- (void)doManyThingsWithParams:(NSArray *)array completion:(void (^)(NSArray *))completion {

    NSMutableArray *todoList = [array mutableCopy];
    NSMutableArray *results = [NSMutableArray array];

    // results will always have N elements, one for each task
    // nulls can be replaced by either good results or NSErrors
    for (int i=0; i<array.count; ++i) results[i] = [NSNull null];

    for (id params in array) {
        [self doAsynchThingWithParams:params completion:^(id result) {
            if (result) {
                NSInteger index = [array indexOfObject:params];
                [results replaceObjectAtIndex:index withObject:result];
            }
            [todoList removeObject:params];
            if (!todoList.count) completion(results);
        }];
    }
}