MVVM with realm:跨线程传递Realm结果?

时间:2017-02-18 09:23:07

标签: swift multithreading asynchronous mvvm realm

使用Xcode-8.2.1,Swift-3.0.2,RealmSwift-2.2.0,iOS-Simulator-10:

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

直到我尝试访问viewmodel-property的那一刻(在VIEW部分内部 - 见下文),一切正常。它说:Realm accessed from incorrect thread

我怎么能让MVVM模式完成分离模型,视图模型和视图的工作,但同时又能获得与领域的线程安全性?

有没有办法让Realm-results(即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)
        }
    }
}

VIEW-MODEL:

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

查看:

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

    }
}

2 个答案:

答案 0 :(得分:2)

您可以使用ThreadSafeReference将Realm的线程限制类型(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

答案 1 :(得分:1)

我找到了解决方法(见下文):也许你有更好的解决方案 - 请告诉我!

这是我的github-code realm_mvvm_c on github

在引入新协议并使(几乎所有东西)符合它之后,事情就成功了。

以下是名为DataEntry的协议:

import Foundation

protocol DataEntry: class {

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

现在,让一切都符合它,例如

- &GT;领域对象(即class BalancesDataEntry: Object, DataEntry {...

- &GT; getEntries返回值(即func getEntries(_ completionHandler: @escaping (_ entries: [DataEntry]) -> Void)

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

- &GT;所有相应的Model-和View-Model协议也需要DataEntry数据类型(参见git-repo获取完整图片)

之后,只需将MODEL方法getEntries(..)的completion-handler返回数组更改为新创建的对象实例(即DataEntryDub),该对象实例符合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