2017-02-18 120 views
2

使用的Xcode-8.2.1,夫特-3.0.2,RealmSwift-2.2.0,IOS的模拟器-10:带领域的MVVM:跨线程传递领域结果?

我尝试应用使用境界MVVM模式(explained by Steve Scott here)。

一切工作,直到现在(在VIEW部分 - 见下文),我尝试访问viewmodel属性。它说:Realm accessed from incorrect thread

我怎么能仍然使MVVM模式做它的工作分离模型,视图模型和视图,但在同一时间,获得领域的线程安全?

是否有办法让Realm结果(即Results<BalancesDataEntry>)跨线程传递?

这里是我的代码: (问题发生在最底层,查看部之中)

// REALM-OBJECT: 

import Foundation 
import RealmSwift 

class BalancesDataEntry: Object { 

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

MODEL:

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

视图模型:

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中复制数据来强制执行异步,这是非常不必要的,因为查询结果已经被异步更新。您可以只进行一次查询并将结果对象存储在模型对象中。然后你可以直接从'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)

- > View-Model的条目(即fileprivate var entries: [DataEntry]? {..

- >所有相应的机型和视图模型的协议还需要的DataEntry数据类型(请查看git回购的完整画面)

之后,这是足以改变完成处理程序返回数组MODEL的方法getEntries(..)到新创建的对象实例(即。 DataEntryDub)是keept符合的DataEntry协议:

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传递领域的线程限制类型(ObjectResultsListLinkingObjects),以不同的线程。该文档的上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