什么是最佳实践 - Realm有序列表?

4

我已经使用Realm一段时间了,非常满意!但是在实现过程中我遇到了一些问题。

我制作了一个测试场景来尝试指出我需要一些输入的地方。

我有一个带有Person对象数据库的Realm。这些都在UITableView中显示。我想保持对象的特定顺序,并且用户应该能够重新排序对象。根据我所读的,我必须使用Realms 'List'来实现这一点。这又意味着我有一个名为Person的类和一个名为PersonList的类。PersonList只有一个属性:- list。

应用程序在其Realm中应该只有一个PersonList对象,但可以有多个Person对象。

我的问题:

  1. 在我的Realm中只有一个PersonList实例的最佳实践是什么?如您所见,在下面的示例中,我首先检查是否存在,如果不存在则创建它。

  2. 当涉及到Realm通知的使用时,最佳实践是什么?将其添加到我的Realm中的一个PersonList对象的列表属性中是否正确?

  3. 假设我想要一个单独的类来处理我的Realm中的写入事务。如您所见,在我的示例中,所有读/写事务都保存在UITableViewController类中 - 这被认为是混乱的吗?

我的示例应该能够在Xcode 8、Swift 3和Realm 1.1.0上正常运行。

我欢迎任何反馈和想法!

问候,Erik

import UIKit
import RealmSwift

class PersonList : Object {
    var list = List<Person>()
}

class Person : Object {

    dynamic var favorite = false

    dynamic var username : String?
    dynamic var firstName : String?
    dynamic var lastName : String?

    var fullName : String? {
        get {

            guard let firstName = firstName, let lastName = lastName else {

                return nil
            }

            return "\(firstName) \(lastName)"
        }
    }
}

class ViewController: UITableViewController {

    var results : List<Person>?
    var notificationToken: NotificationToken? = nil

    func addPerson() {

        let alert = UIAlertController(title: "Add Person", message: "Please fill in the information", preferredStyle: .alert)

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { alertAction in

            if let firstNameTextField = alert.textFields?[0], let lastNameTextField = alert.textFields?[1] {

                self.savePerson(firstName: firstNameTextField.text, lastName: lastNameTextField.text)
            }

        }))

        alert.addTextField { (textField : UITextField!) -> Void in
            textField.placeholder = "First Name"
        }
        alert.addTextField { (textField : UITextField!) -> Void in
            textField.placeholder = "Last Name"
        }

        self.present(alert, animated: true, completion: nil)

    }

    func savePerson(firstName: String?, lastName: String?) {

        guard let firstName = firstName, !firstName.isEmpty else {

            let alert = UIAlertController(title: "Oops!", message: "First name missing!", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

            self.present(alert, animated: true, completion: nil)

            return
        }

        guard let lastName = lastName, !lastName.isEmpty else {

            let alert = UIAlertController(title: "Oops!", message: "Last name missing!", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

            self.present(alert, animated: true, completion: nil)

            return
        }

        let realm = try! Realm()

        let newPerson = Person()
        newPerson.firstName = firstName
        newPerson.lastName = lastName
        newPerson.username = "\(Date())"

        do {
            try realm.write {

                results?.append(newPerson)

            }
        }
        catch let error {
            print("Error: \(error)")
        }
    }

    func editButtonAction(_ sender: UIBarButtonItem) {

        if tableView.isEditing {

            tableView.setEditing(false, animated: true)

            sender.title = "Edit"
        }
        else {

            tableView.setEditing(true, animated: true)

            sender.title = "Done"
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addPerson))

        let editButton = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.plain, target: self, action: #selector(self.editButtonAction(_:)))

        self.navigationItem.rightBarButtonItems = [addButton, editButton]

        tableView.allowsSelectionDuringEditing = true

        let realm = try! Realm()


        //First, make sure a list exists in realm
        if realm.objects(PersonList.self).first?.list == nil {

            print("No existing list found in realm. Creating one.")

            let defaultList = PersonList()

            do {
                try realm.write {

                    realm.add(defaultList)

                }
            }
            catch let error { print("Error creating person list: \(error)") }

        }

        results = realm.objects(PersonList.self).first?.list

        // Observe Results Notifications
        notificationToken = results?.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
            guard let tableView = self?.tableView else { return }
            switch changes {
            case .initial:
                // Results are now populated and can be accessed without blocking the UI
                tableView.reloadData()
                break
            case .update(_, let deletions, let insertions, let modifications):

                // Query results have changed, so apply them to the UITableView
                tableView.beginUpdates()

                tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)

                tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                tableView.endUpdates()
                break
            case .error(let error):
                // An error occurred while opening the Realm file on the background worker thread
                print("Error: \(error)")
                break
            }
        }
    }

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

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return results?.count ?? 0
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let reuseIdentifier = "PersonTestCell"

        var cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier)

        if cell == nil {

            cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier: reuseIdentifier)
        }

        if let results = self.results {

            let person = results[indexPath.row]

            cell!.textLabel?.text = person.fullName ?? "Name not found."

            cell!.detailTextLabel?.text = person.username ?? "Username not found."

        }


        return cell!

    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        tableView.deselectRow(at: indexPath, animated: true)
    }

    // Override to support conditional editing of the table view.
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {

        return true
    }

    // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == .delete {

            if let results = self.results {

                //Delete Person
                let realm = try! Realm()

                do {
                    try realm.write {

                        results.remove(objectAtIndex: indexPath.row)

                    }
                }
                catch let error {
                    print("Error: \(error)")
                }
            }

        }

    }

    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {

        return UITableViewCellEditingStyle.delete
    }

    // Override to support rearranging the table view.
    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to toIndexPath: IndexPath) {

        let realm = try! Realm()

        do {
            try realm.write {

                results?.move(from: toIndexPath.row, to: fromIndexPath.row)
                results?.move(from: fromIndexPath.row, to: toIndexPath.row)
            }
        }
        catch let error {
            print("Error: \(error)")
        }


    }

    // Override to support conditional rearranging of the table view.
    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the item to be re-orderable.

        return true

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    deinit {
        notificationToken?.stop()
    }
}
1个回答

4
感谢使用Realm!以下是您的问题:
关于我Realm中只有一个PersonList实例的最佳实践是什么? 正如您在下面的示例中看到的那样,我首先检查是否存在它,如果不存在,则创建它。
您可以处理此情况的几种方式。我建议您给PersonList一个主键,并且每当您使用PersonList时都使用常量值作为该主键。 Realm强制执行只能存储具有给定主键值的一个对象的不变性。
因此:
- 使用带有常数主键的Realm.object(ofType:forPrimaryKey :)来获取现有的PersonList。 - 如果该方法返回nil,则创建一个新的PersonList。 - 每次要保存PersonList时,请使用Realm.add(_:update :),将update设置为true。 这将添加该对象(如果不存在),或者如果先前已添加,则更新数据库中的现有副本。
对于使用Realm通知的最佳实践是什么? 将其添加到我Realm中的一个PersonList对象的列表属性中是否正确?
是的,我认为您使用通知的方法是适当的。
假设我想有一个单独的类来处理我的Realm中的写入事务。 如您在我的示例中所见,所有读/写事务都保存在UITableViewController类中 - 这被认为是混乱吗?
这更多是一种编码风格问题而不是Realm问题,但这最终是个人喜好问题。 如果您想避免创建“大型视图控制器”,并且所有逻辑都在其中,可以尝试以下几种方法:
- 将您的视图控制器类拆分为一个主类和若干扩展,每个扩展都存在于自己的文件中。例如,您可能会针对Realm相关方法、表视图委托/数据源方法等创建一个扩展。请注意,存储的属性无法存在于扩展中,必须在主类声明中声明。 - 您可以创建一个或多个助手类来组织您的逻辑。例如,您有一些方法展示模态弹出块并编写到Realm中。这些不一定要生存在表视图类中,而是可以生存在PersonManager类中。该类将负责创建和呈现警报控制器以及与Realm交互。然后,如果您的PersonManager需要向表视图控制器通信,则可以使用基于闭包的回调或代理模式(尽管,由于Realm通知自动处理刷新表视图,这甚至可能不是必要的!)。
希望可以帮到您。

感谢您的详尽解释!这让我对事情更加清晰了。 - fisher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接