4

我已经使用 Realm 一段时间了,我对它非常满意!但是,我在执行过程中偶然发现了一些问题。

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

我有一个带有 Person 对象数据库的领域。这些都呈现在 UITableView 中。我想保持对象的特定顺序,并且用户应该能够重新排序对象。从我读过的内容来看,我必须使用 Realms 'List' 来实现这一点。这再次意味着我有一个名为 Person 的类和一个名为 PersonList 的类。PersonList 只有一个属性: - list。

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

我的问题:

  1. 在我的领域中只有一个 PersonList 实例的最佳做法是什么?正如您在下面的示例中看到的,我首先检查是否存在,如果不存在,则创建它。

  2. 使用领域通知的最佳实践是什么。将它添加到我的领域中一个 PersonList 对象的列表属性是否正确?

  3. 假设我想要一个单独的类来处理我的领域中的写事务。正如您在我的示例中看到的那样,所有读/写事务都保存在 UITableViewController 类中——这被认为是混乱的吗?

我下面的示例应该能够使用 Xcode 8、Swift 3 和 Realm 1.1.0 正常运行。

我感谢任何反馈和想法!

问候, 埃里克

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

1 回答 1

4

感谢您使用 Realm!至于你的问题:

在我的领域中只有一个 PersonList 实例的最佳做法是什么?正如您在下面的示例中看到的,我首先检查是否存在,如果不存在,则创建它。

有几种方法可以处理这种情况。我建议您提供PersonList一个主键,并且每当您使用PersonList. Realm 强制执行一个不变式,即只能存储一个具有给定主键值的对象。

像这样:

  • 与您的常量主键一起使用Realm.object(ofType:forPrimaryKey:)以获取现有的PersonList.
  • 如果该方法返回nil,则创建一个新的PersonList.
  • 任何时候你想保存PersonList,使用Realm.add(_:update:)update设置为true。如果对象不存在,这将添加该对象,或者如果先前添加了该对象,则更新数据库中的现有副本。

使用领域通知的最佳实践是什么。将它添加到我的领域中一个 PersonList 对象的列表属性是否正确?

是的,您对通知的使用对我来说似乎很合适。

假设我想要一个单独的类来处理我的领域中的写事务。正如您在我的示例中看到的那样,所有读/写事务都保存在 UITableViewController 类中——这被认为是混乱的吗?

这更像是一个编码风格问题,而不是 Realm 问题,但这最终是个人喜好问题。如果您想避免使用所有逻辑创建“大型视图控制器”,您可以尝试以下几件事:

  • 将您的视图控制器类拆分为一个主类和多个扩展,每个扩展都存在于自己的文件中。例如,您可能有一个与领域相关的方法的扩展,一个用于表视图委托/数据源方法等。请注意,存储属性不能存在于扩展中,必须在主类声明中声明。

  • 您可以创建一个或多个辅助类来组织您的逻辑。例如,您有几个方法可以显示模式弹出窗口并写入 Realm。那些不一定必须住在表格视图类中,并且可以住在一个PersonManager类中。此类将负责创建和呈现警报控制器以及与领域交互。然后,如果您PersonManager需要与表视图控制器进行通信,则可以使用基于闭包的回调或委托模式(尽管 Realm 通知会自动处理刷新表视图,这甚至可能没有必要!)。

希望有帮助。

于 2016-09-27T00:48:39.883 回答