CoreData和MJExtension结合遇到的问题

最近在学习CoreData,在之前的学习Demo中进行测试学习,由于项目已经存在,现在需要引入CoreData框架。需做如下修改:
1.需将已存在的model继承自NSManagedObject;
2.创建.xcdatamodeld映射文件;
3.添加需要使用CoreData的model实体(Entity);将Entity->Show the Data Model inspector->Codegen修改为Manual/None(即不自动生成实体类,因为model实体类已经存在了,如果修改的话会再次生成同名实体类,导致类文件重复错误)。


65C27B5A-512E-42F8-A73D-BF9FB1872EE1.png

实体类中NSArray、NSDictionary、NSData类型对应CoreData的Transformable类型。在与MJExtension结合使用时不能对属性为NSArray(保存自定义model)的字段采用映射文件关联关系方式。如果使用关联关系NSArray类型数

采用关联关系One To Many 时属性类型必须是NSSet而非NSArray;Transformable则可以使用NSArray;但当与MJExtension结合使用时,Transformable类型属性不能使用NSSet,会导致数据丢失。
总结(集合数据情况):
1.采用关联关系:使用NSSet类型定义属性,NSArray会崩溃;
2.Transformable类型:
a)普通情况:使用NSArray/NSSet定义属性;
b)与MJExtension结合使用:NSArray定义属性,NSSet会数据丢失。

CoreData本地虽然对数据进行了缓存,但查询返回数据为空。此时应采用Transformable类型。

#import "ZTCDBaseModel.h"
#import "UserInfo.h"


@interface User : ZTCDBaseModel

@property(nonatomic,copy) NSString*                     userName;
@property(nonatomic,copy) NSString*                     password;
@property(nonatomic,copy) NSArray<User*>*         childs;
@property(nonatomic,strong) UserInfo*                   info;
@end
3E2FE308-4A79-4D42-AE5B-CC9939191407.png

在使用Transformable类型时,实际上是将NSArray、NSDictionary对象转换成NSData进行存储,此时需要我们执行转换器,如果不指定在读取包含NSArray、NSDictionary类型属性(属性中保存自定义model)的model会报错

-[XXX initWithCoder:]: unrecognized selector sent to instance 0x60000091a180

系统为我们提供了NSValueTransformer转换器,在关系映射文件选中Transformable类型属性,在Show the Data Model inspector中的Value Transformer写入NSValueTransformer来指定转换器可以正常进行数据存取操作。


D981DE14-5D2F-440E-812D-4F6F58851AFC.png

但是NSValueTransformer是转换成NSData进行存取的,读取出的OC对象为NSData类型,需要再存进行转换,NSData->NSArray/NSDictionary。

每次读取然后转换比较麻烦,方便起见,我们可以自定义转换器,在转换器中统一处理。继承自NSValueTransformer类,重写一下方法:

- (nullable id)transformedValue:(nullable id)value;           // by default returns value
- (nullable id)reverseTransformedValue:(nullable id)value; 

例如:

#import "ZTCoreDataTransformer.h"

@implementation ZTCoreDataTransformer

/**
 转化方法实现(如:将OC对象转换成Sqlite可存储的对象-序列化过程)

 @param value 待转换数据
 @return 转换结果
 */
- (id)transformedValue:(id)value{
    if (value == nil) {
        return nil;
    }
    if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]){
        return [NSKeyedArchiver archivedDataWithRootObject:value];
    }
    return nil;
}

/**
 逆向转换实现(如:将Sqlite存储的对象转换成OC对象-反序列化过程)

 @param value 待转换数据
 @return 转换结果
 */
- (id)reverseTransformedValue:(id)value{
    if (value) {
       return [NSKeyedUnarchiver unarchiveObjectWithData:value];
    }
    return nil;
}
@end

然后将转换器指定为自定义的。

采用归档(NSKeyedArchiver)方式进行本地化存储的类还需要实现NSCoding协议。
1.一般model直接实现NSCoding协议即可;
使用MJExtension直接在model的实现中使用MJCodingImplementation宏即可;

2.当需要归档的model对象继承自NSManagedObject时,无法通过init进行初始化。

- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.userName forKey:@"userName"];
    [aCoder encodeObject:self.password forKey:@"password"];
    [aCoder encodeObject:self.childs forKey:@"childs"];
    [aCoder encodeObject:self.info forKey:@"info"];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    NSEntityDescription* descr = [NSEntityDescription entityForName:NSStringFromClass(self.class) inManagedObjectContext:self.class.shareManage];
    self = [[self.class alloc] initWithEntity:descr insertIntoManagedObjectContext:nil];
    if (self){
        self.userName = [aDecoder decodeObjectForKey:@"userName"];
        self.password = [aDecoder decodeObjectForKey:@"password"];
        self.childs = [aDecoder decodeObjectForKey:@"childs"];
        self.info = [aDecoder decodeObjectForKey:@"info"];
    }
    return self;
}

使用MJExtension时需自定义宏:

#define ZTCoreDataCodingImplementation \
- (id)initWithCoder:(NSCoder *)decoder \
{ \
NSEntityDescription* descr = [NSEntityDescription entityForName:NSStringFromClass(self.class) inManagedObjectContext:self.class.shareManage];\
self = [[self.class alloc] initWithEntity:descr insertIntoManagedObjectContext:nil];\
if (self) { \
[self mj_decode:decoder]; \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)encoder \
{ \
[self mj_encode:encoder]; \
}

在model实现(.m)文件中使用ZTCoreDataCodingImplementation宏即可。shareManage是CoreData的上下文。我自定义了一个BaseMode。

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import <MJExtension/MJExtension.h>

/**
 需要CoreData本地化的模型集成此类;需创建与boundle id最后名称一致的.xcdatamodeld映射文件
 */
@interface ZTCDBaseModel : NSManagedObject

+ (NSManagedObjectContext*)shareManage;

+ (id)ZT_JSONToModel:(id)JSON;

+ (id)ZT_fetchModel:(NSDictionary*)fetchParams;
@end
#import "ZTCDBaseModel.h"

@interface ZTCDBaseModel()

@end

@implementation ZTCDBaseModel

+ (NSManagedObjectContext*)shareManage{
    static NSManagedObjectContext* context;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        NSString* modelName = [[[NSBundle mainBundle] bundleIdentifier] componentsSeparatedByString:@"."].lastObject;
        NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:modelName ofType:@"momd"]];
        if (!url) {
            NSString* errorMsg = [NSString stringWithFormat:@"%@.xcdatamodeld文件不存在!!!",modelName];
            NSAssert(url,errorMsg);
        }
        NSManagedObjectModel* model = [[NSManagedObjectModel alloc] initWithContentsOfURL:url];
        NSPersistentStoreCoordinator* storeCoordicate = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
        NSString* dataPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject stringByAppendingFormat:@"/%@.sqlite",modelName];
        [storeCoordicate addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
        context.persistentStoreCoordinator = storeCoordicate;
        context.undoManager = nil;
    });
    return context;
}

+ (id)ZT_JSONToModel:(id)JSON{
    if (!JSON) {
        return nil;
    }
    id result = [JSON isKindOfClass:[NSArray class]] ? [self.class mj_objectArrayWithKeyValuesArray:JSON context:self.shareManage] : [self.class mj_objectWithKeyValues:JSON context:self.shareManage];
    NSError* error;
    [self.shareManage save:&error];
    return error ? nil : result;
}
@end

个人笔记,还没整理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容