记录一些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 = @;
}]; // 接口完整地址,肯定是受分类和页面的影响的.但是因为分类的变化最终会通过分页的变化来体现,所以此处仅需监测分页的变化情况即可.
[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: ]; if (YES != [self.nextPageNumber isEqualToNumber: @]) {
[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 = @;
} - (void)next
{
self.nextPageNumber = [NSNumber numberWithInteger: [self.nextPageNumber integerValue] + ];
} @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(, , , ));
}]; _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(, , , ));
}];
} 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:]
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 = ; i < numberOfUsers; i++) {
NSString *name = [LoremIpsum name];
NSURL *avatarURL = [[LoremIpsum URLForPlaceholderImageFromService:LIPlaceholderImageServiceHhhhold withSize:CGSizeMake(, )] 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 = ;
[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 ;
} - (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(, , , );
self.textLabel.frame = CGRectMake(, , , );
} @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