如何在获取 api 响应并存储在 coredata 中然后在 UItableview 中显示后消除重复数据

Posted

技术标签:

【中文标题】如何在获取 api 响应并存储在 coredata 中然后在 UItableview 中显示后消除重复数据【英文标题】:How to eliminate duplicate data after fetching api response and stored in coredata then display in UItableview 【发布时间】:2020-10-30 18:14:10 【问题描述】:

我解析了 Api 并存储在 CoreData 中,但是当我在 UITableView 中显示结果时,它会显示图像、姓名和电子邮件两次。如何消除重复并仅显示唯一数据。我还添加了一个约束和“context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy,它在每次运行后停止合并数据,但现在它显示所有数据两次。”

任何帮助都将不胜感激

视图控制器

import UIKit
import Kingfisher

class ViewController: UIViewController 

    @IBOutlet weak var textName: UITextField!
    @IBOutlet weak var textAge: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    let database = DatabaseHandle.shared
    var users: [User]?
        didSet
            DispatchQueue.main.async 
                self.tableView.reloadData()
            
        
    
    
    override func viewDidLoad() 
        super.viewDidLoad()
        //tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserTableViewCell")
        tableView.tableFooterView = UIView(frame: .zero)
    
    override func viewWillAppear(_ animated: Bool) 
        users = database.fetch(User.self)
        
        //print(users)
    
    override func viewDidAppear(_ animated: Bool) 
        ApiHandler.shared.syncUser 
            self.users = self.database.fetch(User.self)
        
    
    

extension ViewController: UITableViewDataSource,UITableViewDelegate
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return users?.count ?? 0
    
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
        
        cell.user = users?[indexPath.row]
        
        return cell
    
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool 
        return true
    
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) 
        guard let user = users?[indexPath.row] else return
        tableView.beginUpdates()
        self.database.delete(object: user)
        users?.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
        tableView.endUpdates()
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return 60
    

**ApiHandler **

import UIKit

class ApiHandler
    
    static let shared = ApiHandler()
    
    func syncUser(completion: @escaping (() -> Void))
        var req = URLRequest(url: URL(string: "https://reqres.in/api/users?page=2")!)
        req.httpMethod = "GET"
        let session = URLSession.shared
        
        let task = session.dataTask(with: req, completionHandler:  data,response,error -> Void in
            print(response!)
            
            do
                let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary <String,AnyObject>
                let model = try JSONDecoder().decode(ApiResponse<[UserServerModel]>.self, from: data!)
                model.data.forEach($0.store())
                print(json)
                completion()
            catch
                print(error.localizedDescription)
                completion()
            
            
            
        )
        task.resume()
        
    


public struct ApiResponse<T: Codable>: Codable 
    public let total_pages: Int
    public let per_page: Int
    public let data: T
    public let page: Int
    public let total: Int
    

数据库句柄

import UIKit
import CoreData

class DatabaseHandle
    
    private var viewContext:NSManagedObjectContext
    static let shared = DatabaseHandle()
    init() 
        viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    
    func add<T: NSManagedObject>(_ type: T.Type)-> T?
        guard let entityName = T.entity().name else return nil
        guard let entity = NSEntityDescription.entity(forEntityName: entityName, in: viewContext) elsereturn nil
        let object = T(entity: entity, insertInto: viewContext)
        return object
        
    
    func fetch<T:NSManagedObject>(_ type: T.Type) -> [T] 
        let request = T.fetchRequest()
        do
            let result = try viewContext.fetch(request)
            
            return result as! [T]
        catch
            print(error.localizedDescription)
            return []
        
        
    
    
    func save()
        do
            try viewContext.save()
        catch
            print(error.localizedDescription)
        
    
    
    func delete<T:NSManagedObject>(object: T)
        viewContext.delete(object)
        save()
    

【问题讨论】:

【参考方案1】:

当你打电话时

model.data.forEach($0.store())

我不确定 store 是否是一个自定义函数,但也许你没有首先检查它是否已经存在于 coredata 中,所以你最终只是保存了两次。

【讨论】:

是的 store 是一个自定义函数。 import Foundation struct UserServerModel:Codable var avatar: String var email: String var first_name: String var last_name: String var id: Int static let database = DatabaseHandle.shared func store() guard let user = UserServerModel.database.add(User.self) else return user.avatar = avatar user.email = email user.first_name = first_name user.last_name = last_name user.id = Int16(id) UserServerModel.database.save() 为什么要在guard语句中添加用户? 除非我在用户中使用保护声明,否则我必须解开 user.avatar = avatar user.email = email user.first_name = first_name user.last_name = last_name user.id = Int16(id) 某处我必须添加一个条件来检查重复元素并在存储到 coredata 之前删除它,我不想维护冗余数据,然后在获取时过滤。 在添加之前不检查重复项,听起来像是问题的根源。【参考方案2】:

正如其他人所提到的,您需要在添加新记录之前检查持久层是否存在记录。我建议实现一个协议,以便您的其他 API 数据结构也可以持久化。

protocol Persistable 
    associatedtype Persisted: NSManagedObject
    var predicate: NSPredicate?  get 
    func persist(into object: Persisted)


extension NSPredicate 
    static func userId(equalTo id: Int16) -> NSPredicate 
        return .init(format: "id == %d", id)
    


extension UserServerModel: Persistable 

    var predicate: NSPredicate?  .userId(equalTo: Int16(self.id)) 

    func persist(into object: User) 
        object.avatar = avatar
        object.email = email
        object.first_name = first_name
        object.last_name = last_name
        object.id = Int16(id)
    

我不喜欢将存储库逻辑与我的模型混合在一起,因此我会在您的 API 和持久性模型之间建立某种桥梁。

enum PersistenceBridge 

    @discardableResult
    static func updateOrCreate<T: Persistable>(model: T) -> T.Persisted? 
        return update(model: model) ?? create(model: model)
    

    @discardableResult
    static func update<T: Persistable>(model: T) -> T.Persisted? 
        let object = DatabaseHandle.shared.fetch(T.Persisted.self, predicate: model.predicate).first
        return persist(model: model, into: object)
    

    @discardableResult
    static func create<T: Persistable>(model: T) -> T.Persisted? 
        let object = DatabaseHandle.shared.add(T.Persisted.self)
        return persist(model: model, into: object)
    

    static func persist<T: Persistable>(model: T, into object: T.Persisted?) -> T.Persisted? 
        if let object = object  model.persist(into: object) 
        return object
    

您需要在 fetch 方法中添加谓词参数,以便限制结果:

extension DatabaseHandle 

    func fetch<T:NSManagedObject>(_ type: T.Type, predicate: NSPredicate? = nil) -> [T] 
        let request = T.fetchRequest()
        request.predicate = predicate
        do
            let result = try viewContext.fetch(request)

            return result as! [T]
        catch
            print(error.localizedDescription)
            return []
        
    

最后,为您的 ApiHandler 添加一些持久性逻辑,一切顺利:

extension ApiHandler 
    func persist<T: Persistable>(response: ApiResponse<[T]>) 
        response.data.forEach 
            PersistenceBridge.updateOrCreate(model: $0)
        
    

【讨论】:

以上是关于如何在获取 api 响应并存储在 coredata 中然后在 UItableview 中显示后消除重复数据的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式在运行时删除或添加属性到CoreData

我如何使用 fetch API 并在状态下存储响应?

如何在 MYSQL 数据库中存储 API JSON 响应

在 CoreData (Swift) 中存储位置

如何在 UITableView 中显示 Core Data 的数据

SwiftUI CoreData - 存储获取请求数据