简体   繁体   English

将数组保存到CoreData Swift

[英]Saving array to CoreData Swift

I would like to save this kind of arrays with Core Data : 我想用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")

Is that even possible? 这甚至可能吗?

I know I can create an array to save like that... 我知道我可以创建一个像这样保存的数组......

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()

...but this seems pretty inconvenient when a theoretical infinite number of arrays will be created by the user. ...但是当用户创建理论上无限数量的数组时,这似乎非常不方便。

What would be the best way for me to save data, load it, edit it and delete it? 对我来说,保存数据,加载,编辑和删除数据的最佳方法是什么?

This is a question for a tutorial rather than a straight forward answer. 这是一个教程而不是直接答案的问题。 I suggest you give some time to read about CoreData . 我建议你花点时间阅读一下CoreData Having said that, your question sounds generic, "Saving array to CoreData in Swift", so I guess it doesn't hurt to explain a simple implementation step by step: 话虽如此,你的问题听起来很通用,“在Swift中将数组保存到CoreData”,所以我想一步一步地解释一个简单的实现并没有什么坏处:

Step 1: Create your model file (.xcdatamodeld) 第1步:创建模型文件(.xcdatamodeld)

In Xcode, file - new - file - iOS and choose Data Model 在Xcode中, file - new - file - iOS并选择Data Model

Step 2: Add entities 第2步:添加实体

Select the file in Xcode, find and click on Add Entity , name your entity ( CryptosMO to follow along), click on Add Attribute and add the fields you like to store. 在Xcode中选择文件,找到并单击Add Entity ,命名您的实体( CryptosMO跟随),单击Add Attribute并添加您想要存储的字段。 ( name, code, symbol... all of type String in this case). name, code, symbol...在这种情况下所有类型为String )。 I'll ignore everything else but name for ease. 我会忽略其他一切,但为了轻松而name

Step 3 Generate Object representation of those entities (NSManagedObject) 步骤3生成这些实体的对象表示(NSManagedObject)

In Xcode, Editor - Create NSManagedObject subclass and follow the steps. 在Xcode中, Editor - Create NSManagedObject subclass并按照步骤操作。

Step 4 Lets create a clone of this subclass 步骤4让我们创建这个子类的克隆

NSManagedObject is not thread-safe so lets create a struct that can be passed around safely: NSManagedObject不是线程安全的,所以让我们创建一个可以安全传递的结构:

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

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

Step 5: CoreDataStore 第5步:CoreDataStore

Lets create a store that gives us access to NSManagedObjectContext s: 让我们创建一个商店,让我们访问NSManagedObjectContext

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 sets up a persistent container using your .xcdatamodeld file. Store使用.xcdatamodeld文件设置持久性容器。

Step 6: Data source to fetch these entities 第6步:获取这些实体的数据源

Core Data comes with NSFetchedResultsController to fetch entities from a context that allows extensive configuration, here is a simple implementation of a data source support using this controller. 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)
    }
}

All we need from an instance of this class is, number of objects, count and item at a given indexPath. 我们从这个类的一个实例所需要的只是给定indexPath的对象countcount和item。 Note that the data source returns the struct Cryptos and not an instance of NSManagedObject . 请注意,数据源返回struct Cryptos而不是NSManagedObject的实例。

Step 7: APIs for add, edit and delete 第7步:添加,编辑和删除的API

Lets add this apis as an extension to NSManagedObjectContext : But before that, these actions may succeed or fail so lets create an enum to reflect that: 让我们将此api添加为NSManagedObjectContext的扩展:但在此之前,这些操作可能成功或失败,因此我们创建一个枚举来反映:

enum Result {
    case success, fail(Error)
} 

The APIs: 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))
        }
    }
}

Step 8: Last step, use case 第8步:最后一步,用例

To fetch the stored data in main queue, use Store.viewContext.dataSource . 要获取主队列中存储的数据,请使用Store.viewContext.dataSource To add, edit or delete an item, decide if you'd like to do on main queue using viewContext , or from any arbitrary queue (even main queue) using newContext or a temporary background context provided by Store container using Store.container.performInBackground... which will expose a context. 要添加,编辑或删除项目,请使用viewContext确定是否要在主队列上viewContext ,或使用newContext或使用Store.container.performInBackground...的Store容器提供的临时后台上下文从任意任意队列(甚至是主队列) Store.container.performInBackground...将暴露一个背景。 eg adding a cryptos: 例如,添加密码:

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")
   }
}

Simple UITableViewController that uses the cryptos data source: 使用密码数据源的简单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.
    }
}

You cannot save arrays directly with CoreData, but you can create a function to store each object of an array. 您无法使用CoreData直接保存数组,但您可以创建一个函数来存储数组的每个对象。 With CoreStore the whole process is quite simple: 使用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 })
}

You can show the objects within a UITableViewController . 您可以在UITableViewController显示对象。 CoreStore is able to automatically update the table whenever database objects are added, removed or updated: 每当添加,删除或更新数据库对象时,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)
    }

}

Assuming that you have a CryptoTableViewCell with the function update registered to the CryptoTableViewController . 假设您有一个CryptoTableViewCell ,其功能update已注册到CryptoTableViewController

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM