将数组保存到 CoreData Swift

Posted

技术标签:

【中文标题】将数组保存到 CoreData Swift【英文标题】:Saving array to CoreData Swift 【发布时间】:2018-03-02 20:31:25 【问题描述】:

我想用Core Data保存这种arrays

let crypto1 = Cryptos(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0")
let crypto2 = Cryptos(name: "Bitcoin Cash", code: "bitcoinCash", symbol: "BCH", placeholder: "BCH Amount", amount: "0.0")

这可能吗?

我知道我可以创建一个数组来保存...

let name = "Bitcoin"
let code = "bitcoin"
let symbol = "BTC"
let placeholder = "BTC Amount"
let amount = "0.0"
let cryptos = CryptoArray(context: PersistenceService.context)
cryptos.name = name
cryptos.code = code
cryptos.symbol = symbol
cryptos.placeholder = placeholder
cryptos.amount = amount
crypto.append(cryptos)
PersistenceService.saveContext()

...但是当用户将创建理论上无限数量的数组时,这似乎很不方便。

对我来说保存、加载、编辑和删除数据的最佳方式是什么?

【问题讨论】:

不保存数组。保存 Core Data 实体的多个实例。 @Paulw11 非常感谢您的回答!这很有趣,但我认为我不完全理解为什么。这对我来说意味着什么? (我刚开始使用 Core Data,如果我在这里看不到明显的错误,请原谅任何错误) Cryptos 的每个实例都将映射到一个 Core Data 实体实例。 (事实上​​,您的 Cryptos 对象很可能是您的 NSManagedObject 子类。 【参考方案1】:

这是一个教程问题,而不是直截了当的答案。我建议您花一些时间阅读有关CoreData 的信息。话虽如此,您的问题听起来很笼统,“将数组保存到 Swift 中的 CoreData”,所以我想逐步解释一个简单的实现并没有什么坏处:

第 1 步:创建模型文件 (.xcdatamodeld)

在 Xcode 中,file - new - file - ios 并选择 Data Model

第 2 步:添加实体

在 Xcode 中选择文件,找到并点击 Add Entity,命名您的实体(CryptosMO 跟随),点击 Add Attribute 并添加您想要存储的字段。 (在这种情况下,name, code, symbol... 的所有类型都是 String)。除了name,我将忽略其他所有内容。

第 3 步生成这些实体的对象表示 (NSManagedObject)

在 Xcode 中,Editor - Create NSManagedObject subclass 并按照步骤操作。

第 4 步让我们创建这个子类的克隆

NSManagedObject 不是线程安全的,所以让我们创建一个可以安全传递的结构:

struct Cryptos 
    var reference: NSManagedObjectID! // ID on the other-hand is thread safe. 

    var name: String // and the rest of your properties
 

第 5 步:CoreDataStore

让我们创建一个商店,让我们可以访问NSManagedObjectContexts:

class Store 
    private init() 
    private static let shared: Store = Store()

    lazy var container: NSPersistentContainer = 

        // The name of your .xcdatamodeld file.
        guard let url = Bundle().url(forResource: "ModelFile", withExtension: "momd") else 
            fatalError("Create the .xcdatamodeld file with the correct name !!!")
            // If you're setting up this container in a different bundle than the app,
            // Use Bundle(for: Store.self) assuming `CoreDataStore` is in that bundle.
        
        let container = NSPersistentContainer(name: "ModelFile")
        container.loadPersistentStores  _, _ in 
        container.viewContext.automaticallyMergesChangesFromParent = true

        return container
    ()

    // MARK: APIs

    /// For main queue use only, simple rule is don't access it from any queue other than main!!!
    static var viewContext: NSManagedObjectContext  return shared.container.viewContext 

    /// Context for use in background.
    static var newContext: NSManagedObjectContext  return shared.container.newBackgroundContext() 

Store 使用您的.xcdatamodeld 文件设置一个持久化容器。

第 6 步:获取这些实体的数据源

Core Data 带有NSFetchedResultsController,用于从允许广泛配置的上下文中获取实体,这是使用此控制器的数据源支持的简单实现。

class CryptosDataSource 

    let controller: NSFetchedResultsController<NSFetchRequestResult>
    let request: NSFetchRequest<NSFetchRequestResult> = CryptosMO.fetchRequest()

    let defaultSort: NSSortDescriptor = NSSortDescriptor(key: #keyPath(CryptosMO.name), ascending: false)

    init(context: NSManagedObjectContext, sortDescriptors: [NSSortDescriptor] = []) 
        var sort: [NSSortDescriptor] = sortDescriptors
        if sort.isEmpty  sort = [defaultSort] 

        request.sortDescriptors = sort

        controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
    

    // MARK: DataSource APIs

    func fetch(completion: ((Result) -> ())?) 
        do 
            try controller.performFetch()
            completion?(.success)
         catch let error 
            completion?(.fail(error))
        
    

    var count: Int  return controller.fetchedObjects?.count ?? 0 

    func anyCryptos(at indexPath: IndexPath) -> Cryptos 
        let c: CryptosMO = controller.object(at: indexPath) as! CryptosMO
        return Cryptos(reference: c.objectID, name: c.name)
    

我们需要从这个类的实例中得到对象的数量、count 和给定 indexPath 处的项目。请注意,数据源返回结构体Cryptos,而不是NSManagedObject 的实例。

第 7 步:用于添加、编辑和删除的 API

让我们添加这个 api 作为NSManagedObjectContext 的扩展: 但在此之前,这些操作可能成功或失败,所以让我们创建一个枚举来反映这一点:

enum Result 
    case success, fail(Error)
 

API:

extension NSManagedObjectContext 

    // MARK: Load data

    var dataSource: CryptosDataSource  return CryptosDataSource(context: self) 

    // MARK: Data manupulation

    func add(cryptos: Cryptos, completion: ((Result) -> ())?) 
        perform 
            let entity: CryptosMO = CryptosMO(context: self)
            entity.name = cryptos.name
            self.save(completion: completion)
        
    

    func edit(cryptos: Cryptos, completion: ((Result) -> ())?) 
        guard cryptos.reference != nil else  
            print("No reference")
            return 
        
        perform 
            let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
            entity?.name = cryptos.name
            self.save(completion: completion)
        
    

    func delete(cryptos: Cryptos, completion: ((Result) -> ())?) 
        guard cryptos.reference != nil else  
            print("No reference")
            return 
        
        perform 
            let entity: CryptosMO? = self.object(with: cryptos.reference) as? CryptosMO
            self.delete(entity!)
            self.save(completion: completion)
        
    

    func save(completion: ((Result) -> ())?) 
        do 
            try self.save()
            completion?(.success)
         catch let error 
            self.rollback()
            completion?(.fail(error))
        
    

第 8 步:最后一步,用例

要获取主队列中存储的数据,请使用Store.viewContext.dataSource。 要添加、编辑或删除项目,请决定您是要使用 viewContext 在主队列上执行操作,还是使用 newContext 从任意队列(甚至主队列)执行操作,或使用 @ 存储容器提供的临时后台上下文987654347@ 将公开一个上下文。 例如添加密码:

let cryptos: Cryptos = Cryptos(reference: nil, name: "SomeName")
Store.viewContext.add(cryptos: cryptos)  result in
   switch result 
   case .fail(let error): print("Error: ", error)
   case .success: print("Saved successfully")
   

使用密码数据源的简单UITableViewController

class ViewController: UITableViewController 

    let dataSource: CryptosDataSource = Store.viewContext.dataSource

    // MARK: UITableViewDataSource

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return dataSource.count
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        return tableView.dequeueReusableCell(withIdentifier: "YourCellId", for: indexPath)
    
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 
        let cryptos: Cryptos = dataSource.anyCryptos(at: indexPath)
        // TODO: Configure your cell with cryptos values.
    

【讨论】:

您能否添加方法来取回数据并在 indexPath 的 tableView 单元格中使用它我不知道如何处理 Store.viewContext.dataSource 以及它返回的内容 我有点迷失了,无法找回数组并将其用于我的单元格 Store.viewContext.dataSource 返回一个数据源实例,但我错过了一个用于获取的 api。我会在编辑时添加它 最后我还添加了一些代码来展示如何使用数据源。 我不会说谎我完全迷路了哈哈,我不应该同时学习tableViews和CoreData,我无法理解何时获取以及如何使用方法。有没有机会我可以将我的文件发送给您以供审查,并且您指出我需要在代码中使用 cmets 做什么?我真的需要自己动手,但文件和方法的多样性让我感到困惑,我需要指导。【参考方案2】:

您不能直接使用 CoreData 保存数组,但您可以创建一个函数来存储数组的每个对象。使用CoreStore,整个过程非常简单:

let dataStack: DataStack = 
    let dataStack = DataStack(xcodeModelName: "ModelName")
    do 
        try dataStack.addStorageAndWait()
     catch let error 
        print("Cannot set up database storage: \(error)")
    
    return dataStack
()

func addCrypto(name: String, code: String, symbol: String, placeholder: String, amount: Double) 
    dataStack.perform(asynchronous:  transaction in
        let crypto = transaction.create(Into<Crypto>())
        crypto.name = name
        crypto.code = code
        crypto.symbol = symbol
        crypto.placeholder = placeholder
        crypto.amount = amount
    , completion:  _ in )

您可以在UITableViewController 中显示对象。 CoreStore 能够在添加、删除或更新数据库对象时自动更新表:

class CryptoTableViewController: UITableViewController 

    let monitor = dataStack.monitorList(From<Crypto>(), OrderBy(.ascending("name")), Tweak( fetchRequest in
        fetchRequest.fetchBatchSize = 20
    ))

    override func viewDidLoad() 
        super.viewDidLoad()
        // Register self as observer to monitor
        self.monitor.addObserver(self)
    

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return self.monitor.numberOfObjects()
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "CryptoTableViewCell", for: indexPath) as! CryptoTableViewCell
        let crypto = self.monitor[(indexPath as NSIndexPath).row]
        cell.update(crypto)
        return cell
    



// MARK: - ListObjectObserver

extension CryptoTableViewController : ListObjectObserver 

    // MARK: ListObserver

    func listMonitorWillChange(_ monitor: ListMonitor<Crypto>) 
        self.tableView.beginUpdates()
    

    func listMonitorDidChange(_ monitor: ListMonitor<Crypto>) 
        self.tableView.endUpdates()
    

    func listMonitorWillRefetch(_ monitor: ListMonitor<Crypto>) 
    

    func listMonitorDidRefetch(_ monitor: ListMonitor<Crypto>) 
        self.tableView.reloadData()
    

    // MARK: ListObjectObserver

    func listMonitor(_ monitor: ListMonitor<Crypto>, didInsertObject object: Switch, toIndexPath indexPath: IndexPath) 
        self.tableView.insertRows(at: [indexPath], with: .automatic)
    

    func listMonitor(_ monitor: ListMonitor<Crypto>, didDeleteObject object: Switch, fromIndexPath indexPath: IndexPath) 
        self.tableView.deleteRows(at: [indexPath], with: .automatic)
    

    func listMonitor(_ monitor: ListMonitor<Crypto>, didUpdateObject object: Crypto, atIndexPath indexPath: IndexPath) 
        if let cell = self.tableView.cellForRow(at: indexPath) as? CryptoTableViewCell 
            cell.update(object)
        
    

    func listMonitor(_ monitor: ListMonitor<Crypto>, didMoveObject object: Switch, fromIndexPath: IndexPath, toIndexPath: IndexPath) 
        self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
        self.tableView.insertRows(at: [toIndexPath], with: .automatic)
    


假设您有一个CryptoTableViewCell,其函数update 注册到CryptoTableViewController

【讨论】:

这看起来棒极了!它是否适合与 tableView 一起使用并轻松编辑/删除 indexPath 处的数组? 是的,CoreStore 提供了特殊功能来执行此操作。我相应地扩展了我的答案。

以上是关于将数组保存到 CoreData Swift的主要内容,如果未能解决你的问题,请参考以下文章

将数组保存到 CoreData Swift

在CoreData中保存数组的值时,只保存一个值

IOS/Objective-C:无法将 CoreData 值保存到 NSArray

如何一次将多个关系保存到 CoreData?

结构数组:如何保存在 coredata 中?

将对象数组保存到 Core Data