Доступ к области из неправильного потока

Я использую последовательную очередь GCD для работы с областью. Приложение вылетает с Realm accessed from incorrect thread исключением, когда GCD начинает переключать потоки для очереди. Есть ли способ связать данную область с потоком с помощью GCD API?

Вот быстрый пример

self.realmQueue = dispatch_queue_create("db", DISPATCH_QUEUE_SERIAL);

__block RLMRealm *realm = nil;
dispatch_async(self.realmQueue, ^{
    realm = [RLMRealm realmWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp"]];
});

self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.accelerometerUpdateInterval = 0.001;
__block int i = 0;
__block BOOL shouldBeginWriteTransaction = YES;

[self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {

    dispatch_async(self.realmQueue, ^{
        if (shouldBeginWriteTransaction) {
            [realm beginWriteTransaction];
            shouldBeginWriteTransaction = NO;
        }

        AccelerationEvent *event = [[AccelerationEvent alloc] init];
        event.x = accelerometerData.acceleration.x;
        event.y = accelerometerData.acceleration.x;
        event.z = accelerometerData.acceleration.y;
        event.time = [NSDate date];
        [realm addObject:event];

        if (i % 1000) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.xLabel.text = [NSString stringWithFormat:@"%f", event.x];
                self.yLabel.text = [NSString stringWithFormat:@"%f", event.y];
                self.zLabel.text = [NSString stringWithFormat:@"%f", event.z];
            });
        }

        if (i % 10000 == 0) {
            NSDate *startDate = [NSDate date];
            [realm commitWriteTransaction];
            NSLog(@"save time: %f", [[NSDate date] timeIntervalSinceDate:startDate]);
            shouldBeginWriteTransaction = YES;
        }

        i++;
    });
}];

person Maxim    schedule 04.09.2014    source источник


Ответы (1)


Из документов Realm: RLMRealm объекты не являются потокобезопасными и не могут использоваться совместно между потоками, поэтому вы должны получить экземпляр RLMRealm в каждом потоке / dispatch_queue, в котором вы хотите читать или писать.

Также из документов Realm: Объекты RLMRealm кэшируются внутри Realm, и вызов этого метода несколько раз в одном потоке в рамках одной итерации цикла выполнения обычно возвращает тот же объект RLMRealm.

Зная это, я изменил ваш пример кода, чтобы получить RLMRealm непосредственно из блока dispatch_async, в котором он используется, без снижения производительности, поскольку он кэширован.

Я также заметил, что AccelerationEvent передается по потокам, что также недопустимо. Таким образом, этот модифицированный образец кода вместо этого передает NSStrings по потокам.

self.realmQueue = dispatch_queue_create("db", DISPATCH_QUEUE_SERIAL);

self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.accelerometerUpdateInterval = 0.001;
__block int i = 0;
__block BOOL shouldBeginWriteTransaction = YES;

[self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {

    dispatch_async(self.realmQueue, ^{
        RLMRealm *realm = [RLMRealm realmWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"temp"]];
        if (shouldBeginWriteTransaction) {
            [realm beginWriteTransaction];
            shouldBeginWriteTransaction = NO;
        }

        AccelerationEvent *event = [[AccelerationEvent alloc] init];
        event.x = accelerometerData.acceleration.x;
        event.y = accelerometerData.acceleration.x;
        event.z = accelerometerData.acceleration.y;
        event.time = [NSDate date];
        [realm addObject:event];

        if (i % 1000) {
            NSString *xString = [NSString stringWithFormat:@"%f", event.x];
            NSString *yString = [NSString stringWithFormat:@"%f", event.y];
            NSString *zString = [NSString stringWithFormat:@"%f", event.z];
            dispatch_async(dispatch_get_main_queue(), ^{
                self.xLabel.text = xString;
                self.yLabel.text = yString;
                self.zLabel.text = zString;
            });
        }

        if (i % 10000 == 0) {
            NSDate *startDate = [NSDate date];
            [realm commitWriteTransaction];
            NSLog(@"save time: %f", [[NSDate date] timeIntervalSinceDate:startDate]);
            shouldBeginWriteTransaction = YES;
        }

        i++;
    });
}];

Я не запускал этот код, чтобы убедиться, что он работает, поэтому дайте мне знать, если это все еще не решит проблему.

person jpsim    schedule 04.09.2014
comment
@jpsim из того, что вы сказали, я думал, что только RLMRealm не является потокобезопасным, оказалось, что все подклассы RLMObject, полученные из этой области, тоже не являются потокобезопасными - person onmyway133; 21.07.2015
comment
Из документации Realm: вы можете использовать объект только в потоке, в котором он был создан, и вы не можете напрямую обращаться к его ivars для любых сохраненных свойств. realm.io/docs/objc/latest/#models - person jpsim; 22.07.2015