Skip to content

Instantly share code, notes, and snippets.

@jverkoey
Created April 14, 2014 11:52
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jverkoey/10641155 to your computer and use it in GitHub Desktop.
Save jverkoey/10641155 to your computer and use it in GitHub Desktop.
Core Data Managed Object Context Debugging
// NSManagedObjectContext+DebugSwizzling.h
#import <CoreData/CoreData.h>
#if DEBUG
/**
* Toggles debugging of Core Data managed object contexts.
*
* When enabled, will fire NSLogs in the following cases:
*
* - accessing/modifying an entity outside of a performBlock* operation.
* - accessing/modifying entities in a context other than their own.
*/
void SwizzleNSManagedObjectContextForDebug();
#else
#define SwizzleNSManagedObjectContextForDebug()
#endif
// NSManagedObjectContext+DebugSwizzling.m
#import "NSManagedObjectContext+DebugSwizzling.h"
#if DEBUG
static NSString* swizzle_NSManagedObjectContextActiveContextKey = @"swizzle_NSManagedObjectContextActiveContextKey";
NSManagedObjectContext* GetManagedObjectContextForCurrentThread() {
NSMutableArray* stack = [NSThread currentThread].threadDictionary[swizzle_NSManagedObjectContextActiveContextKey];
return [stack lastObject];
}
void LogManagedObjectContextInfo(NSManagedObjectContext* activeMoc, NSString* entityName) {
NSManagedObjectContext* threadMoc = GetManagedObjectContextForCurrentThread();
if (nil == threadMoc) {
NSLog(@"An entity (%@) is being used outside of a managed object context block!", entityName);
} else if (threadMoc != activeMoc) {
NSLog(@"An entity (%@) was being used in a different managed object context than the current thread's.", entityName);
NSLog(@"Thread context: %@", threadMoc);
NSLog(@"Active context: %@", activeMoc);
}
}
@implementation NSManagedObject (DebugSwizzling)
- (id)swizzle_initWithEntity:(NSEntityDescription *)entity insertIntoManagedObjectContext:(NSManagedObjectContext *)context {
LogManagedObjectContextInfo(self.managedObjectContext, NSStringFromClass(self.class));
return [self swizzle_initWithEntity:entity insertIntoManagedObjectContext:context];
}
- (void)swizzle_willAccessValueForKey:(NSString *)key {
LogManagedObjectContextInfo(self.managedObjectContext, NSStringFromClass(self.class));
[self swizzle_willAccessValueForKey:key];
}
- (void)swizzle_willChangeValueForKey:(NSString *)key {
LogManagedObjectContextInfo(self.managedObjectContext, NSStringFromClass(self.class));
[self swizzle_willChangeValueForKey:key];
}
@end
@implementation NSEntityDescription (DebugSwizzling)
+ (id)swizzle_insertNewObjectForEntityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context {
LogManagedObjectContextInfo(context, entityName);
return [self swizzle_insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
}
+ (NSEntityDescription *)swizzle_entityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context {
LogManagedObjectContextInfo(context, entityName);
return [self swizzle_entityForName:entityName inManagedObjectContext:context];
}
@end
@implementation NSManagedObjectContext (DebugSwizzling)
- (void)debug_push {
NSMutableArray* stack = [NSThread currentThread].threadDictionary[swizzle_NSManagedObjectContextActiveContextKey];
if (nil == stack) {
stack = [NSMutableArray array];
[NSThread currentThread].threadDictionary[swizzle_NSManagedObjectContextActiveContextKey] = stack;
}
[stack addObject:self];
}
- (void)debug_pop {
NSMutableArray* stack = [NSThread currentThread].threadDictionary[swizzle_NSManagedObjectContextActiveContextKey];
[stack removeLastObject];
}
- (void (^)())debug_blockWithBlock:(void (^)())block {
return ^{
[self debug_push];
if (block) {
block();
}
[self debug_pop];
};
}
- (NSArray *)swizzle_executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error {
[self debug_push];
NSArray* results = [self swizzle_executeFetchRequest:request error:error];
[self debug_pop];
return results;
}
- (void)swizzle_performBlock:(void (^)())block {
[self swizzle_performBlock:[self debug_blockWithBlock:block]];
}
- (void)swizzle_performBlockAndWait:(void (^)())block {
[self swizzle_performBlockAndWait:[self debug_blockWithBlock:block]];
}
@end
void SwizzleNSManagedObjectContextForDebug() {
NISwapInstanceMethods([NSManagedObjectContext class], @selector(executeFetchRequest:error:), @selector(swizzle_executeFetchRequest:error:));
NISwapInstanceMethods([NSManagedObjectContext class], @selector(performBlock:), @selector(swizzle_performBlock:));
NISwapInstanceMethods([NSManagedObjectContext class], @selector(performBlockAndWait:), @selector(swizzle_performBlockAndWait:));
NISwapClassMethods([NSEntityDescription class], @selector(insertNewObjectForEntityForName:inManagedObjectContext:), @selector(swizzle_insertNewObjectForEntityForName:inManagedObjectContext:));
NISwapClassMethods([NSEntityDescription class], @selector(entityForName:inManagedObjectContext:), @selector(swizzle_entityForName:inManagedObjectContext:));
NISwapInstanceMethods([NSManagedObject class], @selector(initWithEntity:insertIntoManagedObjectContext:), @selector(swizzle_initWithEntity:insertIntoManagedObjectContext:));
NISwapInstanceMethods([NSManagedObject class], @selector(willAccessValueForKey:), @selector(swizzle_willAccessValueForKey:));
NISwapInstanceMethods([NSManagedObject class], @selector(willChangeValueForKey:), @selector(swizzle_willChangeValueForKey:));
}
#endif
@jverkoey
Copy link
Author

Creates logs like these:

An entity (<some entity>) is being used outside of a managed object context block!
An entity (<another entity>) was being used in a different managed object context than the current thread's.
Thread context: <NSManagedObjectContext: 0x9258050>
Active context: <NSManagedObjectContext: 0x9242320>

@tonyarnold
Copy link

It'd probably be a good idea to cater for an entity not having an active context before you check anything else. Otherwise, super useful! Thanks @jverkoey!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment