realm-swift
realm-swift copied to clipboard
Deleting a bunch of objects at once may cause a crash in migration
Goals
Delete objects in a migration.
Expected Results
Objects get deleted.
Actual Results
Realm crashes with the following stack trace:
This crash is caused by the following method (array index out-of-bounds):
- (void)deleteObjectsMarkedForDeletion {
for (NSString *className in deletedObjectIndices.allKeys) {
RLMResults *objects = [_realm allObjects:className]; // objects.count == 811 at start, decreasing by one with each deletion as this is a self-updating type
for (NSNumber *index in deletedObjectIndices[className]) {
// here the sort order is random (or implicitly user-defined); this means that lower indices may get deleted before higher ones, causing the crash
RLMObject *object = objects[index.longValue];
[_realm deleteObject:object];
}
}
}
Here's a very simple fixed version that sorts the indices before looping (this fixes the crash, haven't verified that it works semantically correctly):
- (void)deleteObjectsMarkedForDeletion {
for (NSString *className in deletedObjectIndices.allKeys) {
RLMResults *objects = [_realm allObjects:className];
// Delete objects with indices sorted in descending order to prevent array index out-of-bounds crashes.
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO];
NSArray *sortedIndices = [deletedObjectIndices[className] sortedArrayUsingDescriptors:@[sort]];
for (NSNumber *index in sortedIndices) {
RLMObject *object = objects[index.longValue];
[_realm deleteObject:object];
}
}
}
Version of Realm and Tooling
Realm framework version: 3.0.2
Can you please share some code that reproduces the issue so we'll be able to verify that whatever fix we make addresses the problem you're encountering?
This was simple to reproduce. First create some data (doesn't matter what the data is):
class O: Object {
@objc dynamic var i = 0
}
var conf = Realm.Configuration.defaultConfiguration
let realm = try! Realm(configuration: conf)
try! realm.write {
for _ in 0..<1000 {
realm.add(O())
}
}
Then increment version number and implement the following migration block:
conf.schemaVersion = 1
conf.migrationBlock = {
migration, oldSchemaVersion in
var toDelete = [MigrationObject]()
migration.enumerateObjects(ofType: O.className()) {
oldObject, newObject in
toDelete.append(newObject!)
}
// deleting objects in reversed order causes the crash
for object in toDelete.reversed() {
migration.delete(object)
}
}
Thank you!
@anlaital did you find any workaround for this? :)
Maintaining the enumeration order when deleting the objects doesn't crash.
@anlaital 👍🏻👍🏻 Thank you, will try :)