Created
June 8, 2025 10:44
-
-
Save pookjw/9989aff494ae1ed866cbaf9e60126e21 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// main.m | |
// MyScript | |
// | |
// Created by Jinwoo Kim on 6/6/25. | |
// | |
#import <CoreData/CoreData.h> | |
#include <objc/runtime.h> | |
#include <objc/message.h> | |
@interface DataStack : NSObject { | |
@package NSPersistentStoreCoordinator *_persistentCoordinator; | |
@package NSManagedObjectContext *_managedObjectContext; | |
@package NSPersistentHistoryToken *_historyToken; | |
} | |
@end | |
@implementation DataStack | |
+ (NSManagedObjectModel *)_makeModel { | |
NSEntityDescription *entity = [[NSEntityDescription alloc] init]; | |
entity.name = @"Entity"; | |
NSAttributeDescription *nameAttribute = [[NSAttributeDescription alloc] init]; | |
nameAttribute.name = @"name"; | |
nameAttribute.attributeType = NSStringAttributeType; | |
entity.properties = @[nameAttribute]; | |
[nameAttribute release]; | |
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init]; | |
model.entities = @[entity]; | |
[entity release]; | |
return [model autorelease]; | |
} | |
+ (NSURL *)_storeURL { | |
static NSURL *result; | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
NSURL *tmpDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory()]; | |
result = [[[tmpDirectory URLByAppendingPathComponent:[NSUUID UUID].UUIDString isDirectory:NO] URLByAppendingPathExtension:@"sqlite"] retain]; | |
}); | |
return result; | |
} | |
- (instancetype)init { | |
if (self = [super init]) { | |
NSPersistentStoreCoordinator *persistentCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[DataStack _makeModel]]; | |
NSPersistentStoreDescription *storeDescription = [[NSPersistentStoreDescription alloc] initWithURL:[DataStack _storeURL]]; | |
storeDescription.type = NSSQLiteStoreType; | |
storeDescription.shouldAddStoreAsynchronously = NO; | |
[storeDescription setOption:@YES forKey:NSPersistentHistoryTrackingKey]; | |
[storeDescription setOption:@YES forKey:NSPersistentStoreRemoteChangeNotificationPostOptionKey]; | |
[persistentCoordinator addPersistentStoreWithDescription:storeDescription completionHandler:^(NSPersistentStoreDescription * _Nonnull desc, NSError * _Nullable error) { | |
assert(error == nil); | |
}]; | |
[storeDescription release]; | |
_persistentCoordinator = persistentCoordinator; | |
_historyToken = [[persistentCoordinator currentPersistentHistoryTokenFromStores:persistentCoordinator.persistentStores] retain]; | |
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; | |
managedObjectContext.persistentStoreCoordinator = persistentCoordinator; | |
_managedObjectContext = managedObjectContext; | |
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_didReceiveRemoteChange:) name:NSPersistentStoreRemoteChangeNotification object:persistentCoordinator]; | |
} | |
return self; | |
} | |
- (void)dealloc { | |
[NSNotificationCenter.defaultCenter removeObserver:self]; | |
[_persistentCoordinator release]; | |
[_managedObjectContext release]; | |
[_historyToken release]; | |
[super dealloc]; | |
} | |
- (void)_didReceiveRemoteChange:(NSNotification *)notification { | |
[_persistentCoordinator performBlock:^{ | |
NSPersistentHistoryToken * _Nullable oldHistoryToken = _historyToken; | |
_historyToken = [notification.userInfo[NSPersistentHistoryTokenKey] retain]; | |
NSPersistentHistoryChangeRequest *request = [NSPersistentHistoryChangeRequest fetchHistoryAfterToken:oldHistoryToken]; | |
[oldHistoryToken release]; | |
request.resultType = NSPersistentHistoryResultTypeTransactionsAndChanges; | |
[_managedObjectContext performBlock:^{ | |
NSError * _Nullable error = nil; | |
NSPersistentHistoryResult *result = [_managedObjectContext executeRequest:request error:&error]; | |
assert(result != nil); | |
NSArray<NSPersistentHistoryTransaction *> *transactions = result.result; | |
for (NSPersistentHistoryTransaction *transaction in transactions) { | |
[_managedObjectContext mergeChangesFromContextDidSaveNotification:[transaction objectIDNotification]]; | |
} | |
}]; | |
}]; | |
} | |
- (void)_resetPrimaryCache { | |
[_persistentCoordinator performBlockAndWait:^{ | |
for (NSPersistentStore *store in _persistentCoordinator.persistentStores) { | |
if (![store isKindOfClass:objc_lookUpClass("NSSQLCore")]) continue; | |
id _generationalRowCache; | |
assert(object_getInstanceVariable(store, "_generationalRowCache", (void **)&_generationalRowCache) != NULL); | |
id oldPrimaryCache; | |
assert(object_getInstanceVariable(_generationalRowCache, "_primaryCache", (void **)&oldPrimaryCache) != NULL); | |
id newPrimaryCache = ((id (*)(id, SEL, id))objc_msgSend)([[oldPrimaryCache class] alloc], sel_registerName("initWithPersistentStore:"), store); | |
[oldPrimaryCache release]; | |
assert(object_setInstanceVariable(_generationalRowCache, "_primaryCache", newPrimaryCache) != NULL); | |
} | |
}]; | |
} | |
@end | |
int main(int argc, const char * argv[]) { | |
@autoreleasepool { | |
DataStack *stack_1 = [[DataStack alloc] init]; | |
DataStack *stack_2 = [[DataStack alloc] init]; | |
__block NSManagedObject *object_1; | |
[stack_1->_managedObjectContext performBlockAndWait:^{ | |
object_1 = [[NSManagedObject alloc] initWithEntity:stack_1->_persistentCoordinator.managedObjectModel.entitiesByName[@"Entity"] insertIntoManagedObjectContext:stack_1->_managedObjectContext]; | |
NSError * _Nullable error = nil; | |
assert([stack_1->_managedObjectContext save:&error]); | |
}]; | |
__block NSManagedObject *object_2; | |
[stack_2->_managedObjectContext performBlockAndWait:^{ | |
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Entity"]; | |
NSError * _Nullable error = nil; | |
NSArray<NSManagedObject *> *objects = [stack_2->_managedObjectContext executeFetchRequest:request error:&error]; | |
assert(objects != nil); | |
assert(objects.count == 1); | |
object_2 = [objects[0] retain]; | |
}]; | |
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | |
NSObject *observer = [NSNotificationCenter.defaultCenter addObserverForName:NSManagedObjectContextDidMergeChangesObjectIDsNotification object:stack_2->_managedObjectContext queue:nil usingBlock:^(NSNotification * _Nonnull notification) { | |
dispatch_semaphore_signal(semaphore); | |
}]; | |
[stack_1->_managedObjectContext performBlockAndWait:^{ | |
[object_1 setValue:@"Foo" forKey:@"name"]; | |
NSError * _Nullable error = nil; | |
assert([stack_1->_managedObjectContext save:&error]); | |
}]; | |
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | |
[NSNotificationCenter.defaultCenter removeObserver:observer]; | |
#define MODE 1 | |
#if MODE == 0 | |
[stack_2->_managedObjectContext performBlockAndWait:^{ | |
NSError * _Nullable error = nil; | |
[stack_2->_managedObjectContext setQueryGenerationFromToken:[NSQueryGenerationToken currentQueryGenerationToken] error:&error]; | |
assert(error == nil); | |
NSString *name = [object_2 valueForKey:@"name"]; | |
assert([name isEqualToString:@"Foo"]); | |
}]; | |
#elif MODE == 1 | |
[stack_2->_managedObjectContext performBlockAndWait:^{ | |
[stack_2 _resetPrimaryCache]; | |
NSString *name = [object_2 valueForKey:@"name"]; | |
assert([name isEqualToString:@"Foo"]); | |
}]; | |
#elif MODE == 2 | |
[stack_2->_managedObjectContext performBlockAndWait:^{ | |
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entity"]; | |
NSError * _Nullable error = nil; | |
NSManagedObject *object_2_1 = [stack_2->_managedObjectContext executeFetchRequest:fetchRequest error:&error].firstObject; | |
assert(error == nil); | |
assert(object_2_1 != nil); | |
NSString *name = [object_2_1 valueForKey:@"name"]; | |
assert([name isEqualToString:@"Foo"]); | |
}]; | |
#elif MODE == 3 | |
[stack_2->_managedObjectContext performBlockAndWait:^{ | |
[object_2 valueForKey:@"name"]; | |
[stack_2->_managedObjectContext refreshObject:object_2 mergeChanges:YES]; | |
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entity"]; | |
NSError * _Nullable error = nil; | |
NSManagedObject *object_2_1 = [stack_2->_managedObjectContext executeFetchRequest:fetchRequest error:&error].firstObject; | |
assert(error == nil); | |
assert(object_2_1 != nil); | |
NSString *name = [object_2_1 valueForKey:@"name"]; | |
assert([name isEqualToString:@"Foo"]); | |
}]; | |
#endif | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment