2016-09-26 51 views
2

我一直在使用Realm一段時間,我非常滿意!然而,我在實施過程中偶然發現了一些問題。什麼是最佳實踐 - 領域有序列表?

我做了一個測試場景,試圖指出我需要什麼輸入。

我有一個領域與個人對象的數據庫。這些都是在UITableView中呈現的。我想保持對象的特定順序,並且用戶應該能夠重新排序對象。從我讀過的,我必須使用Realms'List'來實現這一點。這也意味着我有一個名爲Person的類和一個名爲PersonList的類。 PersonList只有一個屬性: - 列表。

該應用程序應該只有一個PersonList對象的Realm,但可能有Person的幾個對象。

我的問題:

  1. 什麼是最好的做法是隻具有PersonList的一個實例在我的境界?正如你可以在下面的例子中看到的,我首先檢查是否存在,如果沒有,我創建它。

  2. 關於使用領域通知的最佳做法是什麼?將它添加到我的Realm中的一個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() 
    } 
} 

回答

3

感謝您使用境界!至於你的問題:

什麼是最好的做法,只有一個PersonList實例在我的領域?正如你可以在下面的例子中看到的,我首先檢查是否存在,如果沒有,我創建它。

有幾種方法可以處理這種情況。我建議您給PersonList一個主鍵,並且每當您使用PersonList時,您使用該主鍵的常數值。 Realm強制執行不變量,即只能存儲具有給定主鍵值的一個對象。

這樣:

  • 使用Realm.object(ofType:forPrimaryKey:)與不變的主鍵獲取現有PersonList
  • 如果該方法返回nil,請創建一個新的PersonList
  • 任何時候要保存PersonList,請使用Realm.add(_:update:),將update設置爲true。如果該對象不存在,則會添加對象,或者如果之前已添加,則更新數據庫中的現有副本。

什麼是最好的做法,當涉及到使用領域通知的。將它添加到我的Realm中的一個PersonList對象的列表屬性中是否正確?

是的,您使用通知似乎適合我。

比方說,我想有一個單獨的類來處理我的領域中的寫入事務。正如你在我的例子中看到的,所有的讀/寫事務都保存在UITableViewController類中 - 這被認爲是凌亂的嗎?

這是一個編碼風格問題,而不是一個領域問題,但它最終是一個個人喜好的問題。如果你想避免創造一個「巨大的視圖控制器」與所有你的邏輯有一對夫婦的事情,你可以嘗試:

  • 分裂您的視圖控制器類到一個主類和數量的擴展,各生活在自己的文件中。例如,您可能有一個Realm相關方法的擴展,一個用於表視圖委託/數據源方法等。請注意,存儲的屬性不能存在於擴展中,必須在主類聲明中聲明。

  • 您可以創建一個或多個幫助器類來組織您的邏輯。例如,您有幾個方法可以顯示模態彈出窗口並寫入Realm。這些人不一定要住在桌面視圖班,可以住在PersonManager班。這個類將負責創建和呈現警報控制器以及與Realm交互。如果您需要PersonManager與桌面視圖控制器進行通信(儘管Realm通知自動處理刷新桌面視圖,甚至可能沒有必要!),您可以使用基於閉包的回調或委託模式。

希望有所幫助。

+0

感謝您的詳細解釋!它讓我對事情更清楚。 – fisher