Swift中CoreData加载/保存出现重复条目,请求排查问题
我在开发iOS应用时,使用Swift实现了CoreData的加载与保存功能,但遇到了重复条目问题——我已尝试添加去重判断,但并未生效。恳请各位帮忙排查问题所在,谢谢!
ViewController类代码
import UIKit import CoreData class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var headerLabel:UILabel! @IBOutlet weak var myTableView: UITableView! var lenders = [LenderData]() var lendersTemp = [LenderData]() override func viewDidLoad() { super.viewDidLoad() self.myTableView.rowHeight = 90 myTableView.delegate = self myTableView.dataSource = self let fetchRequest: NSFetchRequest<LenderData> = LenderData.fetchRequest() do { let lenders = try PersistenceService.context.fetch(fetchRequest) self.lenders = lenders } catch { // Who cares.... } downloadJSON { for tempLender in self.lendersTemp { if !self.lenders.contains(where: {$0.id == tempLender.id}) { self.lenders.append(tempLender) } } self.lendersTemp.removeAll() PersistenceService.saveContext() self.myTableView.reloadData() } } func downloadJSON(completed: @escaping () -> ()) { let url = URL(string: "https://api.kivaws.org/v1/loans/newest.json") let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if error != nil { print("JSON not downloaded") } else { if let content = data { do { let myJSONData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject var imageID:Int64 = -1 var country:String = "N/A" var latLongPair:String = "0.000000 0.000000" var town:String = "N/A" if let loans = myJSONData["loans"] as? NSArray { for i in 0...loans.count-1 { if let lender = loans[i] as? NSDictionary { if let imageData = lender["image"] as? NSDictionary { imageID = imageData["id"] as! Int64 } if let countryData = lender["location"] as? NSDictionary { country = countryData["country"] as! String town = countryData["town"] as! String if let geo = countryData["geo"] as? NSDictionary { latLongPair = geo["pairs"] as! String } } let newLender = LenderData(context: PersistenceService.context) newLender.id = lender["id"] as! Int64 newLender.name = lender["name"] as? String newLender.image_id = imageID newLender.activity = lender["activity"] as? String newLender.use = lender["use"] as? String newLender.loan_amount = lender["loan_amount"] as! Int32 newLender.funded_amount = lender["funded_amount"] as! Int32 newLender.country = country newLender.town = town newLender.geo_pairs = latLongPair self.lendersTemp.append(newLender) } } } DispatchQueue.main.async { completed() } } catch { print("Error occured \(error)") } } } } task.resume() } }
编辑说明
补充了填充lendersTemp数组的代码部分的内容
嘿,我看了你遇到的CoreData重复问题,咱们一步步来捋清楚问题出在哪:
核心问题:你提前把实例加入了CoreData上下文
你现在的逻辑是先创建newLender = LenderData(context: PersistenceService.context),这行代码会直接把新实例添加到CoreData的上下文中——哪怕你之后通过数组判断没把它加入lenders,最后调用saveContext()的时候,上下文里所有的对象(包括这些你以为“被过滤掉”的重复实例)都会被写入数据库,这就是为什么去重判断完全失效的原因!
解决方案:先检查再创建,避免提前加入上下文
我们需要调整逻辑:在创建新的LenderData实例之前,先检查CoreData数据库里是否已经存在对应ID的条目,只有不存在的时候才创建并添加到上下文。
修改downloadJSON里的循环部分代码如下:
if let loans = myJSONData["loans"] as? NSArray { for i in 0...loans.count-1 { if let lender = loans[i] as? NSDictionary { // 先取出当前贷款的ID let loanID = lender["id"] as! Int64 // 构造查询请求,检查数据库中是否已有该ID的条目 let checkFetchRequest: NSFetchRequest<LenderData> = LenderData.fetchRequest() checkFetchRequest.predicate = NSPredicate(format: "id == %lld", loanID) do { let existingEntries = try PersistenceService.context.fetch(checkFetchRequest) // 只有不存在对应条目时,才创建新实例 if existingEntries.isEmpty { var imageID:Int64 = -1 var country:String = "N/A" var latLongPair:String = "0.000000 0.000000" var town:String = "N/A" if let imageData = lender["image"] as? NSDictionary { imageID = imageData["id"] as! Int64 } if let countryData = lender["location"] as? NSDictionary { country = countryData["country"] as! String town = countryData["town"] as! String if let geo = countryData["geo"] as? NSDictionary { latLongPair = geo["pairs"] as! String } } let newLender = LenderData(context: PersistenceService.context) newLender.id = loanID newLender.name = lender["name"] as? String newLender.image_id = imageID newLender.activity = lender["activity"] as? String newLender.use = lender["use"] as? String newLender.loan_amount = lender["loan_amount"] as! Int32 newLender.funded_amount = lender["funded_amount"] as! Int32 newLender.country = country newLender.town = town newLender.geo_pairs = latLongPair // 直接添加到数据源数组 self.lenders.append(newLender) } } catch { print("检查重复条目出错: \(error)") } } } }
同时,你可以把lendersTemp数组删掉了——现在我们只在需要的时候创建实例,直接加入lenders即可,不需要临时数组中转。
额外保障:给ID字段添加唯一约束
为了避免以后代码逻辑出错导致重复数据,建议给CoreData模型里的LenderData实体的id字段设置唯一约束:
- 打开你的CoreData模型文件(
.xcdatamodeld) - 选中
LenderData实体 - 在右侧的Data Model Inspector里找到“Constraints”区域
- 添加一行约束,属性选择
id,类型选择Unique
这样即使代码里的判断漏了,CoreData在保存时也会抛出错误,阻止重复数据写入。
最后调整viewDidLoad里的回调逻辑
原来的回调代码可以简化成这样:
downloadJSON { PersistenceService.saveContext() self.myTableView.reloadData() }
因为现在我们已经在创建实例时直接添加到lenders数组了,不需要再遍历临时数组做判断。
内容的提问来源于stack exchange,提问作者IncrediblePony

