2016-06-20 11 views
5

新しいプロジェクトでMVVMパターンを使用しようとしています。初めて、私は構造体にすべてのビューモデルを作成しました。しかし、クロージャでfetchDataFromNetworkなどの非同期ビジネスロジックを実装すると、クロージャは古いビューモデルの値を取得し、それを更新しました。新しいビューモデル値ではありません。Swift:ViewModelを構造体またはクラスにする必要がありますか?

ここには遊び場のテストコードがあります。

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

コンソールは、私はクラスに、didSetは呼ばれないのViewModelを変更したがのViewControllerで表示モデルがある場合は

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

問題は、新しい値10

を持っていないのViewControllerでビューモデルである出力します新しい値10.

答えて

5

クラスを使用する必要があります。

mutating関数を使用してstructを使用する場合、関数はクロージャ内で突然変異を実行すべきではありません。あなたはないは、次の操作を行う必要があります:私はクラスにViewModelにを変更した場合は

struct ViewModel { 
    var data: Int = 0 

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

は、didSetがここ

何も間違って呼び出されていない - それは予想される動作です。あなたがstructを使用する場合は


すると、あなたは

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

を行うことができます。またdataTaskWithURLに提供閉鎖はメインスレッド上で実行されないことに注意してください。あなたはそれにdispatch_async(dispatch_get_main_queue()) {...}を呼び出すことがあります。

+0

したがって、非同期API呼び出し@codeでstructを使用する方法はありませんか?私はクラスよりも構造体を使うほうが好きですから。 – Paul

+0

@Paul私の投稿を編集しました(再び)。 – Code

+0

ええ、それは悪いデザインです。 :(この場合はクラスを使うべきです。ありがとう@コード。 – Paul

0

あなたがself.data 2のオプションを得ることができます:どちらか(structとしてviewModelを使用して)fetchResponseのためにあなたの閉鎖に戻りパラメータを使用するか、独自のセット方法/閉鎖を作成し、init方法でそれを使用することができます(viewModelを使用して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() 

コンソール版画:

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

Apple Document は次のように述べている:プロパティが最初に初期化されるときに

willSetとdidSetオブザーバーは呼び出されません。これらは、プロパティの値が初期化コンテキスト外に設定された場合にのみ呼び出されます。

+0

ええ、私はそれを知っています。しかし、私はdidSetオブザーバをfetchData関数を使った委譲やコールバックよりも好む。だから私はクラスよりも構造体を使用します。非同期変換関数でstructを使用する方法がない場合は、クラスを使用する必要があります。 – Paul

関連する問題