12752


現在MVVMパターンでアプリを開発しているのですが、色々と調べたりコードを書いたりしても分からないところがあるので質問させていただきたいです。  
サンプルで以下に自分なりにMVVMパターンを使用したごく簡単なコードを書いてみました。

ViewController.m

[code]

    @interface ViewController ()
    
    @end
    
    @implementation ViewController {
        ViewModel *viewModel;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        viewModel = [[ViewModel alloc] initWithSelf:self];
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return [viewModel vmGetItemsCount];
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        return [viewModel vmSetTableViewCell:tableView :indexPath];
    }
    
    @end
    
[/code]

ViewModel.m

[code]

    @implementation ViewModel {
        ViewController *selfObject;
        NSArray *items;
    }
    
    - (id)initWithSelf:(ViewController *)vcSelfObject {
        if (self = [super init]) {
            selfObject = vcSelfObject;
            items = @[@"Model", @"View", @"ViewModel"];
        }
    
        return self;
    }
    
    - (NSInteger)vmGetItemsCount {
        return [items count];
    }
    
    - (UITableViewCell*)vmSetTableViewCell:(UITableView*)tableView :(NSIndexPath*)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
        cell.textLabel.text = items[indexPath.row];
    
        return cell;
    }
    
    @end
    
[/code]

まず`ViewDidLoad`で`ViewModel`のインスタンスを生成しているのですが、`viewModel`内で`ViewController`のUIを操作しないといけないので、`viewModel
= [[ViewModel alloc]
initWithSelf:self];`というように`ViewController`の`self`を引数で渡して`ViewModel`でその`self`を保持しておくというやり方は正しいやり方でしょうか？色々なサイトを見ていると、`viewModel
= [[XXviewModel alloc]
init];`としか書かれていないのですが、`ViewModel`内でUIの操作をしたいときにいちいち関数の引数に`self`を書くのは冗長なのではないかなと思ったからです。  
そして`initWithSelf`が実行されると各変数を初期化していくのですが、このイニシャライザ内で`NSArray`を初期化するのは正しいやり方でしょうか？ここで`NSArray`を初期化するのではなく、`ViewController`の`viewDidLoad`で`[viewModel
vmSetDataToItems];`のような形で初期化させた方がいいでしょうか？はたまたここは特にそういうことは意識することなくどっちでもいいでしょうか？  
長々とすみません、最後になるのですが、`MVVM`パターンを使うと`ViewController`内の関数は全体的に`numberOfRowsInSection`や`cellForRowAtIndexPath`のように[viewModel
XXXXXX]という形で一行だけになっていくのでしょうか？

分からないことが多々あり、勘違いしたまま質問をしているかもしれませんが、どなたか詳しい方がいれば教えていただきたいです。すみませんが、よろしくお願いします。


CC BY-SA 3.0
2015-07-27T08:11:08.383
12742
2015-07-27T23:34:49.610
2015-07-27T13:53:53.623
3639
5210
question
objective-c デザインパターン mvvm
アプリ開発にMVVMパターンを適用する


> viewModel内でViewControllerのUIを操作しないといけないので、viewModel = [[ViewModel alloc]
> initWithSelf:self];という（中略）やり方は正しいやり方でしょうか？

根本的な間違いだ。

  * ViewModelからViewは操作しない
  * ViewModelはViewへの参照を持たない 
    * 加えていうと、お前のコードでは循環参照になっている

> そしてinitWithSelfが実行されると各変数を初期化していくのですが、このイニシャライザ内でNSArrayを初期化するのは正しいやり方でしょうか？

このNSArrayの`items`が表示したいデータのソースならここも根本的に間違いだ。

  * itemsはModelに分類されるデータソースだ
  * ViewModelは絶対にModelを保持しない。ViewとViewModelの関係以前にViewとModelを分離するからだ

だからその段落のこれ以降の質問が意味を成していない。モデルの初期化はViewにもViewModelにもさせず別の場所でやれ。

>
> MVVMパターンを使うとViewController内の関数は全体的にnumberOfRowsInSectionやcellForRowAtIndexPathのように[viewModel
> XXXXXX]という形で一行だけになっていくのでしょうか？

質問の内容が不明瞭だが、「MVVMパターンを使うと ViewはViewModelのメソッドを呼び出すのが主な仕事になるのか」と解釈して答える。

基本的にはプレゼンテーションの範囲に限りYESだが、ViewはViewModelに問い合わせた結果をアプリケーションの外観に適用する。つまり`[viewModel
xxxx]`ではなく`view.element.property = [viewModel xxxx];`だ。

* * *

以下に、単純なMVVMパターンの例を示す。今回の質問の要旨外なのでモデルは省いている。

ViewController.h

ViewControllerは自身のためのViewModelを保持する。ViewModelは状態変更をデリゲートで通知するため、そのプロトコルを採用する。

[code]

    #import <UIKit/UIKit.h>
    #import "ViewModel.h"
    
    @interface ViewController : UIViewController <ViewModelChangingReceiving>
    
    @end
    
[/code]

ViewController.m

[code]

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic) ViewModel *viewModel;
    
    @end
    
    @implementation ViewController
    
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
      if ([super initWithNibName: nibNameOrNil bundle: nibBundleOrNil]) {
        [self _init];
      }
    
      return self;
    }
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
      if ([super initWithCoder: aDecoder]) {
        [self _init];
      }
    
      return self;
    }
    
    - (void)_init {
      _viewModel = [[ViewModel alloc] init];
      _viewModel.delegate = self;
    }
    
    - (void)viewDidLoad {
      [super viewDidLoad];
    
      self.view.backgroundColor = [self.viewModel tintColor];
    }
    
    - (IBAction)didChangeSwitch:(UISwitch *)aSwitch {
      self.viewModel.isEditing = aSwitch.on;
    }
    
    - (void)viewModelDidChange:(ViewModel *)viewModel {
      self.view.backgroundColor = [viewModel tintColor];
    }
    
    @end
    
[/code]

ViewModel.h

ViewModelは状態変更の通知先としてデリゲートを参照する。これは本来ViewModelはViewを参照しないパターンに反するが、Objective-
Cの構造上よしとする。NSNotificationCenterを使用すればデリゲートで参照することなくパターンの基本に沿えることは言っておく。

[code]

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
    
    @class ViewModel;
    
    @protocol ViewModelChangingReceiving <NSObject>
    
    @optional
    - (void)viewModelDidChange:(ViewModel *)viewModel;
    
    @end
    
    @interface ViewModel : NSObject
    
    @property (weak, nonatomic) id<ViewModelChangingReceiving> delegate;
    @property (assign, nonatomic) BOOL isEditing;
    
    @property (strong, nonatomic) UIColor *defaultColor;
    @property (strong, nonatomic) UIColor *editingColor;
    - (UIColor *)tintColor;
    
    @end
    
[/code]

ViewModel.m

[code]

    #import "ViewModel.h"
    
    @implementation ViewModel
    
    - (id)init {
      if (self = [super init]) {
        _defaultColor = [UIColor blueColor];
        _editingColor = [UIColor redColor];
      }
    
      return self;
    }
    
    - (void)setIsEditing:(BOOL)isEditing {
      _isEditing = isEditing;
      if ([self.delegate respondsToSelector: @selector(viewModelDidChange:)]) {
        [self.delegate viewModelDidChange: self];
      }
    }
    
    - (UIColor *)tintColor {
      return self.isEditing? self.editingColor : self.defaultColor;
    }
    
    @end
    
[/code]

ViewはisEditing（編集中か）という状態をViewModelに問うことができる。また、状態に応じたtintColorを得られる。isEditingが変更されると、デリゲートを通じて担当しているViewへと通知される。Viewは状態変更を自身のもつUI要素へ反映する。

このように、状態の管理とそれに応じた外観要素を適切に提供することと、それをViewから分離するのがViewModelの仕事の一つだ。モデルが持つデータソースをプレゼンテーションに適した形に整形する仕事もあるが、ここでは割愛する。

* * *

最後に、大前提だがデザインパターンはあくまでも雛形だ。実際の要件次第で実装は変化する。デザインパターンを導入する目的そのものを忘れんな。


CC BY-SA 3.0
2015-07-27T23:34:49.610
12752
2015-07-27T23:34:49.610
12742
answer