MagicalRecord是一个简化CoreData操作的工具库,既然用到了这个库,那就看一下源码了解一下这个库是怎么工作的,我阅读的版本是2.3.2。
MagicalRecord的好处
- 清理我的Core Data相关代码
- 支持清晰,简单,一行代码式的查询
- 当需要优化请求时,仍然可以修改 NSFetchRequest
MagicalRecord源码
MagicalRecord的初始化
我们使用CoreData通常是这样的:
- 根据momd路径创建托管对象模型
- 根据托管对象模型创建持久化存储协调器
- 创建并关联SQLite数据库文件
- 创建管理对象上下文并指定持久化存储协调器
// 创建托管对象模型,并使用CoreData.momd路径当做初始化参数
NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];
// 根据托管对象模型创建持久化存储协调器
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 创建并关联SQLite数据库文件,如果已经存在则不会重复创建
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"CoreData"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
// 创建上下文对象,并发队列设置为主队列
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
// 指定持久化存储协调器
context.persistentStoreCoordinator = coordinator;
而使用MagicalRecord我们只需要调用[MagicalRecord setupCoreDataStack]
这个方法的具体实现是
+ (void) setupCoreDataStack
{
[self setupCoreDataStackWithStoreNamed:[self defaultStoreName]];
}
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName
{
//判断默认持久化存储协调器是否存在
if ([NSPersistentStoreCoordinator MR_defaultStoreCoordinator] != nil) return;
//不存在就创建持久化存储协调器
NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator MR_coordinatorWithSqliteStoreNamed:storeName];
//设置默认持久化存储协调器
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:coordinator];
//创建NSManagedObjectContext
[NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:coordinator];
}
创建持久化存储协调器
+ (NSPersistentStoreCoordinator *) MR_coordinatorWithSqliteStoreNamed:(NSString *)storeFileName withOptions:(NSDictionary *)options
{
//获取默认托管对象模型并创建持久化存储协调器
NSManagedObjectModel *model = [NSManagedObjectModel MR_defaultManagedObjectModel];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
//添加持久化存储到协调器
[psc MR_addSqliteStoreNamed:storeFileName withOptions:options];
return psc;
}
添加SQLite存储
- (NSPersistentStore *) MR_addSqliteStoreNamed:(id)storeFileName configuration:(NSString *)configuration withOptions:(__autoreleasing NSDictionary *)options
{
//持久化存储URL
NSURL *url = [storeFileName isKindOfClass:[NSURL class]] ? storeFileName : [NSPersistentStore MR_urlForStoreName:storeFileName];
NSError *error = nil;
//创建持久化存储文件
[self MR_createPathToStoreFileIfNeccessary:url];
NSPersistentStore *store = [self addPersistentStoreWithType:NSSQLiteStoreType
configuration:configuration
URL:url
options:options
error:&error];
//数据库不存在
if (!store)
{
/*
如果工程有DEBUG标记,则kMagicalRecordShouldDeleteStoreOnModelMismatch被设为YES
此时使用默认的SQLite数据存储,不创建新的版本的数据模型而是直接改变数据模型本身的方式,将会删除旧的存储并自动创建一个新的。
这会节省大量的时间 - 不再需要在改变数据模型后每次都重新卸载和安装应用
*/
if ([MagicalRecord shouldDeleteStoreOnModelMismatch])
{
BOOL isMigrationError = (([error code] == NSPersistentStoreIncompatibleVersionHashError) || ([error code] == NSMigrationMissingSourceModelError) || ([error code] == NSMigrationError));
if ([[error domain] isEqualToString:NSCocoaErrorDomain] && isMigrationError)
{
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchWillDeleteStore object:nil];
NSError * deleteStoreError;
// 无法打开数据库就删除
NSString *rawURL = [url absoluteString];
NSURL *shmSidecar = [NSURL URLWithString:[rawURL stringByAppendingString:@"-shm"]];
NSURL *walSidecar = [NSURL URLWithString:[rawURL stringByAppendingString:@"-wal"]];
[[NSFileManager defaultManager] removeItemAtURL:url error:&deleteStoreError];
[[NSFileManager defaultManager] removeItemAtURL:shmSidecar error:nil];
[[NSFileManager defaultManager] removeItemAtURL:walSidecar error:nil];
MRLogWarn(@"Removed incompatible model version: %@", [url lastPathComponent]);
if(deleteStoreError) {
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchCouldNotDeleteStore object:nil userInfo:@{@"Error":deleteStoreError}];
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchDidDeleteStore object:nil];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchWillRecreateStore object:nil];
// 再次创建持久化存储
store = [self addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:options
error:&error];
if (store)
{
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchDidRecreateStore object:nil];
error = nil;
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:kMagicalRecordPSCMismatchCouldNotRecreateStore object:nil userInfo:@{@"Error":error}];
}
}
}
[MagicalRecord handleErrors:error];
}
return store;
}
在平时开发过程中,经常会出现修改模型结构的情况,需要把app卸载了然后重新安装才能避免打不开数据库导致崩溃的问题。MagicalRecord的处理方式是在DEBUG模式下将数据库直接删除重新创建,可以节省很多时间。
接着创建管理对象上下文
+ (void) MR_initializeDefaultContextWithCoordinator:(NSPersistentStoreCoordinator *)coordinator;
{
if (MagicalRecordDefaultContext == nil)
{
NSManagedObjectContext *rootContext = [self MR_contextWithStoreCoordinator:coordinator];
[self MR_setRootSavingContext:rootContext];
NSManagedObjectContext *defaultContext = [self MR_newMainQueueContext];
[self MR_setDefaultContext:defaultContext];
[defaultContext setParentContext:rootContext];
}
}
Core Data有很多种设置方式,常用有3种
-
两个上下文,一个协调器
一个位于主线程的上下文,一个位于后台线程的上下文,共用一个协调器。UI相关的操作在主线程的上下文上进行,后台操作放入后台线程执行。当后台线程上下文保存后,通过监听NSManagedObjectContextDidSaveNotification
来更新主线程的上下文中的数据。把获取和保存等操作放入后台线程来避免阻塞主线程。由于使用同一个协调器,两个上下文可以共用同一个行缓存,避免了一些对数据库不必要的访问,提高了性能。但是由于共享一个协调器,所以在同一时间只有一个上下文能使用协调器。并且要设置合适的合并策略来解决多个上下文都有更改是造成的冲突。 -
两个协调器
两个独立的协调器,共享同一个SQLite数据库。创建一个主线程上下文,连接一个协调器,一个后台线程上下文,连接一个协调器。这样的设置可以减少对协调器的竞争问题,提供了更好的并发处理能力,因为SQLite支持多读单写,在不进行多个写操作时,不会产生竞争问题。但是由于行缓存位于协调器层,所以行缓存不能共享。也就是说如果某一个上下文修改了数据,另一个上下文需要获取到这个数据需要下降到数据库层。访问数据库相比于访问内存效率要低。 -
嵌套上下文
一个位于主线程的上下文,一个位于后台线程的上下文,将后台线程上下文直接与协调器相连,主线程上下文的父上下文设置为后台线程上下文。使用这种设置,主线程上下文的操作完全在内存中进行,因为所有的操作只会被push到父上下文中,当父上下文进行保存操作时,才会通过持久化存储协调器访问数据库,由于父上下文是一个私有队列上下文,这些操作不会阻塞UI线程。
MagicalRecord使用的是嵌套上下文的设计。原因可能是相比于其他两种设计,嵌套上下文使用简单,管理方便,并且也能很好的分离UI操作和后台数据操作,虽然性能不是最高的,但是适用于大部分APP。
MagicalRecord保存
MagicalRecord提供了defaultContext让我们在主线程处理有关UI的数据。如果我们想在后台操作数据,可以使用+ saveWithBlock:completion:
函数。这个函数用于更改实体的Block永远不会在主线程执行,并且提供一个rootContext的子context。当Block中的操作执行完毕,会回调completion block,这个block在主线程中调用,所以可以在此block里安全触发UI更新。
+ (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
{
//创建一个父上下文是rootContext的上下文,类型是NSPrivateQueueConcurrencyType
NSManagedObjectContext *savingContext = [NSManagedObjectContext MR_rootSavingContext];
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:savingContext];
//子上下文通过performBlock回调到自己的线程中执行block
[localContext performBlock:^{
[localContext MR_setWorkingName:NSStringFromSelector(_cmd)];
if (block) {
block(localContext);
}
//保存数据
[localContext MR_saveWithOptions:MRSaveParentContexts completion:completion];
}];
}
而要进行同步保存的话使用MR_saveToPersistentStoreAndWait
函数
- (void) MR_saveToPersistentStoreAndWait
{
[self MR_saveWithOptions:MRSaveParentContexts | MRSaveSynchronously completion:nil];
}
在保存选项中增加了同步保存的选项,如何进行同步和后台的存储,让我们看看保存数据的具体代码
- (void) MR_saveWithOptions:(MRSaveOptions)saveOptions completion:(MRSaveCompletionHandler)completion;
{
__block BOOL hasChanges = NO;
//判断上下文是否存在更改,不存在直接返回
if ([self concurrencyType] == NSConfinementConcurrencyType)
{
hasChanges = [self hasChanges];
}
else
{
[self performBlockAndWait:^{
hasChanges = [self hasChanges];
}];
}
if (!hasChanges)
{
MRLogVerbose(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]);
if (completion)
{
dispatch_async(dispatch_get_main_queue(), ^{
completion(NO, nil);
});
}
return;
}
//父上下文是否应该保存
BOOL shouldSaveParentContexts = ((saveOptions & MRSaveParentContexts) == MRSaveParentContexts);
//是否应该同步保存
BOOL shouldSaveSynchronously = ((saveOptions & MRSaveSynchronously) == MRSaveSynchronously);
//是否应该同步保存除了rootContext
BOOL shouldSaveSynchronouslyExceptRoot = ((saveOptions & MRSaveSynchronouslyExceptRootContext) == MRSaveSynchronouslyExceptRootContext);
//判断是否同步保存
BOOL saveSynchronously = (shouldSaveSynchronously && !shouldSaveSynchronouslyExceptRoot) ||
(shouldSaveSynchronouslyExceptRoot && (self != [[self class] MR_rootSavingContext]));
//保存block
id saveBlock = ^{
MRLogInfo(@"→ Saving %@", [self MR_description]);
MRLogVerbose(@"→ Save Parents? %@", shouldSaveParentContexts ? @"YES" : @"NO");
MRLogVerbose(@"→ Save Synchronously? %@", saveSynchronously ? @"YES" : @"NO");
BOOL saveResult = NO;
NSError *error = nil;
@try
{
saveResult = [self save:&error];
}
@catch(NSException *exception)
{
MRLogError(@"Unable to perform save: %@", (id)[exception userInfo] ?: (id)[exception reason]);
}
@finally
{
[MagicalRecord handleErrors:error];
//判断父上下文是否需要保存
if (saveResult && shouldSaveParentContexts && [self parentContext])
{
// 添加或移除同步选项
MRSaveOptions modifiedOptions = saveOptions;
if (saveSynchronously)
{
modifiedOptions |= MRSaveSynchronously;
}
else
{
modifiedOptions &= ~MRSaveSynchronously;
}
// 父上下文递归保存
[[self parentContext] MR_saveWithOptions:modifiedOptions completion:completion];
}
else
{
if (saveResult)
{
MRLogVerbose(@"→ Finished saving: %@", [self MR_description]);
}
if (completion)
{
//成功回调主线程
dispatch_async(dispatch_get_main_queue(), ^{
completion(saveResult, error);
});
}
}
}
};
if (saveSynchronously)
{
//通过performBlockAndWait:调度到自己的线程中保存并阻塞线程直到block处理结束
[self performBlockAndWait:saveBlock];
}
else
{
//调度到自己的线程中保存,不阻塞线程
[self performBlock:saveBlock];
}
}
保存数据时根据saveOptions决定是否同步以及父上下文是否保存。如果同步,调用performBlockAndWait:
函数,否则调用performBlock:
函数。通过递归调用的方式进行父上下文的保存直到rootContext将数据通过 Persistent Store Coordinator存入数据库。在其它线程保存完毕后,默认上下文如何更新数据呢?在设置默认上下文时已经监听了NSManagedObjectContextDidSaveNotification
事件。
if ((MagicalRecordDefaultContext != nil) && ([self MR_rootSavingContext] != nil)) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(rootContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:[self MR_rootSavingContext]];
}
+ (void)rootContextDidSave:(NSNotification *)notification
{
//判断是否是rootContext存储完毕的通知
if ([notification object] != [self MR_rootSavingContext])
{
return;
}
if ([NSThread isMainThread] == NO)
{
//不是主线程重新在主线程中调用rootContextDidSave:
dispatch_async(dispatch_get_main_queue(), ^{
[self rootContextDidSave:notification];
});
return;
}
for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey])
{
[[[self MR_defaultContext] objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[[self MR_defaultContext] mergeChangesFromContextDidSaveNotification:notification];
}
当后台线程操作完数据,默认上下文接到通知合并更改。
MagicalRecord增删改查
创建实体对象
+ (id) MR_createEntityInContext:(NSManagedObjectContext *)context
{
if ([self respondsToSelector:@selector(insertInManagedObjectContext:)] && context != nil)
{
//判断是否实现了`insertInManagedObjectContext:`函数,如果可以通过这个函数创建实体
id entity = [self performSelector:@selector(insertInManagedObjectContext:) withObject:context];
return entity;
}
else
{
//通过entityName生成NSEntityDescription
NSEntityDescription *entity = nil;
if (context == nil)
{
entity = [self MR_entityDescription];
}
else
{
entity = [self MR_entityDescriptionInContext:context];
}
if (entity == nil)
{
return nil;
}
//创建entity并返回
return [[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
}
}
删除实体
- (BOOL) MR_deleteEntityInContext:(NSManagedObjectContext *)context
{
NSError *error = nil;
//跨上下文不能直接访问实体对象,通过实体对象的ID判断当前上下文中是否存在这个实体对象
NSManagedObject *entityInContext = [context existingObjectWithID:[self objectID] error:&error];
[MagicalRecord handleErrors:error];
//如果实体对象存在就删除
if (entityInContext) {
[context deleteObject:entityInContext];
}
return YES;
}
删除全部实体
+ (BOOL) MR_truncateAllInContext:(NSManagedObjectContext *)context
{
//创建获取所有实体的请求
NSFetchRequest *request = [self MR_requestAllInContext:context];
//设置返回值为惰值
[request setReturnsObjectsAsFaults:YES];
//只获取ObjectID
[request setIncludesPropertyValues:NO];
//查找要删除的数据
NSArray *objectsToDelete = [self MR_executeFetchRequest:request inContext:context];
for (NSManagedObject *objectToDelete in objectsToDelete)
{
//删除数据
[objectToDelete MR_deleteEntityInContext:context];
}
return YES;
}
删除全部实体时,通过使用惰值和ObjectID减少对内存的占用。也可以使用MR_deleteAllMatchingPredicate:inContext:
通过指定谓词来删除符合条件的数据。
MagicalRecord的查找
+ MR_findAllInContext:context
+ MR_findAllSortedBy:ascending:inContext:
+ MR_findAllSortedBy:ascending:withPredicate:inContext:
+ MR_findAllWithPredicate:inContext:
这些查找方法都会调用MR_executeFetchRequest:inContext:
+ (NSArray *) MR_executeFetchRequest:(NSFetchRequest *)request inContext:(NSManagedObjectContext *)context
{
__block NSArray *results = nil;
[context performBlockAndWait:^{
NSError *error = nil;
results = [context executeFetchRequest:request error:&error];
if (results == nil)
{
[MagicalRecord handleErrors:error];
}
}];
return results;
}
最后都是通过executeFetchRequest:error:
函数返回查询结果。
MagicalRecord还提供了短方法名,比如用 findAll 代替 MR_findAll,调用[MagicalRecord enableShorthandMethods]
开启。通过Runtime的方法交换和动态方法解析来实现。
+ (void)enableShorthandMethods
{
if (kMagicalRecordShorthandMethodsSwizzled == NO)
{
NSArray *classes = [self classesToSwizzle];
//将数组中的类的resolveClassMethod:和resolveInstanceMethod:方法替换
[classes enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
Class objectClass = (Class)object;
[self updateResolveMethodsForClass:objectClass];
}];
kMagicalRecordShorthandMethodsSwizzled = YES;
}
}
+ (NSArray *)classesToSwizzle
{
return @[ [NSManagedObject class],
[NSManagedObjectContext class],
[NSManagedObjectModel class],
[NSPersistentStore class],
[NSPersistentStoreCoordinator class] ];
}
+ (void)updateResolveMethodsForClass:(Class)objectClass
{
MRReplaceSelectorForTargetWithSourceImplementation(self, @selector(MR_resolveClassMethod:), objectClass, @selector(resolveClassMethod:));
MRReplaceSelectorForTargetWithSourceImplementation(self, @selector(MR_resolveInstanceMethod:), objectClass, @selector(resolveInstanceMethod:));
}
static void MRReplaceSelectorForTargetWithSourceImplementation(Class sourceClass, SEL sourceSelector, Class targetClass, SEL targetSelector)
{
// 获取MR_resolveClassMethod:方法
Method sourceClassMethod = class_getClassMethod(sourceClass, sourceSelector);
// 获取resolveInstanceMethod:方法
Method targetClassMethod = class_getClassMethod(targetClass, targetSelector);
// 获取目标类的源类
Class targetMetaClass = objc_getMetaClass([NSStringFromClass(targetClass) cStringUsingEncoding:NSUTF8StringEncoding]);
//向目标源类中添加一个方法,SEL为MR_resolveClassMethod:方法的SEL,IMP为resolveInstanceMethod:的IMP
BOOL methodWasAdded = class_addMethod(targetMetaClass, sourceSelector,
method_getImplementation(targetClassMethod),
method_getTypeEncoding(targetClassMethod));
if (methodWasAdded)
{
//把目标源类中的resolveInstanceMethod:方法的IMP替换为MR_resolveClassMethod:方法的IMP
class_replaceMethod(targetMetaClass, targetSelector,
method_getImplementation(sourceClassMethod),
method_getTypeEncoding(sourceClassMethod));
}
}
static NSString *const kMagicalRecordCategoryPrefix = @"MR_";
+ (BOOL)MR_resolveClassMethod:(SEL)originalSelector
{
BOOL resolvedClassMethod = [self MR_resolveClassMethod:originalSelector];
if (!resolvedClassMethod)
{
// 如果原resolveClassMethod:实现无法解析SEL,向本类添加短方法名的实现
resolvedClassMethod = MRAddShortHandMethodForPrefixedClassMethod(self, originalSelector, kMagicalRecordCategoryPrefix);
}
// 返回YES重新发送消息,返回NO则进入消息转发
return resolvedClassMethod;
}
+ (BOOL)MR_resolveInstanceMethod:(SEL)originalSelector
{
BOOL resolvedClassMethod = [self MR_resolveInstanceMethod:originalSelector];
if (!resolvedClassMethod)
{
// 同上
resolvedClassMethod = MRAddShorthandMethodForPrefixedInstanceMethod(self, originalSelector, kMagicalRecordCategoryPrefix);
}
return resolvedClassMethod;
}
static BOOL MRAddShorthandMethodForPrefixedInstanceMethod(Class objectClass, SEL originalSelector, NSString *prefix)
{
NSString *originalSelectorString = NSStringFromSelector(originalSelector);
// 根据名称判断是否添加实现
if ([originalSelectorString hasPrefix:prefix] == NO &&
([originalSelectorString hasPrefix:@"_"] || [originalSelectorString hasPrefix:@"init"]))
{
// 在短方法名前添加MR_
NSString *prefixedSelector = [prefix stringByAppendingString:originalSelectorString];
// 获取带MR_的方法
Method existingMethod = class_getInstanceMethod(objectClass, NSSelectorFromString(prefixedSelector));
if (existingMethod)
{
// 向类中添加SEL为短方法名,IMP为带MR_的方法实现
BOOL methodWasAdded = class_addMethod(objectClass,
originalSelector,
method_getImplementation(existingMethod),
method_getTypeEncoding(existingMethod));
// 返回添加结果
return methodWasAdded;
}
}
return NO;
}
static BOOL MRAddShortHandMethodForPrefixedClassMethod(Class objectClass, SEL originalSelector, NSString *prefix)
{
NSString *originalSelectorString = NSStringFromSelector(originalSelector);
// 根据名称判断是否添加实现
if ([originalSelectorString hasPrefix:prefix] == NO &&
[originalSelectorString hasSuffix:@"entityName"] == NO)
{
// 在短方法名前添加MR_
NSString *prefixedSelector = [prefix stringByAppendingString:originalSelectorString];
// 获取带MR_的方法
Method existingMethod = class_getClassMethod(objectClass, NSSelectorFromString(prefixedSelector));
if (existingMethod)
{
// 获取本类的源类。
// 因为类对象的方法列表在源类中,调用类方法时,通过类对象指向源类的isa指针找到源类,并在源类的类方法列表中查找方法,所以要向源类中添加方法。
Class metaClass = objc_getMetaClass([NSStringFromClass(objectClass) cStringUsingEncoding:NSUTF8StringEncoding]);
// 向源类中添加SEL为短方法名,IMP为带MR_的方法实现
BOOL methodWasAdded = class_addMethod(metaClass,
originalSelector,
method_getImplementation(existingMethod),
method_getTypeEncoding(existingMethod));
// 返回添加结果
return methodWasAdded;
}
}
return NO;
}
MagicalRecord替换了动态方法解析方法resolveClassMethod :
和resolveInstanceMethod:
。由于使用短方法名找不到方法实现而进入动态解析过程,动态解析方法已被替换,进入自己写的解析方法。根据方法名判断是否是自己的方法,如果是,在短方法名前添加MR_获取方法实现,通过class_addMethod
向类中添加一个方法。如果添加成功,返回YES,会重新寻找短方法名的方法实现,这时就能找到我们刚才添加的短方法名的方法,从而实现了短方法名。
网友评论