2016-06-20 6 views
5

Ich versuche, MVVM-Muster in meinem neuen Projekt zu verwenden. Zum ersten Mal habe ich mein gesamtes View-Modell zu struct erstellt. Aber wenn ich asynchrone Geschäftslogik wie fetchDataFromNetwork mit Closures implementierte, erfassen Closures den alten View-Modellwert und werden dann auf diesen Wert aktualisiert. Kein neuer AnsichtsmodellwertSwift: Sollte ViewModel eine Struktur oder Klasse sein?

Hier ist ein Testcode in Spielplatz.

import Foundation 
import XCPlayground 

struct ViewModel { 
    var data: Int = 0 

    mutating func fetchData(completion:()->()) { 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.com")!) { 
     result in 
     self.data = 10 
     print("viewModel.data in fetchResponse : \(self.data)") 
     completion() 
     XCPlaygroundPage.currentPage.finishExecution() 
     }.resume() 
    } 
} 

class ViewController { 
    var viewModel: ViewModel = ViewModel() { 
    didSet { 
     print("viewModel.data in didSet : \(viewModel.data)") 
    } 
    } 

    func changeViewModelStruct() { 
    print("viewModel.data before fetch : \(viewModel.data)") 

    viewModel.fetchData { 
     print("viewModel.data after fetch : \(self.viewModel.data)") 
    } 
    } 
} 

var c = ViewController() 
c.changeViewModelStruct() 

Console druckt

viewModel.data before fetch : 0 
viewModel.data in didSet : 0 
viewModel.data in fetchResponse : 10 
viewModel.data after fetch : 0 

Das Problem Ansicht Modell in Viewcontroller ist nicht über neuen Wert 10.

Wenn ich Ansichtsmodell in der Klasse geändert, didSet nicht genannt, aber Ansicht Modell in Viewcontroller hat neuer Wert 10.

Antwort

5

Sie sollten eine Klasse verwenden.

Wenn Sie eine Struktur mit einer Mutationsfunktion verwenden, sollte die Funktion die Mutation innerhalb eines Abschlusses nicht ausführen. Sie sollten nicht wie folgt vorgehen:

struct ViewModel { 
    var data: Int = 0 

    mutating func myFunc() { 
     funcWithClosure() { 
      self.data = 1 
     } 
    } 
} 

Wenn ich Ansichtsmodell in die Klasse geändert, didSet nicht

Nichts falsch hier genannt -, dass das erwartete Verhalten ist.


Wenn Sie es vorziehen struct zu verwenden, können Sie

func fetchData(completion: ViewModel ->()) { 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.com")!) { 
     result in 
     var newViewModel = self 
     newViewModel.data = 10 
     print("viewModel.data in fetchResponse : \(self.data)") 
     completion(newViewModel) 
     XCPlaygroundPage.currentPage.finishExecution() 
     }.resume() 
    } 


    viewModel.fetchData { newViewModel in 
    self.viewModal = newViewModel 
     print("viewModel.data after fetch : \(self.viewModel.data)") 
    } 

auch tun beachten Sie, dass der Verschluss an dataTaskWithURL vorgesehen nicht auf dem Haupt-Thread läuft. Vielleicht möchten Sie dispatch_async(dispatch_get_main_queue()) {...} darin anrufen.

+0

So gibt es keine Möglichkeit, Struktur mit async API-Aufruf @Code zu verwenden? Weil ich lieber struct als class benutze. – Paul

+0

@Paul Bearbeitete meine Post (wieder). – Code

+0

Ja, das ist ein schlechtes Design. :(Ich sollte in diesem Fall Klasse verwenden. Danke @Code. – Paul

0

Sie könnten die self.data in zwei Optionen erhalten: entweder einen Rückgabeparameter in Ihrem Verschluss für fetchResponse verwenden (unter Verwendung von viewModel als struct) oder Sie können Ihren eigenen Set-Methode/Abschluss erstellen und es in Ihrer init Methode verwenden (mit viewModel als class).

class ViewModel { 
var data: Int = 0 
func fetchData(completion:()->()) { 
    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://stackoverflow.com")!) { 
     result in 
     self.data = 10 
     print("viewModel.data in fetchResponse : \(self.data)") 
     completion() 
     XCPlaygroundPage.currentPage.finishExecution() 
     }.resume() 
    } 
} 

class ViewController { 
    var viewModel: ViewModel! { didSet { print("viewModel.data in didSet : \(viewModel.data)") } } 

    init(viewModel: ViewModel) { 
     // closure invokes didSet 
     ({ self.viewModel = viewModel })() 
    } 

    func changeViewModelStruct() { 
     print("viewModel.data before fetch : \(viewModel.data)") 

     viewModel.fetchData { 
      print("viewModel.data after fetch : \(self.viewModel.data)") 
     } 
    } 
} 

let viewModel = ViewModel() 
var c = ViewController(viewModel: viewModel) 
c.changeViewModelStruct() 

Console druckt:

viewModel.data in didSet : 0 
viewModel.data before fetch : 0 
viewModel.data in fetchResponse : 10 
viewModel.data after fetch : 10 

Apple Document wie diese sagt:

willSet und didSet Beobachter nicht aufgerufen, wenn ein Objekt zum ersten Mal initialisiert wird. Sie werden nur aufgerufen, wenn der Wert der Eigenschaft außerhalb eines Initialisierungskontexts festgelegt wird.

+0

Ja, das weiß ich. Aber ich bevorzuge didset Beobachter als Delegierung oder Rückruf mit fetchData Funktion. Also benutze ich struct als Klasse. Wenn es keine Möglichkeit gibt, struct mit async muting function zu verwenden, sollte ich class verwenden. – Paul