2016-06-20 34 views
5

Sto provando a utilizzare il pattern MVVM nel mio nuovo progetto. La prima volta, ho creato tutto il mio modello di visualizzazione per strutturare. Ma quando ho implementato la logica aziendale asincrona come fetchDataFromNetwork con le chiusure, le chiusure catturano il vecchio valore del modello di vista e poi lo aggiornano. Non un nuovo valore del modello di vista.Swift: ViewModel dovrebbe essere una struttura o una classe?

Ecco un codice di prova nel parco giochi.

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 stampa

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

Il problema è vista Modello in ViewController non ha nuovo valore 10.

Se ho cambiato ViewModel in classe, didSet non chiamato, ma vista Modello in ViewController ha nuovo valore 10.

risposta

5

È necessario utilizzare una classe.

Se si utilizza una struttura con funzione di muting, la funzione non deve eseguire la mutazione all'interno di una chiusura; si dovrebbe non effettuare le seguenti operazioni:

struct ViewModel { 
    var data: Int = 0 

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

Se ho cambiato ViewModel in classe, didSet non chiamato

Niente di male qui - questo è il comportamento previsto.


Se si preferisce utilizzare struct, si può fare

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

Si noti inoltre che la chiusura prevista per dataTaskWithURL non viene eseguito sul thread principale. Potresti voler chiamare dispatch_async(dispatch_get_main_queue()) {...} al suo interno.

+0

Quindi non c'è modo di utilizzare struct con chiamata API asincrona @Code? Perché preferisco usare struct che class. – Paul

+0

@Paul Ho modificato il mio post (di nuovo). – Code

+0

Sì, è un cattivo design. :(Dovrei usare la classe per questo caso. Grazie @Code. – Paul

0

si potrebbe ottenere l'self.data in due opzioni: o utilizzare un parametro di ritorno nella vostra chiusura per fetchResponse (utilizzando viewModel come struct) oppure è possibile creare il proprio set-metodo/chiusura e utilizzarlo nel metodo init (utilizzando viewModel come 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() 

stampe Console:

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

Apple Document dice in questo modo:

willSet e didSet gli osservatori non sono chiamati quando una proprietà viene prima inizializzato. Vengono chiamati solo quando il valore della proprietà è impostato al di fuori di un contesto di inizializzazione.

+0

Sì, lo so. Ma preferisco usare didSet observer piuttosto che delegation o callback con funzione fetchData. Quindi uso struct than class. Se non c'è modo di usare struct con funzione di muting asincrono, dovrei usare la classe. – Paul

Problemi correlati