2017-02-18 23 views
2

Использование Xcode-8.2.1, 3.0.2-Свифта, RealmSwift-2.2.0, КСН-имитатор-10:MVVM с царством: Передача Realm-результатов через потоки?

Я стараюсь применения шаблона MVVM (explained by Steve Scott here), используя Сферу.

Все работает до момента (внутри VIEW-части - см. Ниже), где я пытаюсь получить доступ к свойствам viewmodel. В нем написано: Realm accessed from incorrect thread

Как я могу заставить MVVM-шаблон выполнять свою работу по разделению модели, модели представления и представления, но в то же время получить безопасность потоков с помощью области?

Есть ли способ сделать Realm-results (т. Е. Results<BalancesDataEntry>), проходящий через потоки?

Вот мой код: (проблема происходит в самом низу, внутри View-части)

// REALM-OBJECT: 

import Foundation 
import RealmSwift 

class BalancesDataEntry: Object { 

    dynamic var category: String = "" 
    dynamic var index: Int = 0 
} 

МОДЕЛЬ:

import Foundation 
import RealmSwift 

class MVVMCBalancesModel: BalancesModel 
{ 

    fileprivate var entries = [BalancesDataEntry]() 
    let realm = try! Realm() 

    init() { 
     self.createDataEntries() 
    } 

    fileprivate func createDataEntries() { 

     let myBalance = BalancesDataEntry() 
     myBalance.index = 0 
     myBalance.category = "Love" 

     try! self.realm.write { 

      self.realm.deleteAll() 
      self.realm.add(myBalance) 
     } 
    } 

    func getEntries(_ completionHandler: @escaping (_ entries: [BalancesDataEntry]) -> Void) 
    { 
     // Simulate Aysnchronous data access 
     DispatchQueue.global().async { 

      let realmThread = try! Realm() 
      let returnArray: [BalancesDataEntry] = Array(realmThread.objects(BalancesDataEntry.self)) 
      completionHandler(returnArray) 
     } 
    } 
} 

VIEW-МОДЕЛЬ:

import Foundation 
import RealmSwift 

class MVVMCBalancesViewModel: BalancesViewModel 
{ 
    weak var viewDelegate: BalancesViewModelViewDelegate? 
    weak var coordinatorDelegate: BalancesViewModelCoordinatorDelegate? 

    fileprivate var entries: [BalancesDataEntry]? { 
     didSet { 
      viewDelegate?.entriesDidChange(viewModel: self) 
     } 
    } 

    var model: BalancesModel? { 
     didSet { 
      entries = nil; 
      model?.getEntries({ (myEntries) in 
       self.entries = myEntries 
      }) 
     } 
    } 

    var title: String { 
     return "My Balances" 
    } 

    var numberOfEntries: Int { 
     if let entries = entries { 
      return entries.count 
     } 
     return 0 
    } 

    func entryAtIndex(_ index: Int) -> BalancesDataEntry? 
    { 
     if let entries = entries , entries.count > index { 

      return entries[index] 
     } 
     return nil 
    } 

    func useEntryAtIndex(_ index: Int) 
    { 
     if let entries = entries, let coordinatorDelegate = coordinatorDelegate , index < entries.count { 
      coordinatorDelegate.balancesViewModelDidSelectData(self, data: entries[index]) 
     } 
    } 
} 

VIEW:

import UIKit 

class MVVMCBalancesViewController: UIViewController { 

    @IBOutlet weak var label1Outlet: UILabel! 
    @IBOutlet weak var label2Outlet: UILabel! 

    var viewModel: BalancesViewModel? { 
     willSet { 
      viewModel?.viewDelegate = nil 
     } 
     didSet { 
      viewModel?.viewDelegate = self 
      refreshDisplay() 
     } 
    } 

    var isLoaded: Bool = false 

    func refreshDisplay() 
    { 
     if let viewModel = viewModel , isLoaded { 

      // !!!!!!! HERE IS THE ISSUE: Realm accessed from incorrect thread !!!! 
      self.label1Outlet.text = viewModel.entryAtIndex(0)?.category 
      self.label2Outlet.text = viewModel.entryAtIndex(1)?.category 
     } else { 

     } 
    } 

    override func viewDidLoad() 
    { 
     super.viewDidLoad() 

     isLoaded = true 
     refreshDisplay(); 
    } 

} 

extension MVVMCBalancesViewController: BalancesViewModelViewDelegate 
{ 
    func entriesDidChange(viewModel: BalancesViewModel) 
    { 

    } 
} 
+0

Кажется, проблема заключается в том, что вы каждый раз копируете данные из Realm, чтобы заставить асинхронность, что совершенно не нужно, поскольку результаты запроса уже обновляются асинхронно. Вы можете просто выполнить запрос один раз и сохранить объект Results в объекте модели. Затем вы можете вернуть это прямо из метода 'getEntries()', и он всегда будет актуальным. – ast

ответ

1

Я нашел обходное решение (см. Ниже): Возможно, у вас есть лучшие решения - пожалуйста, дайте мне знать!

Вот мой github-code realm_mvvm_c on github

После введения нового протокола и решений (почти все) соответствовать ему, все получилось.

Вот протокол называется DataEntry:

import Foundation 

protocol DataEntry: class { 

    var idx: Int { get set } 
    var category: String { get set } 
} 

Теперь, сделайте все, чтобы соответствовать ей, например,

-> царством объект (т.е. class BalancesDataEntry: Object, DataEntry {...)

-> Возвращаемое значение getEntries (т.е. func getEntries(_ completionHandler: @escaping (_ entries: [DataEntry]) -> Void))

-> Записи в образце (т.е. fileprivate var entries: [DataEntry]? {..)

-> все соответствующие model- и View-модели протоколов также должны тип данных ВаЬаЕпЬгу (см ГИТ-репо для полной картины)

После этого было достаточно, чтобы изменить обработчик завершения возврата массива метода getEntries(..) модели MODEL для вновь созданного объекта-экземпляра (т.е. DataEntryDub), который keept соответствовать протоколу ВаЬаЕпЬгу:

func getEntries(_ completionHandler: @escaping (_ entries: [DataEntry]) -> Void) 
{ 
    // Simulate Aysnchronous data access 
    DispatchQueue.global().async { 

     let realmThread = try! Realm() 

     class DataEntryDub: DataEntry { 

      var idx: Int 
      var category: String 

      init(idx: Int, category: String) { 
       self.idx = idx 
       self.category = category 
      } 
     } 

     var returnArray = [DataEntry]() 
     for entry in realmThread.objects(BalancesDataEntry.self) { 
      returnArray.append(DataEntryDub(idx: entry.idx, category: entry.category)) 
     } 
     completionHandler(returnArray) 
    } 
} 

Вот мой github-code realm_mvvm_c on github

2

Вы можете использовать ThreadSafeReference передать нить локализованного типа Realm (в Object, Results, List, LinkingObjects) в другом потоке ,раздел В документации по Passing Instances Across Threads содержит этот пример передачи одного экземпляра подкласса Object между потоками:

let person = Person(name: "Jane") 
try! realm.write { 
    realm.add(person) 
} 
let personRef = ThreadSafeReference(to: person) 
DispatchQueue(label: "background").async { 
    let realm = try! Realm() 
    guard let person = realm.resolve(personRef) else { 
    return // person was deleted 
    } 
    try! realm.write { 
    person.name = "Jane Doe" 
    } 
} 

Он может быть использован аналогично для Results.

+0

Интересная концепция! Я посмотрю на это поближе. Большое спасибо. – iKK