Reputation: 331
I'm using serial GCD queue to work with realm. Application crashes with Realm accessed from incorrect thread
exception when GCD starts to switch threads for the queue. Is there any way to bind given realm with a thread using GCD API?
Here's a quick example
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++;
});
}];
Upvotes: 18
Views: 22640
Reputation: 14409
From Realm docs: RLMRealm
objects are not thread safe and cannot be shared across threads, so you must get an RLMRealm
instance in each thread/dispatch_queue in which you want to read or write.
Also from Realm docs: RLMRealm
objects are cached internally by Realm, and calling this method multiple times on a single thread within a single iteration of the run loop will normally return the same RLMRealm
object.
So knowing this, I modified your code sample to get the RLMRealm
directly from the dispatch_async
block where it is used, without incurring a performance penalty, since it is cached.
I also noticed that an AccelerationEvent
was passed across threads, which is also not allowed. So this modified code sample passes NSString
s across threads instead.
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++;
});
}];
I haven't run this code to confirm that it works, so let me know if this still doesn't resolve the issue.
Upvotes: 35