一步一脚印封装CoreData

CoreData是一种非常灵活地数据管理方式,它既不是一种O/RM也不是一个单纯地SQL wrapper,但是却有这两者的有点,也就是说你既可以把它用作对象关系映射,也可以简化数据库的操作。那么要熟练地使用coredata我们需要掌握哪些姿势呢?

整个coredata技术栈中从上游到下游大致有这些技术:

上层:NSManagedObject,NSEntityDescription,NSFetchRequest,NSPredicate,NSSortDescriptors

中层:NSManagedObjectContext,NSManagedObjectModel,NSPersistentStoreCoordinator

底层:NSPersistentStore,SQLite,NSFileManager

那么我们就可以按照这样的分层来组织我们的架构。代码可以在这里下到

TCTCoredata

在这个架构模型当中最关键的就是位于中间这一层的对象。他们起到了呈上启下的作用,所以很明显我们需要封装一个类用来沟通系统暴露出来的接口和需要给上层提供的功能。

@interface TCTCoreData : NSObject

+ (instancetype)defaultCoreData;

@property (nonatomic, copy) NSString *modelName;
@property (nonatomic, copy) NSString *storeFileName;
@property (nonatomic, assign) TCTCoreDataStoreType storeType;

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectMainContext;

@end

接口这边的内容很简单就是context和全局的一个单例对象,支持一些自定义(model的名字,存储文件的名字等),其实整个这一层给上层提供的就是context这个容器。

- (NSManagedObjectContext *)managedObjectMainContext{
    if (_managedObjectMainContext) {
        return _managedObjectMainContext;
    }

    if (self.persistentStoreCoordinator) {
        _managedObjectMainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectMainContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
    }
    return _managedObjectMainContext;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

    NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption: @YES,
                               NSInferMappingModelAutomaticallyOption: @YES };


    NSURL *fileURL = [[[self applicationDocumentsDirectory] URLByAppendingPathComponent:self.storeFileName] URLByAppendingPathExtension:TCT_FileNameExtension(self.storeType)];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if (![fileManager fileExistsAtPath:[fileURL path]]) {
        NSURL *storeURL = [self URLOfFileName:self.storeFileName withExtension:TCT_FileNameExtension(self.storeType)];
        if (storeURL) {
            [fileManager copyItemAtURL:storeURL toURL:fileURL error:NULL];
        }
    }

    NSError *error = nil;
    if (![_persistentStoreCoordinator addPersistentStoreWithType:TCT_StoreType(self.storeType) configuration:nil URL:fileURL options:options error:&error]) {
#if DEBUG
        [self dealWithLinkStoreError:error];
#endif
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }

    NSURL *modelURL = [self URLOfFileName:self.modelName withExtension:kModelNameExtension];

    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    return _managedObjectModel;
}

具体的实现也很简单,主要就是从上而下的创建context,persistent和model。然后在这期间会运用到一些策略,比如说,自动升级,从bundle中拷贝sqlite文件等,主要是这一期是简单的封装,所以像数据库迁移,多context策略都没有写入。这个在后续版本迭代中会陆续改进。

在这个coredata栈建立中,还连接下层的:NSPersistentStore,SQLite,NSFileManager等,达到一个数据持久化的策略。

TCTDatabaseHelper

这一层就对应了上层的内容,对上层的内容做了简单地封装,本来是准备把NSPredicate和NSSortDescriptors封装进去的,后来考虑到这不是一个简单sql wrapper,所以觉得通过category来单独封装。

+ (void)configureDatabaseWithSetting:(TCTDatabaseSettingsBlock)setting;

+ (NSArray *)searchAllModelsWithEntityName:(NSString *)entityName;
+ (NSArray *)searchModelsWithEntityName:(NSString *)entityName predicate:(NSPredicate *)predicate sorts:(NSArray *)sorts;

+ (void)deleteAllModels:(NSArray *)models;
+ (id)createModelWithEntityName:(NSString *)entityName;
+ (BOOL)saveContext;

除了第一个方法是用来对coredata配置外,其余均是用于对使用者提供的功能支持。其实对于使用方来说无非就是增删改查,怎么方便怎么来。然后又要结合对象管理,所以这里提供了增删查(在对象管理中,改需要先查出来后,修改对象属性,最后保存即可)。

+ (NSArray *)searchModelsWithEntityName:(NSString *)entityName predicate:(NSPredicate *)predicate sorts:(NSArray *)sorts{
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:entityName];
    request.predicate = predicate;
    request.sortDescriptors = sorts;

    NSError *error;
    NSArray* objects = [[TCTCoreData defaultCoreData].managedObjectMainContext executeFetchRequest:request error:&error];

    if (!objects) {
        NSLog(@"%@", error.description);
    }
    return objects;

}

主要就是对fetch的组装,非常简洁,也为后续的扩展提供了可能性。那么到这里coredata的最基本封装要告一段落了,在这个过程中主要用到了NSManagedObject,NSEntityDescription,NSFetchRequest,NSManagedObjectContext,NSManagedObjectModel,NSPersistentStoreCoordinator,NSPersistentStore,SQLite,NSFileManager。后期的方向其实还有很多。

下个版本迭代

异步中得数据库

到现在为止,这个版本只能够在一个线程中访问,数据量大得情况下会卡主线程,按照我们老大的话来讲就是先等数据出来后再接入异步,程序总是在迭代中完善的。会用到的技术主要集中在contex中,主要用的策略还是单一Coordinator多context。

更加复杂的版本升级

现在自动升级能够应对绝大多数情况,然而如果修改了entity结构,那么需要更加精准的映射,就需要引入NSMigrationManager,NSMappingModel,NSEntityMapping等类来辅助完成了。

多样化的存储结构以及多个coredata的使用

当数据量达到一定级别后,当程序功能扩展后,为了逻辑上的清晰,工程的拆分,可能会在一个app中引入多个coredata,以及用不同的存储结构来持久化数据。

总结

在整个coredata的封装过程中其实面临了两个个核心问题:

  1. 是否要封装成一个大而全的工具?
  2. 是做一个中央管理式的工具,还是以实体对象来分散管理的数据库工具?

没有哪种方案是绝对的好坏,至少在现在这个节点上,用最少的投入带来最大的产出是比较有利的,同时也希望这个库能够越来越完善,把该有的NSPredicate,NSSortDescriptors便捷方法补全等。