我是核心数据的新手。我有一个使用核心数据作为本地存储的应用程序。写入和读取核心数据是由后台线程完成的。尽管这通常可以正常工作,但在极少数情况下,提取的数据是错误的,即,提取的实体的属性为nil
。
为了检查这种情况,我编写了一个单元测试,该单元测试启动了2个异步线程:一个从核心数据连续获取数据,另一个通过首先删除所有数据然后存储新数据来连续覆盖这些数据。
这个测试很快就会引发错误,但是我不知道为什么。当然,我猜这是一个多线程问题,但是我不明白为什么,因为获取和删除+写操作是在单个persistentContainer
的单独托管环境中完成的。
很抱歉,下面的代码虽然缩短了,但相当长,但我认为没有它就无法识别问题。
任何帮助都非常欢迎!
这是我获取数据的功能:
func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try managedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
let nextname = nextCdShoppingItem.name! // Here, sometimes name is nil
} // performAndWait
} // for all cdShoppingItems
completion(nil, nil)
return
} catch let error as NSError {
// error handling
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
由于name
是nil
,我评论了有时会使测试崩溃的那一行。
这是我存储数据的功能:
func overwriteCD(shoppingItems: Set<ShoppingItem>,completion: @escaping () -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: "CDShoppingItem",in: managedContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity,insertInto: managedContext)
nextCdShoppingItem.name = nextShoppingItem.name
} // for all shopping items
self.saveManagedContext(managedContext: managedContext)
completion()
} // performBackgroundTask
} // overwriteCD
func deleteAllCDRecords(managedContext: NSManagedObjectContext, in entity: String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try managedContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey: objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as [AnyHashable: Any], into: [managedContext])
} catch let error as NSError {
// error handling
}
} // deleteAllCDRecords
func saveManagedContext(managedContext: NSManagedObjectContext) {
if !managedContext.hasChanges { return }
do {
try managedContext.save()
} catch let error as NSError {
// error handling
}
} // saveManagedContext
答案 0 :(得分:0)
您确定对于所有请求的实体,sqlite3.InterfaceError: Error binding parameter 2 - probably unsupported type.
都不为零吗?只需使用guard-let避免使用import sqlite3, rsa
db = sqlite3.connect('database.db')
db.execute('drop table if exists user')
db.execute('create table user (username text, password text, pubKey tuple,
privKey tuple)')
username = input("Input Username: ")
password = input("Input Password: ")
confirm = input("Confirm Password: ")
(pubKey, privKey) = rsa.newkeys(512)
if password == confirm:
db.execute('insert into user (username, password, pubKey, privKey) values (?, ?, ?, ?)', (username, password, pubKey, privKey))
db.commit()
else:
quit()
作为可选变量。同样,name
也不是解开可选变量的安全方法,尤其是在不确定数据源的情况下。
答案 1 :(得分:0)
我的代码出现的问题显然是竞争条件:
在“获取”线程获取核心数据记录并尝试将属性分配给属性的同时,“存储”线程删除了记录。
显然这释放了属性对象,因此nil
被存储为属性。
我认为persistentContainer
会自动阻止这种情况,但事实并非如此。
解决方案是在一个并发串行队列中同时执行persistentContainer
的两个后台线程,同步执行“ fetch”线程,并异步执行“存储”线程。障碍。
因此,可以执行并发获取,而存储将等待直到所有当前获取完成为止。
并发串行队列定义为
let localStoreQueue = DispatchQueue(label: "com.xxx.yyy.LocalStore.localStoreQueue",
attributes: .concurrent)
编辑:
在以下获取和存储函数中,我将核心数据函数persistentContainer.performBackgroundTask
移到了localStoreQueue
内。如果它不在我的原始答案中,则localStoreQueue.async(flags: .barrier)
中的存储代码将设置一个新线程,从而在创建它的另一个线程中使用managedContext
,这是核心数据多线程错误
“ fetch”线程被修改为
localStoreQueue.sync {
self.persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
//…
} // performBackgroundTask
} // localStoreQueue.sync
,“ store”线程为
localStoreQueue.async(flags: .barrier) {
self.persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
//…
} // performBackgroundTask
} // localStoreQueue.async