Core Data详细解析(十二) —— 基于MagicalRecord框架的数据操作简单示例(一)

版本记录

版本号 时间
V1.0 2018.10.02 星期二

前言

数据是移动端的重点关注对象,其中有一条就是数据存储。CoreData是苹果出的数据存储和持久化技术,面向对象进行数据相关存储。感兴趣的可以看下面几篇文章。
1. iOS CoreData(一)
2. iOS CoreData实现数据存储(二)
3. Core Data详细解析(三) —— 一个简单的入门示例(一)
4. Core Data详细解析(四) —— 一个简单的入门示例(二)
5. Core Data详细解析(五) —— 基于多上下文的Core Data简单解析示例(一)
6. Core Data详细解析(六) —— 基于多上下文的Core Data简单解析示例(二)
7. Core Data详细解析(七) —— Core Data的轻量级迁移(一)
8. Core Data详细解析(八) —— Core Data的轻量级迁移(二)
9. Core Data详细解析(九) —— MagicalRecord框架之基本概览(一)
10. Core Data详细解析(十) —— MagicalRecord框架之基本使用(一)
11. Core Data详细解析(十一) —— MagicalRecord框架之基本使用(二)

工程建立

首先,我们需要建立一个工程,这里我们用的是MagicalRecord框架,所以心里工程时候询问是否使用Core Data那个复选框是不勾选的。

不要勾选Use Core Data

这样新的工程就建好了,然后我们要做的就是使用pod引入框架,如下所示:

pod 'MagicalRecord';

install之后我们就可以看见这个框架已经被下载下来了,下面我们就一起看一下这个框架的组织结构,如下:

框架结构

文件挺多的,分为两个文件夹CoreSupport Files,具体文件都是做什么的,这里就不细说了,后面有时间会和大家一起来看。

下一步就是创建xcdatamodeld文件,这个和建立普通文件类似,直接command + N,然后选择下面这个:

点击Next并给该模型命名好一个名字,我这里就是JJMagicalModel.xcdatamodeld。这样就生成了这个xcdatamodeld文件,

我们点击一下该文件,看右侧的文件属性页面,如下:

可以看见,这里的Code Generation默认是Swift,如果你用的是OC开发建立的工程,那么你就需要点击那个下拉框,选择OC,为什么要这么多,后面会和大家说。

xcdatamodeld文件建立好了以后,下面一步就是建立一个实体,实体Entity其实就相当于我们项目中的类,我们可以在一个xcdatamodeld文件中建立多个实体,并且可以不关联它们之间的任何关系。

这里为了演示方便,我只建立一个实体,并给与一个uid属性,如下:

然后我们就要利用我们建立的这个实体,自动化生成其对应的JJData+CoreDataClassJJData+CoreDataProperties文件。这两个文件都可以自动化生成,不需要手工建立。方法是选中这个JJData实体,然后按照下图进行选择就可以自动生成这两个文件了。

我们看一下这两个文件都是干什么的。

  • JJData+CoreDataClass其实集成自NSManagedObject,也就是说它的作用与NSManagedObject作用是一样的就是存储的每一个单元对象。
  • JJData+CoreDataPropertiesJJData的一个分类,里面是和你为实体建立的属性的关联文件和逻辑。
1. JJData+CoreDataClass.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

NS_ASSUME_NONNULL_BEGIN

@interface JJData : NSManagedObject

@end

NS_ASSUME_NONNULL_END

#import "JJData+CoreDataProperties.h"
2. JJData+CoreDataClass.m
#import "JJData+CoreDataClass.h"

@implementation JJData

@end
3. JJData+CoreDataProperties.h
#import "JJData+CoreDataClass.h"


NS_ASSUME_NONNULL_BEGIN

@interface JJData (CoreDataProperties)

+ (NSFetchRequest<JJData *> *)fetchRequest;

@property (nullable, nonatomic, copy) NSString *uid;

@end

NS_ASSUME_NONNULL_END
4. JJData+CoreDataProperties.m
#import "JJData+CoreDataProperties.h"

@implementation JJData (CoreDataProperties)

+ (NSFetchRequest<JJData *> *)fetchRequest {
    return [NSFetchRequest fetchRequestWithEntityName:@"JJData"];
}

@dynamic uid;

@end

注意:必须选中了该实体并且实体的属性已经设置了,才可以这么自动化生成,否则就是灰色不可点击的状态。而且该文件一旦建立,我们不需要在里面进行什么逻辑处理。

接下来我们又建立一个类JJDataManager,它是一个单例,用来对数据进行Core Data存储。

这样我们的工程就建立好了,就这几个文件。

接下来就是进行数据的操作了。


加载所有数据

首先看一下一些初始代码,后面的很多东西都是基于这个进行更改和变化的。

1. AppDelegate.m
#import "MagicalRecord.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    [MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"magicalTest.sqlite"];
    return YES;
}

- (void)applicationWillTerminate:(UIApplication *)application 
{
    [MagicalRecord cleanUp];
}
2. ViewController.m
#import "ViewController.h"
#import "JJDataManager.h"
#import "JJData+CoreDataClass.h"
#import <MagicalRecord/MagicalRecord.h>

@interface ViewController ()

@end

@implementation ViewController

#pragma mark -  Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self loadAllData];
}

//输出全部数据
- (void)loadAllData
{
    NSLog(@"arrM.count = %ld", [JJDataManager shareManager].dataListArrM.count);
    [[JJDataManager shareManager].dataListArrM enumerateObjectsUsingBlock:^(JJData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"obj.uid = %@", obj.uid);
    }];
}

@end
3. JJDataManager.h
#import <Foundation/Foundation.h>

@class JJData;

NS_ASSUME_NONNULL_BEGIN

@interface JJDataManager : NSObject

@property (nonatomic, strong) NSMutableArray <JJData *> *dataListArrM;

+ (instancetype)shareManager;

@end

NS_ASSUME_NONNULL_END
4. JJDataManager.m
#import "JJDataManager.h"
#import <MagicalRecord/MagicalRecord.h>
#import "JJData+CoreDataClass.h"

@implementation JJDataManager

#pragma mark -  Override Base Function

- (id)init
{
    self = [super init];
    if (self) {
        [self loadData];
    }
    return self;
}

#pragma mark -  Object Private Function

//保存数据
- (void)persistMessage
{
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:nil];
}

//加载全部数据
- (void)loadData
{
    NSArray *dataArr = [JJData MR_findAllWithPredicate:nil inContext:[NSManagedObjectContext MR_defaultContext]];
    if (dataArr.count > 0) {
        self.dataListArrM = [NSMutableArray <JJData *> arrayWithArray:dataArr];
    }
    else {
        self.dataListArrM = [NSMutableArray <JJData *> array];
    }
}

#pragma mark -  Class Public Function

+ (nonnull instancetype)shareManager
{
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}

#pragma mark -  Getter && Setter


@end

这些就是初始代码,在- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event中进行逻辑调用,这里首先是加载所有数据loadAllData,运行起来如下输出:

2018-10-02 14:09:01.704056+0800 JJMagicRecord[1907:527921] arrM.count = 0

可以看见,这个数组个数为0没有数据,对了,还没有往里面存数据当然没有数据了,下面我们就看一下存储数据。


存储数据

还是在ViewController.m添加一个私有方法。

//保存数据
- (void)savaData
{
    JJData *userData = [[JJData alloc] initWithContext:[NSManagedObjectContext MR_defaultContext]];
    userData.uid = @"10004";
    [[JJDataManager shareManager] saveDataWithUser:@"10005"];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self loadAllData];
    });
}

这里可以看见,在存储数据之后延时2s读取所有数据用来查看对不对。

然后在JJDataManager.m中添加如下方法:

//保存数据
- (void)saveDataWithUser:(NSString *)userData
{
    if (userData.length > 0) {
        JJData *userDataEntity = [JJData MR_createEntityInContext:[NSManagedObjectContext MR_defaultContext]];
        userDataEntity.uid = userData;
        [self persistMessage];
        [self.dataListArrM addObject:userDataEntity];
    }
}

并将- (void)saveDataWithUser:(NSString *)userData方法暴露给VC调用,然后添加一个私有的对象方法persistMessage用来实现最终的存储逻辑。

//保存数据
- (void)persistMessage
{
    [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:nil];
}

进行如下调用,并运行

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self savaData];
}

你会发现崩溃了,输出如下所示:

2018-10-02 14:15:58.635473+0800 JJMagicRecord[1911:528876] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JJData setUid:]: unrecognized selector sent to instance 0x280c82600'
*** First throw call stack:
(0x1fff07ef8 0x1ff0d5a40 0x1ffe1f154 0x1fff0d810 0x1fff0f4bc 0x100de9a58 0x100de99bc 0x22cb3cd80 0x22cb3cc18 0x22cf295b8 0x22cf2aa38 0x22c7a6d50 0x22c6e28e0 0x22c6e534c 0x22c6ddee0 0x1ffe965b8 0x1ffe96538 0x1ffe95e1c 0x1ffe90ce8 0x1ffe905b8 0x202104584 0x22c78b558 0x100de9eb8 0x1ff950b94)
libc++abi.dylib: terminating with uncaught exception of type NSException

大家还记得JJData这个吗?这个就是实体Entity,前面还自动化生成其properties和class两个文件,这里就是说JJData不支持setter方法。

userData.uid = @"10000";

那么我们如何将uid带过去并给manager进行存储呢?

如果我们换一种方式进行实例化呢?比如下面这种方式:

JJData *userData = [[JJData alloc] initWithContext:[NSManagedObjectContext MR_defaultContext]];

大家会发现不崩溃了,2s后读取存储的数据,会发现一个新的问题,这里数据存储了2次,也就是说存储重复,输出如下所示:

2018-10-02 14:22:16.775774+0800 JJMagicRecord[1914:529666] arrM.count = 2
2018-10-02 14:22:20.163538+0800 JJMagicRecord[1914:529666] obj.uid = 10000
2018-10-02 14:22:20.164466+0800 JJMagicRecord[1914:529666] obj.uid = 10000

这里,我们这种方式是不可以的。其实真正应该是如果实体的属性很多的话,应该在VC实例化一个具有这个数据的对象(不要与JJData这样的实体同名),主要是对象并且具有这些数据就可以,然后送到Manager里面,在下面创建完实体后一个一个属性进行复制然后存储,我这里只有一个属性,因此不用对象,直接穿进去一个字符串就可以。

JJData *userDataEntity = [JJData MR_createEntityInContext:[NSManagedObjectContext MR_defaultContext]];
userDataEntity.uid = userData;

所以具体代码应该更改为:

//保存数据
- (void)savaData
{
    [[JJDataManager shareManager] saveDataWithUser:@"10001"];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self loadAllData];
    });
}

这里我们存一个10001,看是否还重复,删掉App重新运行,看下面输出:

2018-10-02 14:28:58.020827+0800 JJMagicRecord[1917:530485] arrM.count = 1
2018-10-02 14:28:58.021403+0800 JJMagicRecord[1917:530485] obj.uid = 10001

可以看见,这回就不重复了。


删除所有数据

下面我们就看删除所有数据。

在VC中添加如下私有方法

//删除所有数据
- (void)deleteAllData
{
    NSLog(@"删除前 arrM.count = %ld", [JJDataManager shareManager].dataListArrM.count);
    [[JJDataManager shareManager] deleteAllData];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self loadAllData];
    });
}

在manager中也添加一个方法并暴露出来

//删除所有数据
- (void)deleteAllData
{
    [self.dataListArrM enumerateObjectsUsingBlock:^(JJData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj MR_deleteEntityInContext:[NSManagedObjectContext MR_defaultContext]];
        [self persistMessage];
    }];
    [self.dataListArrM removeAllObjects];
}

在VC中进行如下调用:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self deleteAllData];
}

我们看一下输出结果:

2018-10-02 14:37:17.983032+0800 JJMagicRecord[1925:532190] 删除前 arrM.count = 1
2018-10-02 14:37:17.985232+0800 JJMagicRecord[1925:532190] → Saving <NSManagedObjectContext (0x2832e8780): MagicalRecord Default Context> on the main thread
2018-10-02 14:37:17.991855+0800 JJMagicRecord[1925:532220] → Saving <NSManagedObjectContext (0x2832e8690): MagicalRecord Root Saving Context> on a background thread
2018-10-02 14:37:19.988450+0800 JJMagicRecord[1925:532190] arrM.count = 0

大家可以看见,再删除之前是有一个数据的,就是我们前面保存的那个10001,2s延时后数据已经空了,也就是说所有的数据都清空了。


删除部分数据

前面有了删除所有数据,那么这里删除部分数据就很好弄了,判断一下就可以了,如果保存的数据里面有你要删除的部分数据,那么就进行删除操作,如果没有的话就不做处理就可以了,逻辑很简单,但是还是写一写吧。

在进行处理之前,先调用前面的保存数据方法保存三条数据:

2018-10-02 14:52:13.737695+0800 JJMagicRecord[1932:534054] obj.uid = 10000
2018-10-02 14:52:13.738100+0800 JJMagicRecord[1932:534054] obj.uid = 10001
2018-10-02 14:52:13.738455+0800 JJMagicRecord[1932:534054] obj.uid = 10003

下面我们就删除10000这条数据:

在VC中添加如下代码:

//删除所有数据
- (void)deleteAllData
{
    NSLog(@"删除前 arrM.count = %ld", [JJDataManager shareManager].dataListArrM.count);
    [[JJDataManager shareManager] deleteAllData];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self loadAllData];
    });
}

在manager中添加如下代码:

//删除部分数据
- (void)deletadataWithId:(NSString *)uidStr
{
    [self.dataListArrM enumerateObjectsUsingBlock:^(JJData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([uidStr isEqualToString:obj.uid]) {
            [obj MR_deleteEntityInContext:[NSManagedObjectContext MR_defaultContext]];
            [self.dataListArrM removeObject:obj];
            [self persistMessage];
        }
    }];
}

在VC中进行如下调用:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self deletadataWithId:@"10000"];
}

下面看一下输出结果

2018-10-02 14:53:32.306309+0800 JJMagicRecord[1935:534426] 删除前  arrM.count = 3
2018-10-02 14:53:32.306649+0800 JJMagicRecord[1935:534426] 删除前 obj.uid = 10000
2018-10-02 14:53:32.306736+0800 JJMagicRecord[1935:534426] 删除前 obj.uid = 10001
2018-10-02 14:53:32.306783+0800 JJMagicRecord[1935:534426] 删除前 obj.uid = 10003
2018-10-02 14:53:32.307214+0800 JJMagicRecord[1935:534426] → Saving <NSManagedObjectContext (0x281634690): MagicalRecord Default Context> on the main thread
2018-10-02 14:53:32.309076+0800 JJMagicRecord[1935:534439] → Saving <NSManagedObjectContext (0x2816345a0): MagicalRecord Root Saving Context> on a background thread
2018-10-02 14:53:34.464586+0800 JJMagicRecord[1935:534426] arrM.count = 2
2018-10-02 14:53:34.465016+0800 JJMagicRecord[1935:534426] obj.uid = 10001
2018-10-02 14:53:34.465190+0800 JJMagicRecord[1935:534426] obj.uid = 10003

可见,10000数据就被删除了。


更新数据

其实更新数据和删除数据有点像,就在于他们都有查找的过程,删除就是找到了删掉,更新数据其实就是找到了更新成我们想要的值或者结果就可以了。

下面我们就将10001更新为10002

在VC中添加如下代码:

//更新数据
- (void)updateDataWithOriginalUid:(NSString *)uidStr newValue:(NSString *)newUidStr
{
    NSLog(@"更新前  arrM.count = %ld", [JJDataManager shareManager].dataListArrM.count);
    [[JJDataManager shareManager].dataListArrM enumerateObjectsUsingBlock:^(JJData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"更新前 obj.uid = %@", obj.uid);
    }];
    
    [[JJDataManager shareManager] updateDataWithOriginalUid:uidStr newValue:newUidStr];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self loadAllData];
    });
}

在manager中添加如下代码:

//更新数据
- (void)updateDataWithOriginalUid:(NSString *)uidStr newValue:(NSString *)newUidStr
{
    [self.dataListArrM enumerateObjectsUsingBlock:^(JJData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([uidStr isEqualToString:obj.uid]) {
            JJData *userDataEntity = [JJData MR_createEntityInContext:[NSManagedObjectContext MR_defaultContext]];
            userDataEntity.uid = newUidStr;
            [self persistMessage];
            [self.dataListArrM removeObject:obj];
            [self.dataListArrM addObject:userDataEntity];
        }
    }];
}

在VC中进行如下调用:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self updateDataWithOriginalUid:@"10001" newValue:@"10002"];
}

下面看一下输出结果

2018-10-02 15:25:14.462090+0800 JJMagicRecord[1942:537761] 更新前  arrM.count = 2
2018-10-02 15:25:14.462488+0800 JJMagicRecord[1942:537761] 更新前 obj.uid = 10001
2018-10-02 15:25:14.462590+0800 JJMagicRecord[1942:537761] 更新前 obj.uid = 10003
2018-10-02 15:25:14.463154+0800 JJMagicRecord[1942:537761] → Saving <NSManagedObjectContext (0x2816ac780): MagicalRecord Default Context> on the main thread
2018-10-02 15:25:14.465258+0800 JJMagicRecord[1942:537774] → Saving <NSManagedObjectContext (0x2816ac690): MagicalRecord Root Saving Context> on a background thread
2018-10-02 15:25:16.618887+0800 JJMagicRecord[1942:537761] arrM.count = 2
2018-10-02 15:25:16.619310+0800 JJMagicRecord[1942:537761] obj.uid = 10003
2018-10-02 15:25:16.622405+0800 JJMagicRecord[1942:537761] obj.uid = 10002

可以看见结果已经将10001更新为10002了。

后记

本篇主要讲述了基于MagicalRecord框架的数据操作简单示例,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容