NSFastEnumeration message sent to deallocated instance

82 views Asked by At

I am trying to implement the NSFastEnumeration protocol for a sqlite query.

I am running into: message sent to deallocated instance

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len {

    // First call
    if(state->state == 0) {
        state->mutationsPtr = &state->extra[0];
        state->state = 1;
        sqlite3_reset(self.statement);
    }

    state->itemsPtr = stackbuf;

    NSUInteger count = 0;
    while (count < len) {
        int result = sqlite3_step(self.statement);

        if (result == SQLITE_DONE) {
            break;
        }

        MyRow *row = [self queryRow];
        stackbuf[count] = row;
        count += 1;
    }

    return count;
}

-(MyRow *) queryRow {
    MyRow * row = // query sqlite for row
    return row;
}

It seems as if the 'row' object is not being retained, so when it needs to be accessed in loop its already been deallocated.

Do I need to save the results when iterating in 'countByEnumeratingWithState' in a'strong' dataset, so that it is retained?

IE:

@property (nonatomic, strong) NSMutableArray *resultList;

Then inside the while loop:

while (count < len) {
    int result = sqlite3_step(self.statement);

    if (result == SQLITE_DONE) {
        break;
    }

    MyRow *row = [self queryRow];
    [self.resultList addObject:row];  // throw into a strong array so its retained
    stackbuf[count] = row;
    count += 1;
}

EDIT:

A little more research reveals that maybe I can just use __autoreleasing:

MyRow * __autoreleasing row = [self queryRow];

Without having to maintain a strong array of objects. Is this the right solution?

1

There are 1 answers

2
CRD On BEST ANSWER

The fast enumeration protocol relies on the collection it is enumerating retaining its contained items. The caller (the compiler) ensures that the collection is itself is retained during the enumeration.

The array used by countByEnumeratingWithState: contains __unsafe_unretained references. This is safe as the compiler retains the collection, the collection retains the items, and so the references in the array will remain valid.

At the language level an object reference returned by fast enumeration is unowned by the caller and must be retained if need, which is of course handled automatically by ARC. This is no different to how items returned from any other collection (arrays, dictionaries, etc.) are handled.

Now your "collection" is different, it does not contain items but obtains them from an SQL query on demand. Those items are not owned by your "collection" and so are deallocated by ARC when there is no longer any strong references to them. Therefore the __unsafe_unretained references you store in fast enumerations C array really are unsafe - ARC deallocates what they reference.

The solution is to add (i.e. instance variable) a standard collection, say an NSMutableArray, to your "collection". On each call to countByEnumeratingWithState: first empty out this collection, thereby discarding any references you hold to previous query results (which will also deallocated them if the calling code has not retained them), and then fill it with the query results that will be return for this call.

When your "collection" is itself finally deallocated by ARC any references to query results it still holds will also be discarded.

It is worthwhile reading Apple's Enumeration Sample as its comments contain details of the memory management required to implement fast enumeration.

HTH