CoreData + 多线程 + 工厂模式 + 单例模式

文章出处,原创于 http://HawkingOuYang.com/

我的GitHub


© OYXJ

并发编程:API 及挑战



Core Data Lightweight Migration

Ref:

(1)http://www.objc.io/issue-4/core-data-migration.html

(2)http://stackoverflow.com/questions/1830079/iphone-core-data-automatic-lightweight-migration (这个好,具体到步骤)

(3)http://www.raywenderlich.com/86136/lightweight-migrations-core-data-tutorial

(4)http://www.raywenderlich.com/27657/how-to-perform-a-lightweight-core-data-migration

(5)https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html

(6)http://stackoverflow.com/questions/13897348/managed-object-in-core-data-entity-causing-is-not-key-value-coding-compliant-fo

(7)http://stackoverflow.com/questions/2730832/how-can-i-duplicate-or-copy-a-core-data-managed-object

(8)http://stackoverflow.com/questions/3256195/how-to-deal-with-temporary-nsmanagedobject-instances

CoreData数据库轻量迁移

CoreData数据库轻量迁移,针对于同一个iOSApp在版本升级(更新安装App)的过程中,需要注意的数据库迁移;如果不进行数据库迁移,则会导致,覆盖安装闪退。这里说的数据库迁移,特指:CoreData轻量数据库迁移。

CoreData轻量数据库迁移,具体步骤如下:

iPhone Core Data “Automatic Lightweight Migration”

http://stackoverflow.com/questions/1830079/iphone-core-data-automatic-lightweight-migration

To recap/Full guide:

1、 Before making any change, create a new model version.

In Xcode 4: Select your .xcdatamodel –>> Editor –>> Add Model Version.

In Xcode 3: Design –>> Data Model –>> Add Model Version.

You will see that a new .xcdatamodel is created in your .xcdatamodeld folder (which is also created if you have none).

2、 Save.

3、 Select your new .xcdatamodel and make the change you wish to employ in accordance withthe Lightweight Migration documentation.

4、 Save.

5、 Set the current/active schema to the newly created schema.

With the .xcdatamodeld folder selected:

In Xcode 4: Utilities sidebar –>> File Inspector –>> Versioned Core Data Model –>> Select the new schema.

In Xcode 3: Design –>> Data Model –>> Set Current Version.

The green tick on the .xcdatamodel icon will move to the new schema.

6、 Save.

7、 Implement the necessary code to perform migration at runtime.

Where your NSPersistentStoreCoordinator is created (usually AppDelegate class), for the options parameter, replace nil with the following code:

1
2
3
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]

8、 Run your app. If there’s no crash, you’ve probably successfully migrated :)

9、 When you have successfully migrated, the migration code (step 7) can be removed. (It is up to the developer to determine when the users of a published app can be deemed to have migrated.)

IMPORTANT: Do not delete old model versions/schemas. Core Data needs the old version to migrate to the new version.

中文如下:

1、选中DB.xcdatamodeld (对,那个绿色钩)

2、Xcode工具栏中,Editor —> Add Model Version
特别注意:先备份,再更改数据库。
v0.6.0_20150828_01.xcdatamodel
其中:v0.6.0 —> 数据库版本号
20150828 —> 日期,即哪天改动的。
01 —> 上面这个日期的第几次更改数据

表结构
一般来说:
数据表schema 由iOS开发组的一个成员来维护,以便减少 数据表schema 更改的次数。
特别来说:
对于已经上架AppStore的版本,一定要做好CoreData的数据迁移,否则在覆盖安装之后,app重新launch(启动)的时候,会闪退。

3、更改表结构,即编辑DB.xcdatamodeld文件。

特别说明:

如果 跳过第二步:2、Xcode工具栏中,Editor —> Add Model Version
直接到第三部:3、更改表结构,即编辑DB.xcdatamodeld文件。
已经导致:覆盖安装崩溃。

补救方法如下:

1、不管你是用Git还是SVN,回到你更改数据库之前的一个时间点(代码的snapshot),在那个时候备份数据模型文件(即对DB.xcdatamodeld 增加Model Version)。

2、把这个增加的Model Version,添加到现在的DB.xcdatamodeld中。

测试验证:覆盖安装,不崩溃。

CoreData使用参考

深入浅出 Cocoa 之 Core Data(1)- 框架详解

IOS学习笔记16——Core Data

iOS Coredata安全之多线程

iphone开发之数据库CoreData

iOS 开发中使用 Core Data 应避免的十个错误

iphone数据存储之-- Core Data的使用(一)

IOS数据持久化–Core Data(一)

CoreData使用特别注意

1、每个NSThread实例,必须有各自的NSManagedObjectContext实例,该Context实际上是把db.sqlite文件读取到内存中(使用fault这种内存策略,建立索引,以减小内存占用)。

2、CoreData的数据库实体(即 Entity,继承自NSManagedObject),存在于各自所属NSThread实例的NSManagedObjectContext实例中,并且Entity 不能 跨线程传递;如果 一定要 跨线程 传递Entity,请传递该Entity的主键(比如 myEntity.entityUUID),然后在 另一个线程 使用 myEntity.entityUUID 从这个所谓 另一个线程 的Context,查找到这个Entity。

3、临时Entity数据,即 不打算 持久化的Entity,请使用 临时的Context,并且一定不能 [临时Context save]

4、Entity深拷贝?上面有代码。

5、CoreData 数据库迁移?CoreData 关系建立与移除?上面链接有。

CoreData多线程数据合并、多线程数据同步
1
2
3
4
5
6
/**
Updates the persistent properties of a managed object to use the latest values from the persistent store.
If flag is YES, this method does not affect any transient properties; if flag is NO, transient properties are disposed of.
*/
[self refreshObject:obj mergeChanges:YES];//参数YES

业务管理类的抽象基类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* .h */
/*! @brief 业务管理类 的抽象基类
* @author modify by OYXJ
*/
@interface AbstractManager : NSObject
/**
* @note 相信我,`operationQueue` 不会为 nil 。
* @attention 特别地,使用当前 `操作队列`, 进行 `读取、写入(特别注意写入) CoreData数据库的操作`。
* @see BusinessBlockOperation
* @see BusinessSelectorOperation
* @see 文档:`CoreData + 多线程 + 工厂模式 + 单例模式`
*/
@property (weak,readonly) CustomOperationQueue *operationQueue;
自定义操作(基于:Block)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* .h */
/**
自定义操作(基于:Block),说明 by OYXJ on 2016.08.30
@note 当前类依赖于这个方法:
合并数据
合并对象:调用这个方法后,当前线程上下文将合并其它线程有更改的对象。
此函数一般用在运行时间比较长的线程,比如循环比较多的线程。
[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
@attention 特别地,使用当前类处理 `读取、写入(特别注意写入) CoreData数据库的操作`,因为:
当前类 在执行 block 之前,
会调用 [[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
从而 使得多个线程之间 的数据完成同步。
*/
@interface BusinessBlockOperation : NSOperation
/**
* ARC, block will be copied
*
* @param block block will be copied by `the returned object of current method`
*
* @return an initialized instance of current class.
*
* @attention please add the return `operation object` to an operation queue.
*
* @attention Specially,be sure that `block` will be executed synchronously.
* @attention 特别注意:请确保 `block` 将会 同步执行的。
* @attention 特别地,使用当前类处理 `读取、写入(特别注意写入) CoreData数据库的操作`。
*/
+ (nullable instancetype)blockOperationWithBlock:(void (^ _Nonnull)(void))block;
@end
自定义操作(基于:Selector)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* .h */
/**
自定义操作(基于:Selector),说明 by OYXJ on 2016.08.30
@note 当前类依赖于这个方法:
合并数据
合并对象:调用这个方法后,当前线程上下文将合并其它线程有更改的对象。
此函数一般用在运行时间比较长的线程,比如循环比较多的线程。
[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
@attention 特别地,使用当前类处理 `读取、写入(特别注意写入) CoreData数据库的操作`,因为:
当前类 在执行 target的sel方法 之前,
会调用 [[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
从而 使得多个线程之间 的数据完成同步。
*/
@interface BusinessSelectorOperation : NSOperation
/**
* ARC, strong reference to param objs
*
* @param target strong referenced by `the returned object of current method`
* @param sel not referenced by `the returned object of current method`
* @param arg strong referenced by `the returned object of current method`, can be nil
*
* @return an initialized instance of current class.
*
* @attention please add the return `operation object` to an operation queue.
*
* @attention Specially,be sure that `sel` will be executed synchronously.
* @attention 特别注意:请确保 `sel` 将会 同步执行的。
* @attention 特别地,使用当前类处理 `读取、写入(特别注意写入) CoreData数据库的操作`。
*/
- (nullable instancetype)initWithTarget:(id _Nonnull __strong)target
selector:(SEL _Nonnull)sel
object:(id _Nullable __strong)arg;
@end
自定义操作队列
1
2
3
4
5
6
7
8
9
10
11
12
/* .h */
//! 自定义操作队列
@interface CustomOperationQueue : NSOperationQueue
/**
@note 单例
@attention 特别地,使用当前类处理 `读取、写入(特别注意写入) CoreData数据库的操作`,
因为:CoreData 多线程,线程之间数据的同步,多个线程上下文的性能。
*/
+ (CustomOperationQueue *)sharedCustomOperationQueue;
举个🌰(栗子 li zi ) for example
1
2
3
4
5
6
7
8
9
#import <CAbstractManager/BusinessBlockOperation.h> //block写数据库(CoreData)
//! 在后台线程,将所有 从服务端抓取的通话记录 保存到本地数据库中
BusinessBlockOperation *bOP = [BusinessBlockOperation blockOperationWithBlock:^{
// 将所有 从服务端抓取的通话记录 保存到本地数据库中
[[[BusinessManager sharedManager] callRecordManager] saveCallRecordsThatFetchedFromServerAsDic:aDic];
}];
// 将所有 从服务端抓取的通话记录 保存到本地数据库中
[[[BusinessManager sharedManager] callRecordManager].operationQueue addOperation:bOP];

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_1

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_2

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_3

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_4

栗子 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//试点1:写入数据库
#import <CAbstractManager/BusinessBlockOperation.h> //block写数据库(CoreData)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//! 在后台线程,将所有 从服务端抓取的通话记录 保存到本地数据库中
[[[BusinessManager sharedManager] callRecordManager] saveCallRecordsThatFetchedFromServerAsDic:aDic];
});
//改成:
//! 在自定义操作队列:将所有 从服务端抓取的通话记录 保存到本地数据库CoreData
BusinessBlockOperation *bOP = [BusinessBlockOperation blockOperationWithBlock:^{
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
// 将所有 从服务端抓取的通话记录 保存到本地数据库CoreData
[[[BusinessManager sharedManager] callRecordManager] saveCallRecordsThatFetchedFromServerAsDic:aDic];
}];
// 将所有 从服务端抓取的通话记录 保存到本地数据库CoreData
[[[BusinessManager sharedManager] callRecordManager].operationQueue addOperation:bOP];
栗子 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//试点2:网络请求,回调 写入数据库
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 读写 本地数据库CoreData
HTmAccountEntity *currentAccountEntity = [[[BusinessManager sharedManager] systemAccountManager] currentLoginSystemAccount];
if (currentAccountEntity.portraitURL.length > 0) {// 获取网络头像URL
// `逻辑层` 传递参数到 `网络层`,使用 字典(易于扩展) 或者 数据模型(编写时、编译时 有类型检查),我选“字典”。
NSDictionary *dic = @{@"URLString" : aPortraitURL?:@"" , //头像图片 URL地址
@"fileAbsolutePath" : tmp_aFileAbsolutePath?:@"" //头像图片 绝对路径,下载之后保存在本地的路径(临时文件)
};
// processor属于`处理层`(捕获异常 放在 `网络层` 做吧),可以 同步 或者 异步,看具体业务啦~
[processor requestService_query_portraitInfo:dic success:^(NSDictionary *serverResponse) {
/*
注意:如果此处,要 读写CoreData数据库,那么取消注释 此代码。
// 合并对象:调用这个方法后,当前线程上下文将合并其它线程有更改的对象。
[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
*/
// handle success
// DDLogVerbose (Debug时用)
}failure:^(NSDictionary *errorDic) {
// handle error
// DDLogError (Debug/Release时用,然后 `反馈日志` 定时上传)
}];
}else{
dispatch_async(dispatch_get_main_queue(), ^{
if (failure) {
//人为构造 errorDic:获取网络头像URL为nil
failure(errorDic); // TODO: errorDic
}
});
}
});
//改成:
//! 在自定义操作队列:读写 本地数据库CoreData
BusinessBlockOperation *bOP = [BusinessBlockOperation blockOperationWithBlock:^{
// processor属于`处理层`(捕获异常 放在 `网络层` 做吧),可以 同步 或者 异步,看具体业务啦~
[processor requestService_query_portraitInfo:dic success:^(NSDictionary *serverResponse) {
//! 在自定义操作队列:读写 本地数据库CoreData
BusinessBlockOperation *blockOP = [BusinessBlockOperation blockOperationWithBlock:^{
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
if ([serverResponse[@"ResultCode"] isEqualToString:@"0"]) //此App中,所有网络请求成功statusCode用字符串@"0",要不要 使用数据模型呢?用起来好一些,后期再优化,独立开发App,时间来不及了。
{//<-- 解析数据 begin-->//
NSDictionary *responseObject = serverResponse[@"ResponseObject"];//此App中,所有网络请求成功response数据用字典KEY为@"ResponseObject",要不要 使用数据模型呢?用起来好一些,后期再优化,独立开发App,时间来不及了。
HTmAccountEntity *curAccountEntity = [[[BusinessManager sharedManager] systemAccountManager] currentLoginSystemAccount];
//! 更新 头像图片的 本地相对路径
curAccountEntity.portraitPath = [fileAbsolutePath lastPathComponent];
[accessor save]; /* 每个业务管理Manager有个 accessor (数据存取器)*/
}//<-- 解析数据 end-->//
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(serverResponse);
}
});// <-- dispatch async -- main queue --> //
}];//<— 在自定义操作队列:读写 本地数据库CoreData —>//
// 在自定义操作队列:读写 本地数据库CoreData
[[[BusinessManager sharedManager] systemAccountManager].operationQueue addOperation:blockOP];
} failure:^(NSDictionary *errorDic) {
dispatch_async(dispatch_get_main_queue(), ^{
if (failure) {
failure(errorDic);
}
});// <-- dispatch async -- main queue --> //
}];
}];// <-- 在自定义操作队列:读写 本地数据库CoreData --> //
// 在自定义操作队列:读写 本地数据库CoreData
[[[BusinessManager sharedManager] systemAccountManager].operationQueue addOperation:bOP];

这个例子好长,来,看两张美图,放松一下😌。

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_5

iOS-CoreData-MultiThreading-FactoryPattern-Singleton_6

栗子 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#import <CAbstractManager/BusinessBlockOperation.h> //block写数据库(CoreData)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 合并对象:调用这个方法后,当前线程上下文将合并其它线程有更改的对象。
[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
// do something here ....
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(responseDic);
}
});// <-- dispatch async -- main queue --> //
});
// 改成:
//! 在自定义操作队列:读写 本地数据库CoreData
BusinessBlockOperation *bOP = [BusinessBlockOperation blockOperationWithBlock:^{
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
// do something here ...
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(responseDic);
}
});// <-- dispatch async -- main queue --> //
}];
// 在自定义操作队列:读写 本地数据库CoreData
[[[BusinessManager sharedManager] systemAccountManager].operationQueue addOperation:bOP];
// 从
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
//do something
});
// 改成:
//! 在自定义操作队列:读写 本地数据库CoreData
BusinessBlockOperation *bOP = [BusinessBlockOperation blockOperationWithBlock:^{
/*
特别注意:请确保 读写CoreData数据库的 `block` 将会 同步执行的。
*/
//do something
}];//<—— 在自定义操作队列:读写 本地数据库CoreData ——>//
// 在自定义操作队列:读写 本地数据库CoreData
[[[BusinessManager sharedManager] systemAccountManager].operationQueue addOperation:bOP];
CoreData 谓词NSPredicate

nshipster.cn NSPredicate NSPredicate是一个Foundation类,它指定数据被获取或者过滤的方式。它的查询语言就像SQL的WHERE和正则表达式的交叉一样,提供了具有表现力的,自然语言界面来定义一个集合被搜寻的逻辑条件。

NSPredicate that is the equivalent of SQL’s LIKE

Regular Expressions

nshipster.cn NSExpression

KVC Collection Operators

Core Data, NSPredicate and to-many key (CoreData数据关系 的 谓词1)

1
2
3
NSPredicate * dayIsNotExcludedPredicate = [NSPredicate predicateWithFormat:
@"excludedDays.@count == 0 || (excludedDays.@count > 0 && NONE excludedDays.day == %@))",
today];

How to correctly setup a NSPredicate for a to-many relationship when using Core Data? (CoreData数据关系 的 谓词2)

1
2
3
[NSPredicate predicateWithFormat:
@"(excludedOccurrences.@count == 0) OR (0 == SUBQUERY(excludedOccurrences, $sub, $sub.day == %@).@count)",
today]

What’s better way to build NSPredicate with to-many deep relationships? (CoreData数据关系 的 谓词3)

CoreData的关系型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*! @brief 联系人-数据模型
* @author OuYangXiaoJin 2016.03.21
*/
@interface HTmContactEntity : BaseManagedObject
// 这张table的其他field
// 下面是这张table与其他table的数据关系
//! 数据关系:当前联系人 所属 群组 (to-many)
@property (nonatomic, strong) NSSet<HTmGroupEntity*> *belongGroups;
//! 数据关系:当前联系人 包含 callee通话记录(to-many)
@property (nonatomic, strong) NSSet<HTmCallRecordEntity*> *hasCalleeRecords;
//! 数据关系:当前联系人 包含 caller通话记录(to-many)
@property (nonatomic, strong) NSSet<HTmCallRecordEntity*> *hasCallerRecords;
//! 数据关系:当前联系人 拥有 已经绑定的设备(to-many)
@property (nonatomic, strong) NSSet<HTmBindDeviceEntity *> *hasBindDevices;
@end
/*! @brief CoreData关系型数据:联系人HTmContactEntity 和 其它Entity 的关系。
* @author OuYangXiaoJin 2016.04.13
*/
@interface HTmContactEntity (CoreDataGeneratedAccessors)
//! 数据关系:当前联系人 所属 群组
- (void)addBelongGroupsObject:(HTmGroupEntity *)value;
- (void)removeBelongGroupsObject:(HTmGroupEntity *)value;
- (void)addBelongGroups:(NSSet *)values;
- (void)removeBelongGroups:(NSSet *)values;
//! 数据关系:当前联系人 包含 callee通话记录
- (void)addHasCalleeRecordsObject:(HTmCallRecordEntity *)value;
- (void)removeHasCalleeRecordsObject:(HTmCallRecordEntity *)value;
- (void)addHasCalleeRecords:(NSSet *)values;
- (void)removeHasCalleeRecords:(NSSet *)values;
//! 数据关系:当前联系人 包含 caller通话记录
- (void)addHasCallerRecordsObject:(HTmCallRecordEntity *)value;
- (void)removeHasCallerRecordsObject:(HTmCallRecordEntity *)value;
- (void)addHasCallerRecords:(NSSet *)values;
- (void)removeHasCallerRecords:(NSSet *)values;
//! 数据关系:当前联系人 拥有 已经绑定的设备
- (void)addHasBindDevicesObject:(HTmBindDeviceEntity *)value;
- (void)removeHasBindDevicesObject:(HTmBindDeviceEntity *)value;
- (void)addHasBindDevices:(NSSet *)values;
- (void)removeHasBindDevices:(NSSet *)values;
@end
/**
移除 数据关系 (many-many)
*/
[obj removeBelongAccountsObject: currentAccountEntity];
[currentAccountEntity removeHasBindDevicesObject: obj];
/**
建立 数据关系 (many-many)
*/
[currentAccountEntity addHasBindDevicesObject: aBdEntity];
[aBdEntity addBelongAccountsObject: currentAccountEntity];
CoreData的Entity深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*! @brief 深拷贝
*
* @param source 拷贝源。
* @param context 上下文,可以为nil。
*
* @return 实体,使用时请强制转换成 相应的实体。
*/
+ (NSManagedObject *)clone:(NSManagedObject *)source
inContext:(NSManagedObjectContext *)context
{
// 预处理
if (nil == context) {//参数context上下文为nil,则与拷贝源source使用同一个上下文。
context = [source managedObjectContext];
}
if (nil == [source managedObjectContext]) {//拷贝源source的上下文不存在,说明此拷贝源source 已经是拷贝的了。
return source;
}
/*
How can I duplicate, or copy a Core Data Managed Object?
Ref.: http://stackoverflow.com/questions/2730832/how-can-i-duplicate-or-copy-a-core-data-managed-object
*/
/*
//注释这里。不能把 克隆的数据cloned 插入 source的上下文context,因为会导致上下文context中 数据重复。
//create new object in data store
NSManagedObject *cloned = [NSEntityDescription
insertNewObjectForEntityForName:entityName
inManagedObjectContext:context];
*/
/*
How to Deal with Temporary NSManagedObject instances?
Ref.: http://stackoverflow.com/questions/3256195/how-to-deal-with-temporary-nsmanagedobject-instances
*/
NSEntityDescription *entity = [source entity];
NSManagedObject *cloned = [[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:nil];
NSString *entityName = [[source entity] name];
//loop through all attributes and assign then to the clone
NSDictionary *attributes = [[NSEntityDescription
entityForName:entityName
inManagedObjectContext:context] attributesByName];
for (NSString *attr in attributes) {
[cloned setValue:[source valueForKey:attr] forKey:attr];
}
//Loop through all relationships, and clone them.
NSDictionary *relationships = [[NSEntityDescription
entityForName:entityName
inManagedObjectContext:context] relationshipsByName];
for (NSRelationshipDescription *rel in relationships){
NSString *keyName = [NSString stringWithFormat:@"%@",rel];
//get a set of all objects in the relationship
NSMutableSet *sourceSet = [source mutableSetValueForKey:keyName];
NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName];
NSEnumerator *e = [sourceSet objectEnumerator];
NSManagedObject *relatedObject;
while ( relatedObject = [e nextObject]){
//Clone it, and add clone to set
NSManagedObject *clonedRelatedObject = [self clone:relatedObject
inContext:context];
[clonedSet addObject:clonedRelatedObject];
}
}
return cloned;
}

CoreData实体Entity深度拷贝(每一个‘字段’、每一个‘数据关系’),Context上下文 数据重复,导致PersistentStoreFile数据库重复 —> fix BUG —> 自测 通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#pragma mark - 实体更新 - private
/*! @brief 实体更新:把 sourceEntity实体的所有字段 覆盖 destinationEntity实体的所有字段 (除了attributeNames这些字段)。
*
* @param sourceEntity 源头 数据实体。
* @param context 源头 数据实体,所在的 上下文。
* @param destinationEntity 目标 数据实体。
* @param attributeNames 跳过 这些字段。
*
* @return 把 sourceEntity实体的所有字段 覆盖 destinationEntity实体的所有字段 (除了attributeNames这些字段);
若 成功,则YES;否则 NO。
*/
+ (BOOL)p_cloneSourceEntity:(NSManagedObject *_Nonnull)sourceEntity
inContext:(NSManagedObjectContext *_Nullable)sourceEntityInContext
intoDestinationEntity:(NSManagedObject *_Nonnull __strong *_Nonnull)destinationEntity
insertInContext:(NSManagedObjectContext *_Nonnull __strong *_Nonnull)destinationEntityInsertContext
ignoreAttributes:(NSArray<NSString*> *_Nullable)ignoreAttributeNames
relationshipDescription:(NSMutableArray<NSRelationshipDescription*> *_Nonnull __strong *_Nonnull)hasLoopThroughRelationshipDescription;
{
BOOL isSuccess = NO;
/**
注意:&errorOnMain,是double pointer。
How do pointer to pointers work in C? ------ double pointer
http://stackoverflow.com/questions/897366/how-do-pointer-to-pointers-work-in-c
by OYXJ 2015-09-02
*/
NSManagedObject *source = sourceEntity; // 注意,这里一定是 strong reference。
NSManagedObject *cloned = *destinationEntity; // 注意,这里一定是 strong reference。
// 预处理
if (nil == sourceEntityInContext) {//参数context上下文为nil,则与拷贝源source使用同一个上下文。
sourceEntityInContext = [source managedObjectContext];
}
if (nil == [source managedObjectContext]) {
// 注意,context一定不能为nil。
DDLogError(@"%@ %@, %@", THIS_FILE,THIS_METHOD, @"注意,context一定不能为nil。");
isSuccess = NO;
return isSuccess; // 返回NO
}
/*
How can I duplicate, or copy a Core Data Managed Object?
Ref.: http://stackoverflow.com/questions/2730832/how-can-i-duplicate-or-copy-a-core-data-managed-object
*/
{//[begin] --- 实体拷贝
/**
拷贝对象cloned 每个'字段'、每个'数据关系' 重新赋值
*/
NSString *entityName = [[source entity] name];//表名
//loop through all attributes and assign then to the clone
NSDictionary<NSString *, NSAttributeDescription *> *attributes =
[[NSEntityDescription entityForName: entityName
inManagedObjectContext: sourceEntityInContext] //Raises NSInternalInconsistencyException if context is nil.
attributesByName];
for (NSString *attr in attributes) {//遍历 每个字段
BOOL isIgnoreAttr = NO;//是否 忽略该字段
for (NSString *each_ignoreAttr in ignoreAttributeNames) {
if ([attr isEqualToString:each_ignoreAttr]) {
isIgnoreAttr = YES;//是否 忽略该字段 --- YES
break;
}
}
if (isIgnoreAttr==NO) {//是否 忽略该字段 --- NO
[cloned setValue:[source valueForKey:attr] forKey:attr];//loop through all attributes and assign then to the clone
}
}
//Loop through all relationships, and clone them.
NSDictionary<NSString *, NSRelationshipDescription *> *relationships =
[[NSEntityDescription entityForName: entityName
inManagedObjectContext: sourceEntityInContext] //Raises NSInternalInconsistencyException if context is nil.
relationshipsByName];
for (NSRelationshipDescription *rel in relationships){//遍历 每个‘数据关系’
if ([*hasLoopThroughRelationshipDescription containsObject:rel]){//退出"递归"的条件
continue;// 这个是,退出“递归”。
}else{
//根据 CoreData每一张表的数据关系链,遍历。记录此数据关系;以便之后 退出“递归”。
[*hasLoopThroughRelationshipDescription addObject:rel];
}
NSString *keyName = [NSString stringWithFormat:@"%@",rel];
BOOL isIgnoreRel = NO;//是否 忽略该'数据关系'
for (NSString *each_ignoreAttr in ignoreAttributeNames) {
if ([keyName isEqualToString:each_ignoreAttr]) {
isIgnoreRel = YES;//是否 忽略该'数据关系' --- YES
break;
}
}
if (isIgnoreRel==YES) {//是否 忽略该'数据关系' --- YES
continue;//忽略该'数据关系'
}
//to-one relationship ?
//- (nullable id)valueForKey:(NSString *)key;
//to-many relationship ?
//- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
/*
NSKeyValueCoding.h valueForKey
Ref.: Apple Document
Given a key that identifies an attribute or to-one relationship, return the attribute value or the related object. Given a key that identifies a to-many relationship, return an immutable array or an immutable set that contains all of the related objects.
*/
id sourceId = [source valueForKey:keyName];//to-one relationship OR to-many relationship
// id clonedID = [cloned valueForKey:keyName];//to-one relationship OR to-many relationship
if (sourceId) {
if ([sourceId isKindOfClass:[NSArray class]] || [sourceId isKindOfClass:[NSSet class]]) {//to-many relationship
/*
NSKeyValueCoding.h mutableSetValueForKey
Ref.: Apple Document
Given a key that identifies an _unordered_ and uniquing to-many relationship, return a mutable set that provides read-write access to the related objects. Objects added to the mutable set will become related to the receiver, and objects removed from the mutable set will become unrelated.
*/
//get a set of all objects in the relationship
NSMutableSet *sourceSet = [source mutableSetValueForKey:keyName];//to-many relationship
NSMutableSet *clonedSet = [cloned mutableSetValueForKey:keyName];//to-many relationship
NSEnumerator *e = [[sourceSet mutableCopy] objectEnumerator];// TODO: mutableCopy TODO: ??
NSManagedObject *relatedObject;
while ( relatedObject = [e nextObject]){
//Clone it, and add clone to set
/*
注释 此处的代码 --- [begin]
NSManagedObject *clonedRelatedObject = [self clone: relatedObject
inContext: context];
[clonedSet addObject: clonedRelatedObject];
注释 此处的代码 --- [end]
*/
/*
How to Deal with Temporary NSManagedObject instances?
Ref.: http://stackoverflow.com/questions/3256195/how-to-deal-with-temporary-nsmanagedobject-instances
*/
// NSEntityDescription *local_entityDesc = [relatedObject entity];
// NSManagedObject *local_clonedRelatedObject =
// [[NSManagedObject alloc] initWithEntity: local_entityDesc
// insertIntoManagedObjectContext: *destinationEntityInsertContext];//这里会导致数据库的数据重复,所以注释。
NSManagedObject *local_clonedRelatedObject = [self p_existingObjectWithID: relatedObject.objectID
inContext: *destinationEntityInsertContext];
if (local_clonedRelatedObject) {
// // NSManagedObjectContext *local_context = [relatedObject managedObjectContext];
BOOL isSuc = [self p_cloneSourceEntity: relatedObject
inContext: sourceEntityInContext
intoDestinationEntity: &local_clonedRelatedObject //double pointer
insertInContext: destinationEntityInsertContext //double pointer
ignoreAttributes: ignoreAttributeNames
relationshipDescription: hasLoopThroughRelationshipDescription]; //double pointer
if (isSuc) {
//Objects added to the mutable set will become related to the receiver, and objects removed from the mutable set will become unrelated.
[clonedSet addObject:local_clonedRelatedObject];//Loop through all relationships, and clone them.
}
}
}//<--- while遍历 --->//
}else{//to-one relationship
NSManagedObject *relatedObject = sourceId;
/*
How to Deal with Temporary NSManagedObject instances?
Ref.: http://stackoverflow.com/questions/3256195/how-to-deal-with-temporary-nsmanagedobject-instances
*/
// NSEntityDescription *local_entityDesc = [relatedObject entity];
// NSManagedObject *local_clonedRelatedObject =
// [[NSManagedObject alloc] initWithEntity: local_entityDesc
// insertIntoManagedObjectContext: *destinationEntityInsertContext];//这里会导致数据库的数据重复,所以注释。
NSManagedObject *local_clonedRelatedObject = [self p_existingObjectWithID: relatedObject.objectID
inContext: *destinationEntityInsertContext];
if (local_clonedRelatedObject) {
// // NSManagedObjectContext *local_context = [relatedObject managedObjectContext];
BOOL isSuc = [self p_cloneSourceEntity: relatedObject
inContext: sourceEntityInContext
intoDestinationEntity: &local_clonedRelatedObject //double pointer
insertInContext: destinationEntityInsertContext //double pointer
ignoreAttributes: ignoreAttributeNames
relationshipDescription: hasLoopThroughRelationshipDescription]; //double pointer
if (isSuc) {
//Objects added to the mutable set will become related to the receiver, and objects removed from the mutable set will become unrelated.
// // id clonedID = [cloned valueForKey:keyName];//to-one relationship OR to-many relationship
// // clonedID = local_clonedRelatedObject;
[cloned setValue:local_clonedRelatedObject forKey:keyName];//Loop through all relationships, and clone them.
}
}
}
}
}//<--- for遍历 --->//
isSuccess = YES; // 这个很重要 !
}//[end] --- 实体拷贝
return isSuccess;
}
/**
依赖方法: - (NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID error:(NSError **)error
Return Value:
The object specified by objectID. If the object cannot be fetched, or does not exist, or cannot be faulted, it returns nil.
Discussion:
If there is a managed object with the given ID already registered in the context, that object is returned directly; otherwise the corresponding object is faulted into the context.
This method might perform I/O if the data is uncached.
Unlike objectWithID:, this method never returns a fault.
*/
+ (NSManagedObject *)p_existingObjectWithID:(NSManagedObjectID *)aObjectID
inContext:(NSManagedObjectContext *)aContext
{
if(!aObjectID)
{
DDLogError(@"%@ %@ ,%@", THIS_FILE,THIS_METHOD, @"参数 aObjectID is nil");
return nil;
}
NSManagedObject *object = nil;
NSError * opError = nil;
{
object = [aContext existingObjectWithID:aObjectID error:&opError];
if(opError)
{
DDLogError(@"%@ %@ , error:%@", THIS_FILE,THIS_METHOD, opError);
}
if(!object)
{
DDLogError(@"%@ %@ , %@ %@", THIS_FILE,THIS_METHOD, aObjectID, @"查询:object is nil");
}
}
DDLogError(@"%@ %@ , %@ %@", THIS_FILE,THIS_METHOD, aObjectID, @"查询:object is found");
return object;
}

entity深拷贝,举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
应用于 HTmLocalAddressBookInviteController.m (本地通讯录-邀请页面)
/**
* 关联 临时实体 的 临时上下文
* @attention 临时上下文 可以insert 临时实体;
* @attention 临时上下文 一定不能save,切记切记!
*
* @return 临时上下文
*/
- (NSManagedObjectContext *)tempMainQueueContext
{
if (!_tempMainQueueContext) {
// _tempMainQueueContext = ({
//! 当前线程 的托管对象上下文
NSManagedObjectContext *curThreadContext = [[DataBaseManager sharedDataBaseManager]
currentThreadManagedObjectContext];
/**
临时的 主队列Context:关联 临时实体 的 临时上下文
@attention 临时上下文 可以insert 临时实体;
@attention 临时上下文 一定不能save,切记切记!
*/
_tempMainQueueContext
/*NSManagedObjectContext *mainQueueContext*/ = [[NSManagedObjectContext alloc] initWithConcurrencyType:
NSMainQueueConcurrencyType];
[_tempMainQueueContext setPersistentStoreCoordinator: curThreadContext.persistentStoreCoordinator];//共用一个Coordinator
[_tempMainQueueContext setMergePolicy: curThreadContext.mergePolicy];
[_tempMainQueueContext setUndoManager: nil];
{//
NSMutableArray<HTmContactEntity *> *myArr = [[NSMutableArray alloc] initWithCapacity:2];
//! iPhoneAddressBook通讯录 不支持HomeTime 的联系人 (这是 临时上下文 的 临时实体)
NSArray<HTmContactEntity *> *tmpCEArr = [[[BusinessManager sharedManager] localContactManager]
getNonHomeTimeContactEntitys];
[tmpCEArr enumerateObjectsUsingBlock:^(HTmContactEntity * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
HTmContactEntity *sourceEntity = obj;
NSManagedObjectContext *destinationEntityInsertContext = _tempMainQueueContext;
// 克隆 实体
NSEntityDescription *entityDesc = [sourceEntity entity];
HTmContactEntity *cloned = (HTmContactEntity*)[[NSManagedObject alloc] initWithEntity: entityDesc
insertIntoManagedObjectContext: destinationEntityInsertContext];
BOOL isSuc = [HTmContactEntity cloneSourceEntity: sourceEntity
inContext: sourceEntity.managedObjectContext
intoDestinationEntity: &cloned //double pointer
insertInContext: &destinationEntityInsertContext]; //double pointer
if (isSuc) {
[myArr addObject:cloned];
}
}];
_tempNonHomeTimeContacts = [myArr copy];
}//
//! 把 临时数据 插入到 临时的主队列Context;一定不能 保存!
// NSArray<HTmContactEntity *> *tmpNonHomeTimeContacts = [self tempNonHomeTimeContacts];
// NSArray<HTmContactEntity *> *tmpNonHomeTimeContacts = _tempNonHomeTimeContacts;
// //步骤一:
// [tmpNonHomeTimeContacts enumerateObjectsUsingBlock:^(HTmContactEntity * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// // 删除 临时实体
// [_tempMainQueueContext deleteObject:obj];
// }];
// //步骤二:
// [tmpNonHomeTimeContacts enumerateObjectsUsingBlock:^(HTmContactEntity * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// /**
// 插入 临时实体
// 临时上下文 一定不能save,切记切记!
// */
// [_tempMainQueueContext insertObject:obj];
// }];
// mainQueueContext; //返回:主队列Context
// });
}
return _tempMainQueueContext;
}

entity深拷贝,debug的日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
(lldb) po relationships
{
belongGroups = "(<NSRelationshipDescription: 0x1270031f0>), name belongGroups, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier belongGroups, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmGroupEntity, inverseRelationship hasContacts, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasBindDevices = "(<NSRelationshipDescription: 0x1270032a0>), name hasBindDevices, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasBindDevices, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmBindDeviceEntity, inverseRelationship belongContacts, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasCalleeRecords = "(<NSRelationshipDescription: 0x127003350>), name hasCalleeRecords, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasCalleeRecords, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmCallRecordEntity, inverseRelationship belongCalleeContact, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasCallerRecords = "(<NSRelationshipDescription: 0x125eefc80>), name hasCallerRecords, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasCallerRecords, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmCallRecordEntity, inverseRelationship belongCallerContact, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
}
(lldb)
Printing description of attributes:
{
abRecordID = "(<NSAttributeDescription: 0x141047d20>), name abRecordID, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier abRecordID, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
blackListPhoneNumbers = "(<NSAttributeDescription: 0x141047db0>), name blackListPhoneNumbers, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier blackListPhoneNumbers, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
colorHexValue = "(<NSAttributeDescription: 0x141047e40>), name colorHexValue, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier colorHexValue, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 200 , attributeValueClassName NSNumber, defaultValue 0";
contactTypeBitmask = "(<NSAttributeDescription: 0x141047ed0>), name contactTypeBitmask, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier contactTypeBitmask, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 100 , attributeValueClassName NSNumber, defaultValue 0";
dataSourceType = "(<NSAttributeDescription: 0x141047f60>), name dataSourceType, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier dataSourceType, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 100 , attributeValueClassName NSNumber, defaultValue 0";
"data_1" = "(<NSAttributeDescription: 0x141047ff0>), name data_1, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier data_1, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
"data_2" = "(<NSAttributeDescription: 0x141048080>), name data_2, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier data_2, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
"data_3" = "(<NSAttributeDescription: 0x141048110>), name data_3, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier data_3, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
"data_4" = "(<NSAttributeDescription: 0x1410481a0>), name data_4, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier data_4, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
"data_5" = "(<NSAttributeDescription: 0x141048230>), name data_5, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier data_5, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
deviceID = "(<NSAttributeDescription: 0x1410482c0>), name deviceID, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier deviceID, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
entityUUID = "(<NSAttributeDescription: 0x141048350>), name entityUUID, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier entityUUID, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
etag = "(<NSAttributeDescription: 0x1410483e0>), name etag, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier etag, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
firstLetterSpell = "(<NSAttributeDescription: 0x141048470>), name firstLetterSpell, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier firstLetterSpell, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
foreignKeyAccountUserID = "(<NSAttributeDescription: 0x141048500>), name foreignKeyAccountUserID, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier foreignKeyAccountUserID, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
fullSpell = "(<NSAttributeDescription: 0x141048590>), name fullSpell, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier fullSpell, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
headIconPath = "(<NSAttributeDescription: 0x141048620>), name headIconPath, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier headIconPath, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
hometimePhoneNumbers = "(<NSAttributeDescription: 0x1410486b0>), name hometimePhoneNumbers, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hometimePhoneNumbers, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
indexMark = "(<NSAttributeDescription: 0x141048740>), name indexMark, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier indexMark, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
isAddedContactBySyncingServer = "(<NSAttributeDescription: 0x1410487d0>), name isAddedContactBySyncingServer, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isAddedContactBySyncingServer, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isBlackList = "(<NSAttributeDescription: 0x141048860>), name isBlackList, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isBlackList, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isLetvBindDeviceContact = "(<NSAttributeDescription: 0x1410488f0>), name isLetvBindDeviceContact, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isLetvBindDeviceContact, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isLetvContact = "(<NSAttributeDescription: 0x141048980>), name isLetvContact, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isLetvContact, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isRemovedFromAddressBook = "(<NSAttributeDescription: 0x141048a10>), name isRemovedFromAddressBook, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isRemovedFromAddressBook, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isShouldDelete = "(<NSAttributeDescription: 0x141048aa0>), name isShouldDelete, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isShouldDelete, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
isVIPContact = "(<NSAttributeDescription: 0x141048b30>), name isVIPContact, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier isVIPContact, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 800 , attributeValueClassName NSNumber, defaultValue (null)";
"is_voip_number" = "(<NSAttributeDescription: 0x141048bc0>), name is_voip_number, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier is_voip_number, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
lastConnectDate = "(<NSAttributeDescription: 0x141048c50>), name lastConnectDate, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier lastConnectDate, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 900 , attributeValueClassName NSDate, defaultValue (null)";
letvAccountPhoneNumber = "(<NSAttributeDescription: 0x141048ce0>), name letvAccountPhoneNumber, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier letvAccountPhoneNumber, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
letvPicture = "(<NSAttributeDescription: 0x141048d70>), name letvPicture, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier letvPicture, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
letvUserName = "(<NSAttributeDescription: 0x141048e00>), name letvUserName, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier letvUserName, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
nameFirstChar = "(<NSAttributeDescription: 0x141048e90>), name nameFirstChar, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier nameFirstChar, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
nickname = "(<NSAttributeDescription: 0x141048f20>), name nickname, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier nickname, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
nonHometimePhoneNumbers = "(<NSAttributeDescription: 0x141048fb0>), name nonHometimePhoneNumbers, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier nonHometimePhoneNumbers, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
phoneNumberInAddressBookCheckExistResultType = "(<NSAttributeDescription: 0x141049040>), name phoneNumberInAddressBookCheckExistResultType, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier phoneNumberInAddressBookCheckExistResultType, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 100 , attributeValueClassName NSNumber, defaultValue 0";
"phone_number" = "(<NSAttributeDescription: 0x1410490d0>), name phone_number, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier phone_number, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
remarkName = "(<NSAttributeDescription: 0x141049160>), name remarkName, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier remarkName, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
sourceID = "(<NSAttributeDescription: 0x1410491f0>), name sourceID, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier sourceID, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
syncStatus = "(<NSAttributeDescription: 0x141049280>), name syncStatus, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier syncStatus, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 100 , attributeValueClassName NSNumber, defaultValue 0";
telephoneNumbers = "(<NSAttributeDescription: 0x141049310>), name telephoneNumbers, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier telephoneNumbers, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, attributeType 700 , attributeValueClassName NSString, defaultValue (null)";
}
(lldb)
Printing description of relationships:
{
belongGroups = "(<NSRelationshipDescription: 0x14104a0f0>), name belongGroups, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier belongGroups, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmGroupEntity, inverseRelationship hasContacts, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasBindDevices = "(<NSRelationshipDescription: 0x14104a1a0>), name hasBindDevices, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasBindDevices, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmBindDeviceEntity, inverseRelationship belongContacts, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasCalleeRecords = "(<NSRelationshipDescription: 0x14104a250>), name hasCalleeRecords, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasCalleeRecords, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmCallRecordEntity, inverseRelationship belongCalleeContact, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
hasCallerRecords = "(<NSRelationshipDescription: 0x14104a300>), name hasCallerRecords, isOptional 1, isTransient 0, entity HTmContactEntity, renamingIdentifier hasCallerRecords, validation predicates (\n), warnings (\n), versionHashModifier (null)\n userInfo {\n}\n, destination entity HTmCallRecordEntity, inverseRelationship belongCallerContact, minCount 0, maxCount 0, isOrdered 0, deleteRule 1";
}
(lldb)
(lldb) po rel
belongGroups
(lldb) po hasLoopThroughRelationshipDescription
0x000000016e892478
(lldb) po *hasLoopThroughRelationshipDescription
<__NSArrayM 0x1411da430>(
belongGroups,
hasBindDevices,
hasCalleeRecords,
belongCalleeContact
)
CoreData的内存策略(fault)

Coredata Error “ data: fault ”

What Is a Core Data Fault?

Entity不能跨线程使用(特别是修改),否则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2016-10-24 11:47:25.864 HomeTime[8345:5086261] CRASH: CoreData could not fulfill a fault for '0xd000000000040002 <x-coredata://FB88D6DC-A39B-4590-8A7F-4BC8A53F4E24/HTmContactEntity/p1>'
Stack trace:
#0 CoreFoundation 000000000183192DB0 <redacted>()
#1 libobjc.A.dylib 0000000001827F7F80 objc_exception_throw()
#2 CoreData 00000000018500C750 <redacted>()
#3 CoreData 00000000018501C8C4 <redacted>()
#4 CoreData 00000000018501C610 <redacted>()
#5 HomeTime 000000000100136398 __87-[HTmLocalContactManager request_query_letvAccountInfoForPhoneNumbers:success:failure:]_block_invoke.458()
#6 CoreFoundation 000000000183184F58 <redacted>()
#7 HomeTime 000000000100135384 __87-[HTmLocalContactManager request_query_letvAccountInfoForPhoneNumbers:success:failure:]_block_invoke_2.409()
#8 CAbstractManager 000000000101B10E9C -[BusinessBlockOperation main]
#9 Foundation 000000000183A8EE48 -[__NSOperationInternal _start:]
#10 Foundation 000000000183B4E934 <redacted>()
#11 libdispatch.dylib 000000000101B75A3C _dispatch_client_callout()
#12 libdispatch.dylib 000000000101B82554 _dispatch_queue_drain()
#13 libdispatch.dylib 000000000101B7972C _dispatch_queue_invoke()
#14 libdispatch.dylib 000000000101B8466C _dispatch_root_queue_drain()
#15 libdispatch.dylib 000000000101B84364 _dispatch_worker_thread3()
#16 libsystem_pthread.dylib 000000000182DF5470 _pthread_wqthread()
#17 libsystem_pthread.dylib 000000000182DF5020 start_wqthread())
2016-10-24 11:47:25.864 HomeTime[8345:5086261] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd000000000040002 <x-coredata://FB88D6DC-A39B-4590-8A7F-4BC8A53F4E24/HTmContactEntity/p1>''
*** First throw call stack:
(0x183192db0 0x1827f7f80 0x18500c750 0x18501c8c4 0x18501c610 0x100136398 0x183184f58 0x100135384 0x101b10e9c 0x183a8ee48 0x183b4e934 0x101b75a3c 0x101b82554 0x101b7972c 0x101b8466c 0x101b84364 0x182df5470 0x182df5020)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
(lldb)

Coredata Error “data:
http://stackoverflow.com/questions/7304257/coredata-error-data-fault

1
[request setReturnsObjectsAsFaults:NO]

This is expected behaviour, core data won’t return full objects until you need to access the persistent values of the objects. Each of your returned objects will be a ‘fault’ until this point.

You can force the fetch request to return full objects using [request setReturnsObjectsAsFaults:NO], but in most cases what you have will be fine. Look at the documentation for NSFetchRequest for more information.

If you access one of the properties, core data will go to the persistent store and fetch the rest of your values, then you’ll get the full description in the logs.

This seems to be such a common misunderstanding that I decided to write about it, here.

“Core Data could not fulfill a fault” for objects that were created in the appDelegate managedObjectContext on the main thread
http://stackoverflow.com/questions/20006689/core-data-could-not-fulfill-a-fault-for-objects-that-were-created-in-the-appde

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
HTmLocalContactManager.m
/*
CoreData使用特别注意
1、每个NSThread实例,必须有各自的NSManagedObjectContext实例,该Context实际上是把db.sqlite文件读取到内存中。
2、CoreData的数据库实体(即 Entity,继承自NSManagedObject),存在于各自所属NSThread实例的NSManagedObjectContext实例中,并且Entity 不能 跨线程传递;如果 一定要 跨线程 传递Entity,请传递该Entity的主键(比如 myEntity.entityUUID),然后在 另一个线程 使用 myEntity.entityUUID 从这个所谓 另一个线程 的Context,查找到这个Entity。
3、临时Entity数据,即 不打算 持久化的Entity,请使用 临时的Context,并且一定不能 [临时Context save]
4、Entity深拷贝?上面链接有。
5、CoreData 数据库迁移?CoreData 关系建立与移除?上面链接有。
*/
__block NSMutableArray<HTmContactEntity*> *oldHomeTimePersons;
__block NSMutableArray<NSDictionary*> *newestHomeTimePersons;
//请求查询前,先缓存上次启动保存到数据库的联系人信息
dispatch_sync(dispatch_get_main_queue(), ^{//[begin] 没错,使用 同步 dispatch_sync
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
oldHomeTimePersons = (NSMutableArray *)[self allHomeTimeContacts];
NSLog(@"oldHomeTimePersons number %ld", [oldHomeTimePersons count]);
});
});//[end] 没错,使用 同步 dispatch_sync
/* oldHomeTimePersons 元素是 CoreData的数据库实体 ——> 不能 “跨线程 修改”。 */
用这个 把 代码,包围起来。
{//CoreData数据库中 把原来支持现在变为不支持hometime联系人标记出来 --- begin ---//
dispatch_sync(dispatch_get_main_queue(), ^{//[begin]没错,使用 同步 dispatch_sync。因为:CoreData的数据库实体Entity不能跨线程使用,而oldHomeTimePersons是在主线程创建的,所以 必须在 主线程修改oldHomeTimePersons。
});//[end]没错,使用 同步 dispatch_sync
}//CoreData数据库中 把原来支持现在变为不支持hometime联系人标记出来 --- end ---//

上述代码

1
2
3
dispatch_sync(dispatch_get_main_queue(), ^{
// 一些在主线程 操作数据库的代码
});

改成

1
2
3
4
5
6
7
BusinessBlockOperation *blockOp = [BusinessBlockOperation blockOperationWithBlock:^{
// some code
}];// blockOp
[[[BusinessManager sharedManager] localContactManager].serialOperationQueue addOperation:blockOp];

更为合理。

CoreData性能优化

指定字段

1
2
3
4
5
6
{//<---优化查找--begin--->//
// 指定 返回类型
fetchRequest.resultType = NSDictionaryResultType;
// 指定 查找字段
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:@"entityUUID",@"telephoneNumbers", nil]];
}//<---优化查找--end--->//

数据分页

线程池中,并发数量 控制 (使用 操作队列)

主线程操作数据库 ——> 不可避免有这样的场景,数据量很小(三五条数据)。

其他情况,批量写入数据库,都在 worker 线程。——> 这样的worker 线程,只有3个(太多导致 context增多,同时context合并数据 也耗性能)。

问题:以后数据越来越多,问题就明显了 ?——> 不会,因为:必须有一个 主线程的Context,而 Context的内存策略是 fault(这个不会 阻塞 线程 很久;如果数据量很大,使用 worker线程,不要用主线程)。

CoreData + NSFetchedResultsController

NSFetchedResultsController 通过KVO/Notification模式,在一个线程(一般是主线程) 观察CoreData的一个NSManagedObjectContext(一般是主线程的Context)的变化(增、删、改),定义一个NSFetchRequest将数据(包括数据的增删改)通过delegate模式绑定到view(一般是tableView 或 collectionView)。

具体做法,这里 Stanford公开课-iOS9-Swift-第10课 Core Data 和 这里 Stanford公开课-iOS7-ObjectiveC-第13课 Core Data and Tale View , Stanford(美国斯坦福大学)的iOS开发公开课 下载方式在这 , Stanford的官网下载代码:cs193p iPhone App Devcs193p iPhone App Dev downloads-2013-fall

做framework的脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash -e -x
PROJECT_NAME=CCoreData
SRCROOT=$PWD
# 来源Ref. http://years.im/Home/Article/detail/id/52.html
# Sets the target folders and the final framework product.
# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME
# 例如: FMK_NAME = "MyFramework"
FMK_NAME=${PROJECT_NAME}
# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
#${FMK_NAME}.framework
#CONFIG=Debug
CONFIG=Release
# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/${CONFIG}-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/${CONFIG}-iphonesimulator/${FMK_NAME}.framework
INSTALL_DIR=${SRCROOT}/Products/
# Clean and Building both architectures.
xcodebuild -configuration $CONFIG -target "${FMK_NAME}" -project CCoreData.xcodeproj -sdk iphoneos build
xcodebuild -configuration $CONFIG -target "${FMK_NAME}" -project CCoreData.xcodeproj -sdk iphonesimulator build
# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -r "${DEVICE_DIR}" "$INSTALL_DIR"
# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/${FMK_NAME}.framework/${FMK_NAME}"
# rm -r "${WRK_DIR}"
echo "done."
open "${INSTALL_DIR}"
CCoreData.framework + CAbstractManager.framework

基于Apple的 CoreData.framework ,根据CoreData的线程安全、多线程数据合并、数据库迁移、关系型数据 等特点,使用Objective-C,封装成为 CCoreData.framework

基于`工厂模式`,`单例模式`,`多线程并发` ,`通知+block 进行线程通讯`,`GCD + Operation Queue` 等,并依赖于CoreData的线程安全、多线程数据合并(即自己封装的 CCoreData.framework ),使用Objective-C,封装成为 CAbstractManager.framework

App Store Review 需要支持bitcode,所以自己造的framework也得支持bitcode,那么,修改工程配置的bitcode为YES,如下图:

1
2
3
4
5
6
7
8
CAbstractManager.framework/CAbstractManagerDefine.h
/**
@brief 线程最大运行个数
@attention 请勿在其他地方 重复定义`此常量`
@note `此常量`的含义为:AbstractManager的'操作队列' 线程并发操作数
*/
static NSInteger const MY_THREAD_CONCURRENT_OPERATION_COUNT = 3 ;

by OYXJ thinking carefully at night on 2016.10.26, and in the morning on 2016.10.27,
resulting in a conclusion that:

change MY_THREAD_CONCURRENT_OPERATION_COUNT after —> CAbstractManager.framework is complied into “Unix executable” file named “CAbstractManager”, WON’T re-compile CAbstractManager, thus the effort of trying changing MY_THREAD_CONCURRENT_OPERATION_COUNT does not work.

so, revert back to AS IS.

1
2
3
4
5
6
CAbstractManager.framework/CAbstractManager.h
#import <CAbstractManager/AbstractManager.h> //注意这里的顺序,项目中实际应用,编译报错了。
#import <CAbstractManager/CAbstractManagerDefine.h> //注意这里的顺序,项目中实际应用,编译报错了。
#import <CAbstractManager/BusinessBlockOperation.h>
#import <CAbstractManager/BusinessSelectorOperation.h>
1
2
3
4
5
6
7
8
9
10
➜ ~ git:(文稿+音乐) ✗ cd CustomFramework/WebRTC.framework
➜ WebRTC.framework git:(文稿+音乐) ✗ l
total 19272
drwxr-xr-x 7 OYXJ staff 238B 10 21 13:21 .
drwxr-xr-x@ 9 OYXJ staff 306B 10 27 10:06 ..
-rw-r--r--@ 1 OYXJ staff 6.0K 10 21 13:22 .DS_Store
drwxr-xr-x 37 OYXJ staff 1.2K 9 19 12:04 Headers
-rw-r--r-- 1 OYXJ staff 778B 10 14 01:05 Info.plist
drwxr-xr-x 3 OYXJ staff 102B 9 19 12:04 Modules
-rwxr-xr-x 1 OYXJ staff 9.4M 10 14 01:05 WebRTC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTmSyncServerContactManager.m
同步联系人:读、写 数据库的操作,放在 串行队列上,避免 多线程 写 数据库,导致 数据重复。
.operationQueue 变成 .serialOperationQueue
#import <CAbstractManager/BusinessBlockOperation.h>
BusinessBlockOperation *blockOp = [BusinessBlockOperation blockOperationWithBlock:^{
// some code
}];// blockOp
[[BusinessManager sharedManager].syncServerContactManager.operationQueue addOperation:blockOp];
比如
[[BusinessManager sharedManager].syncServerContactManager.operationQueue addOperation:blockOp]
变成
[[BusinessManager sharedManager].syncServerContactManager.serialOperationQueue addOperation:blockOp];



Base 查看沙盒 数据库,分享一

踩过的坑 & Debug
CoreData 多个Context的数据同步

以下数据,已经洗白,避免泄漏公司项目的关键信息。

多线程数据同步
对比
etag
sourceID
syncStatus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
特别说明:by OYXJ on 2016.11.02
此处 此线程(简称线程A) 使用 此mAllLocalList内存数据(即 objContact.sourceID),
在此处之前或者之后 还有另一个线程(即[BusinessManager sharedManager].syncServerContactManager.serialOperationQueue)的线程(简称线程B)
在写入 数据库,包括更改 mAllLocalList所引用的数据库实体(对应沙盒中持久化StoreFile的数据库Record),
但是 此文件中,并没有 把“线程B的context”更改、插入的数据 同步至 “线程A的context“ ---> 目前来说,是不合理的、不正确的。
所以 在 “线程B”更改、插入的数据 [线程B的context save]; 之后,需要在“线程A”,执行: [[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
*/
// HTmContactEntity *contact = objContact;
LELOGD(@"contactEtag---- before entityUUID = %@, nickname = %@ etag = %@ sorceid=%@",
objContact.entityUUID, objContact.nickname, objContact.etag, objContact.sourceID );
[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];
// HTmContactEntity *contact1 = objContact;
LELOGD(@"contactEtag---- after entityUUID = %@, nickname = %@ etag= %@ sorceid=%@",
objContact.entityUUID, objContact.nickname, objContact.etag, objContact.sourceID );
(lldb) po mDumpID
<__NSArrayM 0x17445f530>(
B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7,
16635605-811A-4B3C-B287-105AEDC4E07C,
34497B8E-26F7-469C-AF02-C3AF3C850315,
72D55B7E-880A-4FAE-8D84-38FA56EC6FDC,
B11BE4EA-0D89-422F-B1C9-EB1C811568B4,
7FE10CF1-B8C9-4A72-A872-43FDDED253D5,
D36F6A52-0671-4A7A-B321-984F6D8D0FD3,
4AE29811-6D30-4C06-B865-C0E0FEF3BE67,
95F071E9-F538-4BAA-8F15-49B513984CA7,
685957E3-AA52-49BA-B643-D7355F1F2FE5,
9ADEFDD9-B995-4E44-88F9-0B74770381BD,
2E38B92F-9991-4CFF-B2DB-5FEC4F83DC2F
)
2016-11-02 18:30:23:244 HomeTime[17319:3112341] LeNet.h:477 TaskHeartBeat(0x1aac2ac40) hbElapse=10 status=logined last_recv_data_time=1478082570012 last_heart_beat_time=0 elapse_last_heart_beat=0 elapse_last_recv_data=53232 recv_heart_beat_timeused=0
(lldb) po mAllLocalList.count
12
2016-11-02 18:30:33.945822 HomeTime[17319:3112341] reportEvt:ext:2,pri:1
2016-11-02 18:30:23:244 HomeTime[17319:3112341] LeNet.h:658 LeNet.sendHeartBeat
2016-11-02 18:30:23:245 HomeTime[17319:3112341] LeNet.h:635 LeNet.send cmd=9 length=7
(lldb) po objContact
<NSManagedObject: 0x1704c7d90> (entity: HTmContactEntity; id: 0xd000000000200000 <x-coredata://650B8F90-9C0C-47F0-9235-EAF231E02CF5/HTmContactEntity/p8> ; data: {
nameFirstChar = 金;
hasCallerRecords = <relationship fault: 0x1706277a0 'hasCallerRecords'>;
isRemovedFromAddressBook = 0;
contactTypeBitmask = 1;
nickname = 欧阳孝金;
hasBindDevices = <relationship fault: 0x170626040 'hasBindDevices'>;
belongGroups = <relationship fault: 0x1706249e0 'belongGroups'>;
isAddedContactBySyncingServer = nil;
firstLetterSpell = oyxj;
phoneNumberInAddressBookCheckExistResultType = 2;
hasCalleeRecords = <relationship fault: 0x1706280c0 'hasCalleeRecords'>;
dataSourceType = 2;
foreignKeyAccountUserID = nil;
abRecordID = 381;
etag = nil; //“线程B”更改此数据,然后[线程B的context save];
syncStatus = 1; //“线程B”更改此数据, 然后[线程B的context save];
deviceID = nil;
sourceID = B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7;
fullSpell = ouyangxiaojin;
entityUUID = B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7;
isShouldDelete = 0;
colorHexValue = 16240213;
}
)
此前,断点调试,
此处,执行这行代码,
**[[BaseAccessor sharedBaseAccessor] mergeChangesManagedObjectsOnCurrentThread];**
此后,接着 断点调试。
(lldb) po objContact
<NSManagedObject: 0x1704c7d90> (entity: HTmContactEntity; id: 0xd000000000200000 <x-coredata://650B8F90-9C0C-47F0-9235-EAF231E02CF5/HTmContactEntity/p8> ; data: <fault>)
(lldb) po objContact.entityUUID
B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7
(lldb) po objContact
<NSManagedObject: 0x1704c7d90> (entity: HTmContactEntity; id: 0xd000000000200000 <x-coredata://650B8F90-9C0C-47F0-9235-EAF231E02CF5/HTmContactEntity/p8> ; data: {
nameFirstChar = 金;
hasCallerRecords = <relationship fault: 0x17043cf40 'hasCallerRecords'>;
isRemovedFromAddressBook = 0;
contactTypeBitmask = 1;
nickname = 欧阳孝金;
hasBindDevices = <relationship fault: 0x17042f700 'hasBindDevices'>;
belongGroups = <relationship fault: 0x17043d6a0 'belongGroups'>;
isAddedContactBySyncingServer = nil;
firstLetterSpell = oyxj;
phoneNumberInAddressBookCheckExistResultType = 2;
hasCalleeRecords = <relationship fault: 0x170238360 'hasCalleeRecords'>;
dataSourceType = 2;
foreignKeyAccountUserID = nil;
abRecordID = 381;
etag = W/"675d32ae1439229aefaf949ae0cd1abe"; //“线程A”已经同步了“线程B”对数据的更改。
syncStatus = 0; //“线程A”已经同步了“线程B”对数据的更改。
deviceID = nil;
sourceID = B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7;
fullSpell = ouyangxiaojin;
entityUUID = B8ECCAEF-72C8-422C-B5A4-43EBD0C19CA7;
isShouldDelete = 0;
colorHexValue = 16240213;
}
)
(lldb)
1
2
3
4
5
6
7
8
9
10
11
NSEntityDescription搞错,导致KVO时崩溃。
local_entityDesc这个东西搞错,local_clonedRelatedObject是个HTmCallRecordEntity,
但是local_entityDesc描述HTmContactEntity。
NSEntityDescription *local_entityDesc = [relatedObject entity];
NSManagedObject *local_clonedRelatedObject =
[[NSManagedObject alloc] initWithEntity: local_entityDesc
insertIntoManagedObjectContext: *destinationEntityInsertContext];
2016-11-02 22:46:26.787 HomeTime[1387:409219] CRASH: [<NSManagedObject 0x13eecc8a0> setValue:forUndefinedKey:]: the entity HTmContactEntity is not key value coding-compliant for the key "callee".
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
to-many 和 to-one 数据关系,克隆时,崩溃。
2016-11-03 10:39:43.920 HomeTime[1454:434979] CRASH: NSManagedObjects of entity 'HTmCallRecordEntity' do not support -mutableSetValueForKey: for the property 'belongCalleeContact'
因为:
//! 数据关系:所属 Callee联系人 (to-one)
@property (nonatomic, strong) HTmContactEntity *belongCalleeContact;
//! 数据关系:所属 Caller联系人 (to-one)
@property (nonatomic, strong) HTmContactEntity *belongCallerContact;
@end
调试信息:
(lldb) po [[source valueForKey:@"belongCalleeContact"] class]
NSManagedObject
(lldb) po [[source mutableSetValueForKey:@"belongCalleeContact"]]
error: expected identifier
(lldb) po [source mutableSetValueForKey:@"belongCalleeContact"]
error: Execution was interrupted, reason: internal ObjC exception breakpoint(-3)..
The process has been returned to the state before expression evaluation.
(lldb) po [[source mutableSetValueForKey:@"belongCalleeContact"] class]
error: Execution was interrupted, reason: internal ObjC exception breakpoint(-3)..
The process has been returned to the state before expression evaluation.
(lldb)
//to-one relationship OR to-many relationship
//- (nullable id)valueForKey:(NSString *)key;
//to-many relationship
//- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
不同contexts之间的数据库实体Entity,企图建立“数据关系”,导致崩溃。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'belongCalleeContact' between objects in different contexts
Stack trace:
#0 CoreFoundation 00000000018203ADB0 <redacted>()
#1 libobjc.A.dylib 00000000018169FF80 objc_exception_throw()
#2 CoreData 000000000183EC5F80 <redacted>()
#3 CoreData 000000000183EC462C <redacted>()
#4 CoreData 000000000183ED2270 -[NSManagedObject _maintainInverseRelationship:forProperty:forChange:onSet:]
#5 CoreData 000000000183ECE0E0 -[NSManagedObject _didChangeValue:forRelationship:named:withInverse:]
#6 Foundation 000000000182937F64 <redacted>()
#7 Foundation 000000000182937A8C <redacted>()
#8 Foundation 00000000018299CBD8 -[NSObject didChangeValueForKey:withSetMutation:usingObjects:]
#9 CoreData 000000000183ED20E8 -[NSManagedObject didChangeValueForKey:withSetMutation:usingObjects:]
#10 CoreData 000000000183EF4D48 -[_NSNotifyingWrapperMutableSet addObject:]
#11 HomeTime 000000000100193A88 +[BaseManagedObject p_cloneSourceEntity:inContext:intoDestinationEntity:ignoreAttributes:]
#12 HomeTime 000000000100193030 +[BaseManagedObject cloneSourceEntity:intoDestinationEntity:ignoreAttributes:]
#13 HomeTime 00000000010014AB34 -[HTmContactAccessor syncServerContact_insertOrUpdateContact:]
#14 HomeTime 000000000100112ED8 -[HTmLocalContactManager syncServerContact_insertOrUpdateContact:]
#15 HomeTime 0000000001001D1A94 __47-[HTmSyncServerContactManager p_pullNew:local:]_block_invoke_3()
#16 CoreFoundation 000000000181F32584 <redacted>()
#17 CoreFoundation 000000000181F292D4 -[__NSArrayI enumerateObjectsWithOptions:usingBlock:]
#18 HomeTime 0000000001001D1230 __47-[HTmSyncServerContactManager p_pullNew:local:]_block_invoke_2.1035()
#19 CAbstractManager 000000000101B14B88 -[BusinessBlockOperation main]
#20 Foundation 000000000182936E48 -[__NSOperationInternal _start:]
#21 Foundation 0000000001829F6934 <redacted>()
#22 libdispatch.dylib 000000000101B61A3C _dispatch_client_callout()
#23 libdispatch.dylib 000000000101B6E554 _dispatch_queue_drain()
#24 libdispatch.dylib 000000000101B6572C _dispatch_queue_invoke()
#25 libdispatch.dylib 000000000101B7066C _dispatch_root_queue_drain()
#26 libdispatch.dylib 000000000101B70364 _dispatch_worker_thread3()
#27 libsystem_pthread.dylib 000000000181C9D470 _pthread_wqthread()
#28 libsystem_pthread.dylib 000000000181C9D020 start_wqthread())
2016-11-03 11:52:01.538 HomeTime[1508:449017] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'belongCalleeContact' between objects in different contexts (source = <NSManagedObject: 0x15d90da50> (entity: HTmCallRecordEntity; id: 0x15d93dd80 <x-coredata:///HTmCallRecordEntity/t4DF25175-A3C3-489A-AF24-6E661B4175C119> ; data: {
oppositeName = 😄Aaa;
isCalleeTalk = nil;
isOppositeDeviceSelf = nil;
oppositeFirstLetterSpell = A;
oppositeContactEntityUUID = FD8B75F0-92D4-4AB0-A19B-59D14F194B55;
callee = 13661248236;
tsTalk = 0;
inviteTimeIntervalToLocalDate = 2016-11-02 09:01:15 +0000;
oppositeColorHexValue = 6675144;
oppositePhoneNumberLocation = 北京;
calleeName = 😄Aaa;
oppositeDeviceID = nil;
indexYearMonth = 201611月;
oppositePhoneNumberInAddressBookCheckExistResultType = 2;
isCallerInvite = 1;
type = 1;
calleeDevice = --861579030062526;
belongCallerContact = nil;
googRemoteAddress = ;
foreignKeyAccountUserID = 107182627797;
callerDevice = 2818C121-9048-4D64-B7D0-EDD7CE191529;
oppositeIndexMark = A;
talkTimeIntervalToLocalDate = nil;
googLocalAddress = ;
isCalleeInvite = nil;
isOppositeHomeTimePhoneNumber = nil;
oppositeHeadIconPath = FD8B75F0-92D4-4AB0-A19B-59D14F194B55.jpeg;
tsInviteOver = 608526972;
oppositeNameFirstChar = A;
isCallerTalk = nil;
traceError = 0;
callerNameFirstChar = 金;
sessionId = 69910CFD-956D-4421-8CD0-422957527890;
belongCalleeContact = nil;
isShouldDelete = 0;
inviteDurationSeconds = 1;
callerName = 欧阳孝金;
talkDurationSeconds = 0;
isCaller = 1;
googRemoteCandidateType = ;
isPullFromServer = 0;
entityUUID = nil;
tsInvite = 608525822;
tsTalkOver = 0;
isVoiceCall = 0;
googLocalCandidateType = ;
oppositeFullSpell = Aaa;
calleeNameFirstChar = A;
isIllegal = 0;
oppositeDeviceType = 0;
}
) , destination = <NSManagedObject: 0x15df7a530> (entity: HTmContactEntity; id: 0xd000000000640004 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmContactEntity/p25> ; data: {
nameFirstChar = A;
hasCallerRecords = <relationship fault: 0x15d901270 'hasCallerRecords'>;
isRemovedFromAddressBook = 0;
data_5 = nil;
contactTypeBitmask = 1;
isVIPContact = nil;
data_4 = nil;
nickname = Aaa;
hasBindDevices = (
);
blackListPhoneNumbers = nil;
data_3 = nil;
belongGroups = (
);
isAddedContactBySyncingServer = nil;
firstLetterSpell = A;
phoneNumberInAddressBookCheckExistResultType = 2;
data_2 = nil;
hasCalleeRecords = (
0xd000000000200002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p8>,
0x15d93dd80 <x-coredata:///HTmCallRecordEntity/t4DF25175-A3C3-489A-AF24-6E661B4175C119>,
0xd000000000080002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p2>,
0xd000000000280002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p10>,
0xd000000000300002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p12>,
0xd0000000002c0002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p11>,
0xd000000000340002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p13>,
0xd0000000003c0002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p15>,
0xd000000000180002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p6>,
0xd0000000001c0002 <x-coredata://62D351B5-B043-4AB2-B931-7B2A2E298606/HTmCallRecordEntity/p7>,
);
dataSourceType = 2;
foreignKeyAccountUserID = nil;
isBlackList = nil;
data_1 = nil;
isLetvBindDeviceContact = 1;
lastConnectDate = nil;
abRecordID = 147;
etag = W/"cf7ae8f7e49437b68597498bfddac0e2";
syncStatus = 0;
is_voip_number = 1;
deviceID = nil;
sourceID = 0A8DDD04-4E99-4742-B172-D48B9F43A9F0;
fullSpell = Aaa;
remarkName = nil;
indexMark = A;
headIconPath = 0A8DDD04-4E99-4742-B172-D48B9F43A9F0.jpeg;
entityUUID = 0A8DDD04-4E99-4742-B172-D48B9F43A9F0;
nonHometimePhoneNumbers = ;
isShouldDelete = 0;
isLetvContact = 1;
colorHexValue = 6675144;
}
))'
*** First throw call stack:
(0x18203adb0 0x18169ff80 0x183ec5f80 0x183ec462c 0x183ed2270 0x183ece0e0 0x182937f64 0x182937a8c 0x18299cbd8 0x183ed20e8 0x183ef4d48 0x100193a88 0x100193030 0x10014ab34 0x100112ed8 0x1001d1a94 0x181f32584 0x181f292d4 0x1001d1230 0x101b14b88 0x182936e48 0x1829f6934 0x101b61a3c 0x101b6e554 0x101b6572c 0x101b7066c 0x101b70364 0x181c9d470 0x181c9d020)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)