ReactiveCocoa框架下的MVVM模式解读

记录一些MVVM文章中关于ReactiveCocoa的代码:

实例一:带有分页的文章列表,根据文章类别过滤出文章的列表,可以进入文章详细页面

1:YFBlogListViewModel 首先了解关于列表的ViewModel的代码内容:

#import <Foundation/Foundation.h>
#import <ReactiveCocoa.h>


@class YFCategoryArticleListModel;


/**
 *  文章列表的视图模型.
 */
@interface YFBlogListViewModel : NSObject
@property (copy, nonatomic) NSArray * blogListItemViewModels; //!< 文章.内部存储的应为文章列表单元格的视图模型.注意: 刷新操作,存储第一页数据;翻页操作,将存储所有的数据,并按页面排序.

/**
 *  使用一个分类文章列表数据模型来快速初始化.
 *
 *  @param model 文章列表模型.
 *
 *  @return 实例对象.
 */


- (instancetype)initWithCategoryArtilceListModel: (YFCategoryArticleListModel *) model;

/**
 *  获取首页的数据.常用于下拉刷新.
 *
 */
- (void)first;

/**
 *  翻页,获取下一页的数据.常用于上拉加载更多.
 */
- (void)next;


@end
#import "YFBlogListViewModel.h"
#import <ReactiveCocoa.h>
#import <AFNetworking.h>
#import <RACAFNetworking.h>
#import "YFCategoryArticleListModel.h"
#import <MJExtension.h>
#import "YFBlogListItemViewModel.h"
#import "YFArticleModel.h"

@interface YFBlogListViewModel ()
@property (strong, nonatomic) AFHTTPRequestOperationManager * httpClient;
@property (strong, nonatomic) NSNumber * nextPageNumber; //!< 下次要请求第几页的数据.
@property (copy, nonatomic) NSString * category; //!< 文章类别.
@property (copy, nonatomic) NSString * requestPath; //!< 完整接口地址.



@end

@implementation YFBlogListViewModel

- (instancetype)initWithCategoryArtilceListModel:(YFCategoryArticleListModel *)model
{
    self = [super init];
    
    if (nil != self) {
        // 设置 self.category 与 model.category 的关联.
        [RACObserve(model, category) subscribeNext:^(NSString * categoryName) {
            self.category = categoryName;
        }];
        
        // 和类型无关的RAC 初始化操作,应该剥离出来.
        [self setup];
    }
    
    return self;
}

/**
 *  和数据模型无关的初始化设置,放到独立的方法中.
 */
- (void)setup
{
    // 初始化网络请求相关的信息.
    self.httpClient = [AFHTTPRequestOperationManager manager];
    self.httpClient.requestSerializer = [AFJSONRequestSerializer serializer];
    self.httpClient.responseSerializer = [AFJSONResponseSerializer serializer];
    
    // 设置 self.nextPageNumber 与self.category的关联.
    [RACObserve(self, category) subscribeNext:^(id x) {
        // 只要分类变化,下次请求,都需要重置为请求第零页的数据.
        self.nextPageNumber = @0;
    }];
    
    // 接口完整地址,肯定是受分类和页面的影响的.但是因为分类的变化最终会通过分页的变化来体现,所以此处仅需监测分页的变化情况即可.
    [RACObserve(self, nextPageNumber)  subscribeNext:^(NSNumber * nextPageNumber) {
        NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostListViewController&model[category]=%@&model[page]=%@", self.category, nextPageNumber];
        
        self.requestPath = path;
    }];
    
    // 每次数据完整接口变化时,必然要同步更新 blogListItemViewModels 的值.
    [[RACObserve(self, requestPath) filter:^BOOL(id value) {
        return value;
    }] subscribeNext:^(NSString * path) {
        /**
         *  分两种情况: 如果是变为0,说明是重置数据;如果是大于0,说明是要加载更多数据;不处理向上翻页的情况.
         */
        
        NSMutableArray * articls = [NSMutableArray arrayWithCapacity: 42];
        
        if (YES != [self.nextPageNumber isEqualToNumber: @0]) {
            [articls addObjectsFromArray: self.blogListItemViewModels];
        }
        
        [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {
            // 使用MJExtension将JSON转换为对应的数据模型.
            NSArray * newArticles = [YFArticleModel objectArrayWithKeyValuesArray: JSONAndHeaders.first];
            
            // RAC 风格的数组操作.
            RACSequence * newblogViewModels = [newArticles.rac_sequence
                                    map:^(YFArticleModel * model) {
                                        YFBlogListItemViewModel * vm = [[YFBlogListItemViewModel alloc] initWithArticleModel: model];
                                        
                                        return vm;
                                    }];
            
            
            [articls addObjectsFromArray: newblogViewModels.array];
            
            self.blogListItemViewModels = articls;
        }];
    }];
}

- (void)first
{
    self.nextPageNumber = @0;
}

- (void)next
{
    self.nextPageNumber  = [NSNumber numberWithInteger: [self.nextPageNumber integerValue] + 1];
}

@end

2:YFCategoryArticleListModel模型的内容

#import <Foundation/Foundation.h>

/**
 *  分类文章列表.
 */
@interface YFCategoryArticleListModel : NSObject
@property (copy, nonatomic) NSString * category; //!< 分类
@property (strong, nonatomic) NSArray * articles; //!< 此分类下的文章列表.

@end

3:ViewController的代码

#import <UIKit/UIKit.h>

@class YFBlogListViewModel;

@interface YFMVVMPostListViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) UITableView * tableView;
@property (strong, nonatomic) YFBlogListViewModel * viewModel;

@end
#import "YFMVVMPostListViewController.h"
#import "YFBlogListViewModel.h"
#import <ReactiveCocoa.h>
#import "YFCategoryArticleListModel.h"
#import "YFBlogListViewModel.h"
#import "YFBlogListItemViewModel.h"
#import "YFArticleModel.h"
#import "YFBlogDetailViewModel.h"
#import <MJRefresh.h>
#import "YFMVVMPostViewController.h"

@interface YFMVVMPostListViewController ()

@end

@implementation YFMVVMPostListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    [RACObserve(self.viewModel, blogListItemViewModels) subscribeNext:^(id x) {
        [self updateView];
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (UITableView *)tableView
{
    if (nil == _tableView) {
        _tableView = [[UITableView alloc] init];
        
        [self.view addSubview: _tableView];
        
        [_tableView makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
        }];
        
        _tableView.delegate = self;
        _tableView.dataSource = self;
        
        NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
        
        [_tableView registerClass: NSClassFromString(cellReuseIdentifier) forCellReuseIdentifier:cellReuseIdentifier];
        
        _tableView.header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
            [self.viewModel first];
        }];
        
        _tableView.footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
            [self.viewModel next];
        }];
        
    }
    
    return _tableView;
}

/**
 * 更新视图.
 */
- (void) updateView
{
    [self.tableView.header endRefreshing];
    [self.tableView.footer endRefreshing];
    
    [self.tableView reloadData];
}

# pragma mark - tabelView代理方法.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger number  = self.viewModel.blogListItemViewModels.count;
    
    return number;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString * cellReuseIdentifier = NSStringFromClass([UITableViewCell class]);
    
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: cellReuseIdentifier forIndexPath:indexPath];
    
    YFBlogListItemViewModel * vm = self.viewModel.blogListItemViewModels[indexPath.row];
    
    NSString * content = vm.intro;
    
    cell.textLabel.text = content;
    
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    
    return cell;
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 跳转到博客详情.
    YFBlogListItemViewModel * itemVM = self.viewModel.blogListItemViewModels[indexPath.row];
    
    YFMVVMPostViewController * postVC = [[YFMVVMPostViewController alloc] init];
    
    YFBlogDetailViewModel * detailVM = [[YFBlogDetailViewModel alloc] init];
    detailVM.blogId = itemVM.blogId;
    
    postVC.viewModel = detailVM;
    
    [self.navigationController pushViewController: postVC animated: YES];
}

@end

4:跳转到当前页面的内容

        YFMVVMPostListViewController * mvvmPostVC = [[YFMVVMPostListViewController alloc] init];
        
        YFCategoryArticleListModel * articleListModel = [[YFCategoryArticleListModel alloc] init];
        articleListModel.category = @"ui";
        
        YFBlogListViewModel * listVM = [[YFBlogListViewModel alloc] initWithCategoryArtilceListModel: articleListModel];
        
        mvvmPostVC.viewModel = listVM;
        
        [self.navigationController pushViewController: mvvmPostVC animated: YES];

5:详细页面的ViewModel代码:

#import <Foundation/Foundation.h>
@class YFArticleModel;

/**
 *  文章详情的视图模型.
 */

@interface YFBlogDetailViewModel : NSObject
@property (copy, nonatomic) NSString * content; // 要显示的内容.
@property (copy, nonatomic) NSString * blogId; //!< 博客ID.

- (instancetype)initWithModel: (YFArticleModel *) model;

@end
#import "YFBlogDetailViewModel.h"
#import <ReactiveCocoa.h>
#import "YFArticleModel.h"
#import <RACAFNetworking.h>
#import <MJExtension.h>

@interface YFBlogDetailViewModel ()
@property (strong, nonatomic) AFHTTPRequestOperationManager * httpClient;
@property (copy, nonatomic) NSString * requestPath; //!< 完整接口地址.

@end
@implementation YFBlogDetailViewModel

- (instancetype)init
{
    self = [self initWithModel: nil];
    
    return self;
}

- (instancetype)initWithModel:(YFArticleModel *)model
{
    self = [super init];
    
    if (nil != self) {
        // 设置self.blogId与model.id的相互关系.
        [RACObserve(model, id) subscribeNext:^(id x) {
            self.blogId = x;
        }];
        
        [self setup];
    }
    
    return self;
}

/**
 *  公共的与Model无关的初始化.
 */
- (void)setup
{
    // 初始化网络请求相关的信息.
    self.httpClient = [AFHTTPRequestOperationManager manager];
    self.httpClient.requestSerializer = [AFJSONRequestSerializer serializer];
    self.httpClient.responseSerializer = [AFJSONResponseSerializer serializer];
    
    // 接口完整地址,肯定是受id影响.
    [[RACObserve(self, blogId) filter:^BOOL(id value) {
        return value;
    }] subscribeNext:^(NSString * blogId) {
        NSString * path = [NSString stringWithFormat: @"http://www.ios122.com/find_php/index.php?viewController=YFPostViewController&model[id]=%@", blogId];
        
        self.requestPath = path;
    }];
    
    // 每次完整的数据接口变化时,必然要同步更新 self.content 的值.
    [[RACObserve(self, requestPath) filter:^BOOL(id value) {
        return value;
    }] subscribeNext:^(NSString * path) {
        [[self.httpClient rac_GET:path parameters:nil] subscribeNext:^(RACTuple *JSONAndHeaders) {
            // 使用MJExtension将JSON转换为对应的数据模型.
            YFArticleModel * model = [YFArticleModel objectWithKeyValues:JSONAndHeaders.first];
           
            self.content = model.body;
        }];
    }];
}

@end

6:详细页面的ViewController

#import <UIKit/UIKit.h>

@class YFBlogDetailViewModel;

@interface YFMVVMPostViewController : UIViewController
@property (strong, nonatomic) YFBlogDetailViewModel * viewModel;

@end
#import "YFMVVMPostViewController.h"
#import "YFBlogDetailViewModel.h"
#import <ReactiveCocoa.h>

@interface YFMVVMPostViewController ()
@property (strong, nonatomic) UIWebView * webView;
@end

@implementation YFMVVMPostViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [RACObserve(self.viewModel, content) subscribeNext:^(id x) {
        [self updateView];
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
- (UIWebView *)webView
{
    if (nil == _webView) {
        _webView = [[UIWebView alloc] init];
        
        [self.view addSubview: _webView];
        
        [_webView makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
        }];
    }
    
    return _webView;
}

/**
 * 更新视图.
 */
- (void) updateView
{
    [self.webView loadHTMLString: self.viewModel.content baseURL:nil];
}

@end

 

实例二:用户列表实例

1:用户列表的ViewModel代码UsersViewModel

#import <ReactiveViewModel/ReactiveViewModel.h>

@class RACCommand;

#pragma mark -

@interface UsersViewModel : RVMViewModel

/// Array of UserViewModel objects filled by userViewModelsCommand.
@property (nonatomic, readonly) NSArray *userViewModels;

/// Input: nil
@property (nonatomic, readonly) RACCommand *userViewModelsCommand;

/// Input: nil
@property (nonatomic, readonly) RACCommand *clearImageCacheCommand;

@property (nonatomic, readonly, getter=isLoading) BOOL loading;

@end
#import "UsersViewModel.h"

#import "UserViewModel.h"

#import "UserController.h"

#import "User.h"

#import "ImageController.h"

#import <ReactiveCocoa/ReactiveCocoa.h>
#import <ReactiveCocoa/RACEXTScope.h>

#pragma mark -

@implementation UsersViewModel

- (instancetype)init {
    self = [super init];
    if (self) {
        UserController *userController = [[UserController alloc] init];
        ImageController *imageController = [ImageController sharedController];

        _userViewModelsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id _) {
            return [[[userController fetchRandomUsers:100]
                        subscribeOn:[RACScheduler scheduler]]
                        map:^NSArray *(NSArray *users) {
                            return [[[users rac_sequence]
                                        map:^UserViewModel *(User *user) {
                                            return [[UserViewModel alloc] initWithUser:user imageController:imageController];
                                        }]
                                        array];
                        }];
        }];

        RAC(self, userViewModels) =
            [[[_userViewModelsCommand executionSignals]
                switchToLatest]
                deliverOn:[RACScheduler mainThreadScheduler]];

        RAC(self, loading) =
            [_userViewModelsCommand executing];

        _clearImageCacheCommand = [[RACCommand alloc] initWithEnabled:[RACObserve(self, loading) not] signalBlock:^RACSignal *(id _) {
            return [imageController purgeLocalCaches];
        }];
    }
    return self;
}

@end

2:另外封装UserController的代码:

@class RACSignal;

#pragma mark -

@interface UserController : NSObject

/// Sends an array of fabricated User objects then completes.
- (RACSignal *)fetchRandomUsers:(NSUInteger)numberOfUsers;

@end
#import "UserController.h"

#import "User.h"

#import <ReactiveCocoa/ReactiveCocoa.h>
#import <LoremIpsum/LoremIpsum.h>

#pragma mark -

@implementation UserController

- (RACSignal *)fetchRandomUsers:(NSUInteger)numberOfUsers {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSMutableArray *usersArray = [NSMutableArray array];
        for (int i = 0; i < numberOfUsers; i++) {
            NSString *name = [LoremIpsum name];
            NSURL *avatarURL = [[LoremIpsum URLForPlaceholderImageFromService:LIPlaceholderImageServiceHhhhold withSize:CGSizeMake(96, 96)] URLByAppendingPathComponent:[NSString stringWithFormat:@"jpg?test=%i", i]];
            User *user = [[User alloc] initWithName:name avatarURL:avatarURL];
            [usersArray addObject:user];
        }
        [subscriber sendNext:[usersArray copy]];
        [subscriber sendCompleted];
        return nil;
    }];
}

@end

3:Model的代码:

#pragma mark -

@interface User : NSObject

@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) NSURL *avatarURL;

- (instancetype)initWithName:(NSString *)name avatarURL:(NSURL *)avatarURL;

@end
#import "User.h"


#pragma mark -

@implementation User

- (instancetype)initWithName:(NSString *)name avatarURL:(NSURL *)avatarURL {
    self = [super init];
    if (self != nil) {
        _name = name;
        _avatarURL = avatarURL;
    }
    return self;
}

@end

 

4:ViewController的代码

@class UsersViewModel;

@interface UsersViewController : UITableViewController

- (instancetype)initWithViewModel:(UsersViewModel *)viewModel;

@end
#import "UsersViewController.h"

#import "UsersViewModel.h"
#import "UserViewModel.h"

#import "UserCell.h"

#import <ReactiveCocoa/ReactiveCocoa.h>
#import <ReactiveCocoa/RACEXTScope.h>

#pragma mark -

@interface UsersViewController ()

@property (nonatomic, readonly) UsersViewModel *viewModel;

@end


@implementation UsersViewController

- (instancetype)initWithViewModel:(UsersViewModel *)viewModel {
    self = [super init];
    if (self != nil) {
        _viewModel = viewModel;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.rowHeight = 48;
    [self.tableView registerClass:[UserCell class] forCellReuseIdentifier:NSStringFromClass([UserCell class])];

    @weakify(self);

    self.title = NSLocalizedString(@"Random Users", nil);

    UIBarButtonItem *clearImageCacheBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Clear Cache", nil) style:UIBarButtonItemStylePlain target:nil action:nil];
    clearImageCacheBarButtonItem.rac_command = self.viewModel.clearImageCacheCommand;
    self.navigationItem.rightBarButtonItem = clearImageCacheBarButtonItem;

    self.refreshControl = [[UIRefreshControl alloc] init];
    [[[self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged]
        mapReplace:self.viewModel.userViewModelsCommand]
        subscribeNext:^(RACCommand *userViewModelsCommand) {
            [userViewModelsCommand execute:nil];
        }];

    [RACObserve(self.viewModel, loading)
        subscribeNext:^(NSNumber *loading) {
            @strongify(self);
            if ([loading boolValue]) {
                [self.refreshControl beginRefreshing];
                [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
            } else {
                [self.refreshControl endRefreshing];
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
            }
        }];

    [[RACObserve(self.viewModel, userViewModels)
        ignore:nil]
        subscribeNext:^(id _) {
            @strongify(self);
            [self.tableView reloadData];
        }];

    [self.viewModel.userViewModelsCommand execute:nil];
}

#pragma mark UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.viewModel.userViewModels count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UserViewModel *viewModel = self.viewModel.userViewModels[indexPath.row];

    UserCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([UserCell class]) forIndexPath:indexPath];
    cell.viewModel = viewModel;
    cell.viewModel.active = YES;
    return cell;
}

# pragma mark UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UserViewModel *userViewModel = self.viewModel.userViewModels[indexPath.row];
    NSLog(@"Selected: %@", userViewModel);
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    UserCell *userCell = (UserCell *)cell;
    userCell.viewModel.active = NO;
}

@end

6:UserCell代码:

@class UserViewModel;

#pragma mark -

@interface UserCell : UITableViewCell

@property (nonatomic) UserViewModel *viewModel;

@end
#import "UserCell.h"

#import "ImageView.h"

#import "UserViewModel.h"
#import "ImageViewModel.h"

#import <ReactiveCocoa/ReactiveCocoa.h>

#pragma mark -

@interface UserCell ()

@property (nonatomic, readonly) ImageView *avatarImageView;

@end

@implementation UserCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self != nil) {
        _avatarImageView = [[ImageView alloc] init];
        [self.contentView addSubview:_avatarImageView];
    }
    return self;
}

- (void)setViewModel:(UserViewModel *)viewModel {
    if (_viewModel == viewModel) return;

    _viewModel = viewModel;

    self.avatarImageView.viewModel = _viewModel.imageViewModel;
    self.textLabel.text = _viewModel.name;
}

- (void)layoutSubviews {
    [super layoutSubviews];

    self.avatarImageView.frame = CGRectMake(0, 0, 48, 48);
    self.textLabel.frame = CGRectMake(58, 0, 260, 48);
}

@end

7:调用主控制器跳转

    UsersViewModel *usersViewModel = [[UsersViewModel alloc] init];
    UsersViewController *usersViewController = [[UsersViewController alloc] initWithViewModel:usersViewModel];

 

另:reactivecocoa afnetworking地交互可以查下面这个实例,地址:https://github.com/octokit/octokit.objc

小项目框架设计(ReactiveCocoa+MVVM+AFNetworking+FMDB) 地址:http://www.tuicool.com/articles/Q3uuQvA

posted @ 2015-12-28 21:38  踏浪帅  阅读(704)  评论(0编辑  收藏  举报