Like many iOS developers, I use CoreData, and like many iOS developers that use CoreData, I have hard-to-track-down thread violation errors. I'm trying to implement a debugging strategy for throwing exceptions when the CoreData concurrency rules are broken. My attempt is below - my question is, is this valid? Will it produce false positives?
Summary: when an NSManagedObject is created, note the thread. Whenever a value is accessed later on, check if the current thread is the same as the creation thread, and throw an exception if not.
#import "NSManagedObject+DebugTracking.h"
#import "NSObject+DTRuntime.h"
#import <objc/runtime.h>
#import "NSManagedObjectContext+DebugThreadTracking.h"
@implementation NSManagedObject (DebugTracking)
+(void)load {
[NSManagedObject swizzleMethod:@selector(willAccessValueForKey:) withMethod:@selector(swizzled_willAccessValueForKey:)];
[NSManagedObject swizzleMethod:@selector(initWithEntity:insertIntoManagedObjectContext:) withMethod:@selector(swizzled_initWithEntity:insertIntoManagedObjectContext:)];
}
- (__kindof NSManagedObject *)swizzled_initWithEntity:(NSEntityDescription *)entity
insertIntoManagedObjectContext:(NSManagedObjectContext *)context
{
NSManagedObject *object = [self swizzled_initWithEntity:entity insertIntoManagedObjectContext:context];
NSLog(@"Initialising an object of type: %@", NSStringFromClass([self class]));
object.debugThread = [NSThread currentThread];
return object;
}
-(void)swizzled_willAccessValueForKey:(NSString *)key {
NSThread *thread = self.debugThread;
if (!thread) {
NSLog(@"No Thread set");
} else if (thread != [NSThread currentThread]) {
[NSException raise:@"CoreData thread violation exception" format:@"Property accessed from a different thread than the object's creation thread. Type: %@", NSStringFromClass([self class])];
} else {
NSLog(@"All is well");
}
[self swizzled_willAccessValueForKey: key];
}
-(NSThread *)debugThread {
return objc_getAssociatedObject(self, @selector(debugThread));
}
-(void)setDebugThread:(NSThread *)debugThread {
objc_setAssociatedObject(self, @selector(debugThread), debugThread, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end