【问题标题】:Objective C ARC NSMutableArray - returned from function - not released?Objective C ARC NSMutableArray - 从函数返回 - 未发布?
【发布时间】:2011-11-18 23:19:18
【问题描述】:

我已经阅读了很多帖子,并且对为什么这是一个问题感到困惑 - ARC 不应该处理这个问题吗?

利用下面的回复,我能够将问题隔离为“Moves”作为罪魁祸首——它似乎永远不会被释放并继续消耗内存,即使它被设置为 nil。 Moves 设置在 Grid.m 底部的两个位置 - 方法 FindRandomMove 并在 recursiveProcess 例程中调用。

一般结构是由列组成的网格,由单元组成。单元格值是随机生成的以制作拼图。多次这样做是为了测试谜题解决方案。

程序首先在 Grid.m 中运行 generateTest

(如果我做的事情没有遵循最佳实践,或者有更好/正确的方法,我感谢代码任何部分的所有 cmets。

完整的测试代码如下:

Cell.h
@interface Cell : NSObject {

CFMutableBitVectorRef _validNumbersBitVector;
NSUInteger _answerNumber; 
UIColor * _color;
NSUInteger _cellRow;
NSUInteger _cellCol;
}   

@property CFMutableBitVectorRef validNumbersBitVector;
@property NSUInteger answerNumber; 
@property (nonatomic, strong) UIColor * color;
@property NSUInteger cellRow;
@property NSUInteger cellCol;

//设置方法

- (void) setValid:         (BOOL)valid   number:(NSUInteger)numberToChange;
- (void) setAllNumbersToValidState:(BOOL) valid;

// get methods

- (BOOL) numberIsValid: (NSUInteger)numberToCheck;
- (NSUInteger) getRandomValidNumber;
- (NSUInteger) validNumberCount;

@end

Cell.m

#import "Cell.h"

#define RANGE CFRangeMake(0,9)

@implementation Cell

@synthesize validNumbersBitVector = _validNumbersBitVector;
@synthesize answerNumber = _answerNumber; 
@synthesize color = _color;
@synthesize cellRow = _cellRow;
@synthesize cellCol = _cellCol;

-(id) init {

    if ((self = [super init])){

        _answerNumber = 0;
        _validNumbersBitVector =CFBitVectorCreateMutable(NULL,10);
    }
    return self;
}

- (void) setValid:(BOOL)valid number:(NSUInteger)numberToChange {

    CFBitVectorSetBitAtIndex(self.validNumbersBitVector, numberToChange - 1, !valid);
}

-(void) setAllNumbersToValidState:(BOOL) valid{

    CFBitVectorSetBits(self.validNumbersBitVector, RANGE, !valid);
}

-(NSUInteger) validNumberCount{

    return CFBitVectorGetCountOfBit(self.validNumbersBitVector, RANGE, 0);
}

- (BOOL) numberIsValid: (NSUInteger)numberToCheck{

     return !CFBitVectorGetBitAtIndex(self.validNumbersBitVector, numberToCheck - 1);
}

- (NSUInteger) getRandomValidNumber{

    NSUInteger pos;
    NSUInteger counter = 0;
    NSUInteger number = 0;
    NSUInteger validCount = 0;

    validCount = CFBitVectorGetCountOfBit(self.validNumbersBitVector, RANGE, 0);

    if (validCount > 0) {
        pos = (rand() %(validCount)) + 1;

        while (counter < pos){
            number ++;
            if (!CFBitVectorGetBitAtIndex(self.validNumbersBitVector, number - 1) ) {
                counter ++;
            }
        }
    }
    return number;
}    
@end

Moves.h

#import "Cell.h"

@interface Moves : NSObject {       
    NSUInteger _row;
    NSUInteger _col;
    NSUInteger _newValue;
    Cell     * _cell;
}   
@property NSUInteger row;
@property NSUInteger col;
@property NSUInteger newValue;
@property (nonatomic,retain) Cell     * cell;

@end

移动.m

#import "Moves.h"

@implementation Moves

@synthesize row = _row;
@synthesize col = _col;
@synthesize newValue = _newValue;
@synthesize cell =  _cell;

-(id) init {
    if ((self = [super init])) {
        _row = 0;
        _col = 0;
        _newValue = 0;
    }

    return self;
}

@end

网格.h

#import "Col.h"
#import "Moves.h"

@interface Grid : NSObject {

NSMutableArray* _rowData;
    NSMutableArray* _emptyCells;
    BOOL _puzzleSolved;
BOOL _bruteForceEnd;    
}
@property (nonatomic, retain) NSMutableArray * rowData;
@property (nonatomic, retain) NSMutableArray * emptyCells;
@property BOOL puzzleSolved;
@property BOOL bruteForceEnd;

- (void) generateTest;
- (void) initializePuzzle;
- (Cell *) cellAtRow:(NSUInteger) pRow andCol:(NSUInteger) pCol;
- (void) setAnswerNumber:(NSUInteger)newAnswerNumber cell:(Cell *) pCell;
- (Moves*) FindRandomMove;
- (NSUInteger) recursiveProcess:(NSUInteger)BFCount;

@end

网格.m

#import "Grid.h"
#import <QuartzCore/QuartzCore.h>

@implementation Grid 

@synthesize rowData = _rowData;
@synthesize emptyCells = _emptyCells;
@synthesize puzzleSolved = _puzzleSolved;
@synthesize bruteForceEnd = _bruteForceEnd;

- (id) init 
{
if ((self = [super init])) {

    _rowData = [[NSMutableArray alloc] initWithCapacity:10];
    _emptyCells = [[NSMutableArray alloc] init];

    NSUInteger i;

    for (i = 0; i<9; i++) {
        Col *tmpCol = [[Col alloc] initWithRow:i + 1];
        [_rowData addObject:tmpCol];
     }
}
return self;
}

- (void) generateTest {

for (int mloop2 = 1; mloop2 < 10000;mloop2 ++) {       
    for (int mloop = 0; mloop < 1000; mloop ++) {
        [self initializePuzzle];        
        [self recursiveProcess:0];
    }
}
}

- (void) initializePuzzle{

self.bruteForceEnd = NO;
[self.emptyCells removeAllObjects];

for (Col * tmpCol in self.rowData){
    for (Cell * answerCell in tmpCol.colData){
        answerCell.answerNumber = 0;
        [self.emptyCells addObject:answerCell];
    }
}
}

- (Cell *) cellAtRow:(NSUInteger) pRow andCol:(NSUInteger) pCol {

Cell * tmpCell;
tmpCell = [[self.rowData objectAtIndex:pRow - 1] cellAtCol:pCol];
return tmpCell;
tmpCell = nil;
}


- (void) setAnswerNumber:(NSUInteger)newAnswerNumber cell:(Cell *) pCell{

if (pCell.answerNumber != newAnswerNumber) {
    if (newAnswerNumber == 0 ) [self.emptyCells addObject: pCell];
    if (pCell.answerNumber == 0) [self.emptyCells removeObject:pCell]; 
    pCell.answerNumber = newAnswerNumber; 
}    
pCell = nil;
}

- (NSUInteger) recursiveProcess:(NSUInteger)BFCount{

Moves * nextMove; 
Cell * answerCell;
NSUInteger counter;
NSUInteger number;
NSUInteger row;
NSUInteger col;

BFCount ++;
nextMove = [self FindRandomMove];
number = nextMove.newValue;
row = nextMove.row;
col = nextMove.col;
nextMove = nil;

answerCell = [self cellAtRow:row andCol:col];
[self setAnswerNumber:number cell:answerCell];
answerCell = nil;

if (BFCount > 48) self.bruteForceEnd=YES; 
if (self.puzzleSolved) self.bruteForceEnd = YES;
if (self.bruteForceEnd) return BFCount;
counter = [self recursiveProcess:BFCount]; // try again
if (counter > 50) NSLog(@"brute force 50");
return BFCount;
}

- (Moves *) FindRandomMove{

Moves * tMove =[[Moves alloc] init]; 
NSUInteger col = 0;
NSUInteger row = 0;
NSUInteger possibleCount = 0;
NSMutableArray * possibleCells = [[NSMutableArray alloc] initWithCapacity:82];
NSUInteger selectedPossible;
NSUInteger numberToUse = 0;
Cell * answerCell ;

NSUInteger validCount = 0;

for ( Cell * tmpCell in self.emptyCells){

    validCount=tmpCell.validNumberCount;       
    [possibleCells addObject:tmpCell];
    possibleCount ++;                   

}

if (possibleCount != 0){
    selectedPossible = 0;       

    if (possibleCount > 1) selectedPossible = rand() % (possibleCount - 1);

    answerCell = [possibleCells objectAtIndex:selectedPossible];

    row = answerCell.cellRow;
    col = answerCell.cellCol;
    numberToUse = answerCell.getRandomValidNumber;
    answerCell = nil;

}

possibleCells = nil;
tMove.row = row;
tMove.col = col;
tMove.newValue = numberToUse;

return tMove;
}


@end

【问题讨论】:

    标签: objective-c cocoa-touch nsmutablearray automatic-ref-counting


    【解决方案1】:

    基于 POST 更新代码的新答案:

    整个实现相当复杂。我将把我的答案专门限制在内存管理问题上。在generateTest 中,您最终会调用initializePuzzle 10,000,000 次,并且在每次迭代中recursiveProcess 最多可以调用49 次。因此,您最多分配了 490,000,000 次 Moves 对象。您的问题不是对象没有被释放,而是您的运行循环正在分配大量对象,这些对象在运行循环结束之前不会被释放。对您的测试代码的快速修复是执行以下操作:

    - (NSUInteger)recursiveProcess:(NSUInteger)BFCount
    {
        @autoreleasepool {
            // your code here
        }
    }
    

    坦率地说,我会重写它以简化逻辑并消除递归。此外,如果您必须执行多次迭代的操作,您应该在后台执行此处理并允许您的 UI 线程保持畅通。这允许用户在不等待的情况下执行操作(例如取消游戏、重新启动等)。

    原始答案:

    从您发布的代码中,我无法判断 cellObjects 的上下文以及在您致电 [cell objectsRemoveAllObjects] 后它发生了什么。如果您完成了数组而不是删除对象,只需执行cellObjects = nil

    正如@Jesse 所说,您应该确保您没有创建保留周期。如果您的任何处理是在后台发生或使用可能导致保留周期的块分派,具体取决于您分配/引用对象的方式。例如,在调度的块中引用 self。

    在编译时,ARC 将确保正确释放孤立对象(例如,没有其他对象对它们有强引用)。

    如果您没有发现问题,您可以发布更多代码,显示未发布的数组的完整生命周期。除了创建、分配或清除数组或引用数组的对象的任何代码之外,处理的细节并不重要。

    【讨论】:

    • 感谢您的回复。我会尝试零!
    • 关于您对自己的评论的问题。 Cell.h 我定义了接口属性_xxxx。然后使用没有_的属性。前 NSUinteger _cellRow;属性 NSUInteger cellRow;在 Cell.m - 合成 cellRow = _cellRow;然后我在 Cell.m 的函数中使用 self.cellRow。 (我唯一使用 _cellRow 的时间是在 init 中);这是正确的吗?
    • 您可以在任何地方使用 iVar。如果您定义了一个属性,您可以使用self.propertyName。使用 getter/setter(例如 self.propertyName)意味着调用了 setter/getter 函数。合成版本是根据您的属性定义生成的。当您使用 iVar 时,您将直接分配您想要的任何值,如果不在 ARC 下,您需要确保释放旧值(如有必要)并保留新值(如有必要)。需要什么取决于数据类型以及您希望 iVar/属性如何工作。
    • 好吧,我希望不必这样做。但我被困住了。我创建了一个全新的项目,删除了大部分代码以尝试隔离问题。我确实隔离了问题(感谢您的提示),但我仍然不确定它为什么会发生 - 分析器/分配显示“移动”是罪魁祸首。不断增长。 Moves 在两个地方创建 - findRandomMove 和递归例程 -recursiveProcess。使用后我已将其设置为 Nil - 如建议的那样,但我必须遗漏一些东西。抱歉所有代码即将到来..基本上 - 结构是一个网格 - 包含包含单元格的列
    • 非常感谢 - 是的,这段代码很复杂 - 大循环仅用于模拟目的 - 通常只会被调用几次 - 但这会夸大问题并更容易识别我的内存管理错误...
    【解决方案2】:

    我猜你有一个保留循环。在这种情况下,也许其中一个单元格持有对“cellObjects”子集的引用?如果一个单元格保留了子集数组,该子集数组保留了该单元格,则不会释放任何内容。

    如果是这种情况(或者如果一个细胞抓住了一些东西,而这个东西又抓住了……抓住了细胞子集的东西),那么你将不得不以某种方式打破这个链条;可能是通过取消一些你已经完成的事情,或者做一个 __weak 指针。

    保留循环不会显示为泄漏,但如果您使用堆工具,它会告诉您可能保留数组的原因。

    【讨论】:

    • 感谢堆仪器的提示。我会做更多的分析并回帖。我怀疑这是一个保留循环。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多