很早以前就想写写本身在设计模式方面的心得,可是一直感受本身是井底之蛙,毕竟在iOS领域越深刻越感到本身的无知,心中有着敬畏之心,就更没有自信去写这个东西(你也能够理解是没时间(>﹏<),请原谅个人装逼,嘿嘿).程序员
对于设计模式这个让人又爱又恨的玩意,说来其实简单,但一千我的眼中就有一千种哈姆雷特,说他变幻无穷确实是事实,并且当你深刻其中的时候你真的会上瘾,并乐此不疲!数据库
前几天本身写的一篇《iOS Xcode全面剖析》阅读量在短短一天内破千,还上了简书首页(你看这句话字体就知道不是广告了( ⊙o⊙ )),确实很开心,昨天又跟我一朋友用代码讲解了我对MVVM的理解及运用,此情此景下,脑壳一热搞出一篇来分享给你们也情有可原,固然更但愿有更多的大神来指点一下,让我本身也让你们有提高就够了,万分感谢!编程
到这里我就默认你看过MVVM相关文章(毕竟相关文章已经能够用满天飞来形容了~(≧▽≦)/~啦啦啦!),仅仅简要谈谈我对其的理解。设计模式
MVC是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式,市面上大部分App都是这样构建的,具体组建模式不细说,iOS入门者都比较了解(虽然不必定能彻底去遵照),但其几个不能避免的问题倒是很严重困扰开发者好比厚重的ViewController、遗失的网络逻辑(没有属于它的位置)、较差的可测试性等所以也就会有维护性较强、耦合性很低的一种新架构MVVM (MVC 引伸出得新的架构)的流行。网络
MVVM虽然来自微软,可是不该该反对它,它正式规范了正式规范了视图和控制器紧耦合的性质,以下图:数据结构
ViewModel: 相比较于MVC新引入的视图模型。是视图显示逻辑、验证逻辑、网络请求等代码存放的地方,惟一要注意的是,任何视图自己的引用都不该该放在VM中,换句话说就是VM中不要引入UIKit.h (对于image这个,也有人将其看作数据来处理,这就看我的想法了,并不影响总体的架构)。架构
这样,首先解决了VC臃肿的问题,将逻辑代码、网络请求等都写入了VM中,而后又因为VM中包含了全部的展现逻辑并且不会引用V,因此它是能够经过编程充分测试的。app
so,就是这个样子的,6666!框架
特别浅。。。本文重点是框架及实战及MVVM思想,RAC这玩意话说学习曲线较长,难以理解,很差上手,是由于以前学习的时候使用者、中文教程还比较少,因此学习运用起来比较费劲,(当时确实废了好大得劲,实力装逼一把 @%&$%& )但如今已经成熟的烂大街了,只要有心,好的教程一大把,能潜下心来看我写的水文的人,拿下RAC不在话下!异步
ReactiveCocoa 能够说是结合了函数式编程和响应式编程的框架,也可称其为函数响应式编程(FRP)框架,强调一点,RAC虽然最大的优势是提供了一个单一的、统一的方法去处理异步的行为,包括delegate方法,blocks回调,target-action机制,notifications和KVO.可是不要简单的只是单纯的认为他仅仅就是减小代码复杂度,更好的配合MVVM而已,小伙子,这样你就小看它了。
它最大的不同凡响是提供了一种新的写代码的思惟,因为RAC将Cocoa中KVO、UIKit event、delegate、selector等都增长了RAC支持,因此都不用去作不少跨函数的事。
若是全工程都使用RAC来实现,对于同一个业务逻辑终于能够在同一块代码里完成了,将UI事件,逻辑处理,文件或数据库操做,异步网络请求,UI结果显示,这一大套通通用函数式编程的思路嵌套起来,进入页面时搭建好这全部的关系,用户点击后妥妥的等着这一套联系一个个的定期望的逻辑和次序触发,最后显示给用户。
额,就说这么多,再说就没头了~(≧▽≦)/~啦啦啦!
在这次介绍中,会使用MVVM+RAC结合的方式,搞定一个添加上拉加载及下拉刷新的列表,因此更多的诠释MVVM思想,而不是RAC的逻辑链式操做(这一点用登陆界面来写更能体现Y^o^Y ),RAC在此扮演的更大一部分的角色是更好的解耦,减小代码复杂度,使代码井井有条、逻辑清晰更便于维护升级。
首先介绍一下本框架的目录结构,以下图
一、Frameworks
存放系统库的虚拟文件夹, 目前搭建框架的时候须要手动添加一个名称为Frameworks的虚拟文件夹,这样你在Build Phases 中添加的系统库会自动纳入此文件夹,不会直接在外部显示以致于打乱目录结构。系统库添加流程以下:
另外,细心地家伙会发现此目录中有两个相同的Frameworks, 那这究竟是什么鬼?最上面的那个Frameworks是在本身搭框架本身添加的,当时的项目还很单纯, 没有这么淘气,问题出在下面那个Pods Target上,添加它以后就会自动给你生成一个虚拟的Frameworks的文件夹,那又该问了为啥不直接用下面那个呢???(废话真多!反正也没冲突,就留着吧╮(╯﹏╰)╭)
既然提到了Pods,那接下来说讲CocoaPods(第三方类库管理工具)。
二、CocoaPods
当你开发iOS应用时,会常用到不少第三方开源类库,好比JSONKit,AFNetWorking等等。可能某个类库又用到其余类库,因此要使用它,必须得另外下载其余类库,而其余类库又用到其余类库,“子子孙孙无穷尽也”,反正在早期我是体会过这种痛苦,好心酸,手动一个个去下载所需类库是十分麻烦的。
还有另一种常见状况是,你项目中用到的类库有更新,你必须得从新下载新版本,从新加入到项目中,十分麻烦。
CocoaPods就是帮你解决上面的问题的,话说这玩意应该是iOS最经常使用最有名的类库管理工具了,做为iOS程序员的咱们,掌握CocoaPods的使用是必不可少的基本技能了,至于这玩意该咋用?
O(∩_∩)O哈哈~你以为我会告诉你么?好吧,我这人仍是很心软的,下面一张图告诉你该咋用...(๑乛◡乛๑ 磨人的小妖精)
☝(•̀˓◞•́)哎呦,不错哦~是否是get了一个新技能 ?6666!
三、AppDelegate
这个目录下放的是AppDelegate.h(.m)文件,是整个应用的入口文件,因此单独拿出来。一下子告诉你如何写一个简洁的AppDelegate,会在这个文件夹里添加一些类,因此将其放入一个文件夹内仍是颇有必要的。
四、Class
工程主体类, 平常大部分开发代码均在这里,又细分了好屡次级目录。
通用类
工具类
宏定义类
APP具体模块代码类
每一个成员的文件夹下是其所负责模块的文件夹,好比苍老师负责PHP界面模块(我也认为PHP是最好的语言!你们能够在评论区谈论一下!๑乛◡乛๑ 磨人的小妖精),以下(接着上面的我的文件夹):
这就是标准的MVVM了。。。为啥不和上面目录连起来呢?为啥呢?为啥呢?由于臣妾作不到啊!!!(不会三级、四级列表的MarkDown写法,求大神支招!良辰必有重谢!)
第三方类库
到这哥们又该疑惑了,内心该碎碎念了:(๑⁼̴̀д⁼̴́๑)ドヤッ‼ What are you 弄啥嘞!刚才刚讲了个第三方库管理CocoaPods,你丫这里本身又搞了一个,ԅ( ¯་། ¯ԅ) 信不信我突突了你!
哈哈哈,刚才的CocoaPods确实管理着大部分的第三方库,这里创建第三方库目录的缘由有两个:其一,并非全部的你须要的第三方都支持pods的,因此仍是须要手动添加一些类库。其二,一些第三方库虽然支持pods,可是须要咱们去更改甚至自定义这个第三方,此时也须要放入这里,也防止使用pods一不当心更新掉你的自定义!ᕕ(ᐛ)ᕗ 你来打我啊!
五、Resource
这里放置的是工程所需的一些资源,以下
ok,目录就讲到这里!想知道更详细的能够私信我!
这里着重讲解一下VC、V、VM的基类,其余的模式与View相似因此略过,其中TableViewCell的基类稍微特殊因此也提一下。
我目前的基类以下图:
是否是眼花缭乱了..., 我曾经也看它不顺眼, 曾经尝试过把基类都干掉,而后遇到了一些麻烦...就妥协了,在文章的最后能够跟你们聊聊我是怎么去干掉基类,而后又失败的,这里先详细讲一下基类。
一、YDViewController
函数的具体用意图已经标的很清楚了,这里简单讲一下四个函数的做用
yd_addSubviews : 添加View到ViewController
yd_bindViewModel : 用来绑定V(VC)与VM
yd_layoutNavigation : 设置导航栏、分栏
yd_getNewData : 初次获取数据的时候调用(不是特别必要)
二、YDView
三、YDViewModel
四、YDTableViewCell
因为Cell比较特殊,因此单拎出来讲一下。观察上面的ViewMdoel、View等的基类会发现每一个基类都会有数据绑定的地方,可是cell得数据绑定须要放在数据初始化的时候,由于全部的基类的数据逻辑绑定都是在没有返回初始化对象的时候调用的,可是cell中假如在那里面进行数据绑定会出现问题好比下图:
上图中的函数假如是在 bindViewModel
内,则会复用失败,点击按钮是没有反应的,可是假如是在数据初始化的时候调用:好比 setViewModel
的时候,就会OK了,由于里面用到了cell的在RAC中复用机制 rac_prepareForReuseSignal
,在cell尚未初始化返回的时候是失效的。
基类的做用是统一管理,统一风格,便于编码,有更多的额外的附加功能的话,建议使用Protocol 或 Category,这样移植性强,便于管理与扩展,不至于牵一发而动全身。
本篇基类核心是用VM来配置V(VC),并提供一些必须的Protocol方法来处理界面显示、逻辑,将代码风格规范化,各个部分的功能明朗化,这样,当你须要写什么,须要找什么,须要更改什么的时候都会很明确这些代码的位置,逻辑更清晰,而不会浪费更多的时间在思考应该写在哪,该去哪找,要改的地方在哪这种不应费时间的问题上。
这里讲一下以下界面的代码构造方式,很普通的一个列表:(懒得再写了,这是我以前作的一个项目的一个界面,以前基类讲解中会看到都是YD开头的,在这里是YC开头就这个区别而已)
首先观察这个界面,需求是:头部的内容数量多的话是能够左右滑动的,而后总体是能够上拉加载的。我是这样处理的:首先界面总体是一个TableView,而后分为一个Header、一个Section和主体列表Row。在Header上嵌套一个CollectionView保证可复用。具体分层以下
而后处理完后的目录以下:
简单介绍一下:
一个小小的界面这么多类...是否是难以接受了,淡定些,骚年!你要想一想把这些个东西都放在VC内是个什么赶脚?也得好几千行呢!(有点夸张!不过也够头疼的),这么多类,这里着重讲一下主VC、主V、主VM、主M就ok,能详细讲明白MVVM之间是如何工做的就一通百通了。
先上代码:
//
// LSCircleListViewController.m
// ZhongShui
//
// Created by 王隆帅 on 16/3/10.
// Copyright © 2016年 王隆帅. All rights reserved.
//
#import "LSCircleListViewController.h"
#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleMainPageViewController.h"
#import "LSCircleMainPageViewModel.h"
#import "LSCircleListCollectionCellViewModel.h"
#import "LSNewCircleListViewController.h"
@interface LSCircleListViewController ()
@property (nonatomic, strong) LSCircleListView *mainView;
@property (nonatomic, strong) LSCircleListViewModel *viewModel;
@end
@implementation LSCircleListViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#pragma mark - system
- (void)updateViewConstraints {
WS(weakSelf)
[self.mainView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(weakSelf.view);
}];
[super updateViewConstraints];
}
#pragma mark - private
- (void)yc_addSubviews {
[self.view addSubview:self.mainView];
}
- (void)yc_bindViewModel {
@weakify(self);
[[self.viewModel.cellClickSubject takeUntil:self.rac_willDeallocSignal] subscribeNext:^(LSCircleListCollectionCellViewModel *viewModel) {
@strongify(self);
LSCircleMainPageViewModel *mainViewModel = [[LSCircleMainPageViewModel alloc] init];
mainViewModel.headerViewModel.circleId = viewModel.idStr;
mainViewModel.headerViewModel.headerImageStr = viewModel.headerImageStr;
mainViewModel.headerViewModel.title = viewModel.name;
mainViewModel.headerViewModel.numStr = viewModel.peopleNum;
LSCircleMainPageViewController *circleMainVC = [[LSCircleMainPageViewController alloc] initWithViewModel:mainViewModel];
[self.rdv_tabBarController setTabBarHidden:YES animated:YES];
[self.navigationController pushViewController:circleMainVC animated:YES];
}];
[self.viewModel.listHeaderViewModel.addNewSubject subscribeNext:^(id x) {
@strongify(self);
LSNewCircleListViewController *newCircleListVC = [[LSNewCircleListViewController alloc] init];
[self.rdv_tabBarController setTabBarHidden:YES animated:YES];
[self.navigationController pushViewController:newCircleListVC animated:YES];
}];
}
- (void)yc_layoutNavigation {
self.title = @"圈子列表";
[self.rdv_tabBarController setTabBarHidden:NO animated:YES];
}
#pragma mark - layzLoad
- (LSCircleListView *)mainView {
if (!_mainView) {
_mainView = [[LSCircleListView alloc] initWithViewModel:self.viewModel];
}
return _mainView;
}
- (LSCircleListViewModel *)viewModel {
if (!_viewModel) {
_viewModel = [[LSCircleListViewModel alloc] init];
}
return _viewModel;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */
@end复制代码
对于VC,分为三个模块,下面分别来讲一下:
i 第一个模块:系统函数
此函数是从iOS6.0开始在ViewController中新增一个更新约束布局的方法,这个方法默认的实现是调用对应View的 updateConstraints
。ViewController的View在更新视图布局时,会先调用ViewController的updateViewConstraints 方法。咱们能够经过重写这个方法去更新当前View的内部布局,而不用再继承这个View去重写-updateConstraints方法。咱们在重写这个方法时,务必要调用 super 或者 调用当前View的 -updateConstraints 方法。
ⅱ 第二个模块 : 私有函数
前面基类内也提到了这三个函数的具体做用,即
yd_addSubviews : 添加View到ViewController
yd_bindViewModel : 这里绑定了两个跳转事件。
yd_layoutNavigation : 设置了标题为“圈子列表”、及TabBar不隐藏
ⅲ 第三个模块 : 懒加载
这就不用解释了,用到时再加载。
先上代码
//
// LSCircleListView.m
// ZhongShui
//
// Created by 王隆帅 on 16/3/10.
// Copyright © 2016年 王隆帅. All rights reserved.
//
#import "LSCircleListView.h"
#import "LSCircleListViewModel.h"
#import "LSCircleListHeaderView.h"
#import "LSCircleListSectionHeaderView.h"
#import "LSCircleListTableCell.h"
@interface LSCircleListView () <UITableViewDataSource, UITableViewDelegate>
@property (strong, nonatomic) LSCircleListViewModel *viewModel;
@property (strong, nonatomic) UITableView *mainTableView;
@property (strong, nonatomic) LSCircleListHeaderView *listHeaderView;
@property (strong, nonatomic) LSCircleListSectionHeaderView *sectionHeaderView;
@end
@implementation LSCircleListView
/* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */
#pragma mark - system
- (instancetype)initWithViewModel:(id<YCViewModelProtocol>)viewModel {
self.viewModel = (LSCircleListViewModel *)viewModel;
return [super initWithViewModel:viewModel];
}
- (void)updateConstraints {
WS(weakSelf)
[self.mainTableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(weakSelf);
}];
[super updateConstraints];
}
#pragma mark - private
- (void)yc_setupViews {
[self addSubview:self.mainTableView];
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
}
- (void)yc_bindViewModel {
[self.viewModel.refreshDataCommand execute:nil];
@weakify(self);
[self.viewModel.refreshUI subscribeNext:^(id x) {
@strongify(self);
[self.mainTableView reloadData];
}];
[self.viewModel.refreshEndSubject subscribeNext:^(id x) {
@strongify(self);
[self.mainTableView reloadData];
switch ([x integerValue]) {
case LSHeaderRefresh_HasMoreData: {
[self.mainTableView.mj_header endRefreshing];
if (self.mainTableView.mj_footer == nil) {
self.mainTableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
@strongify(self);
[self.viewModel.nextPageCommand execute:nil];
}];
}
}
break;
case LSHeaderRefresh_HasNoMoreData: {
[self.mainTableView.mj_header endRefreshing];
self.mainTableView.mj_footer = nil;
}
break;
case LSFooterRefresh_HasMoreData: {
[self.mainTableView.mj_header endRefreshing];
[self.mainTableView.mj_footer resetNoMoreData];
[self.mainTableView.mj_footer endRefreshing];
}
break;
case LSFooterRefresh_HasNoMoreData: {
[self.mainTableView.mj_header endRefreshing];
[self.mainTableView.mj_footer endRefreshingWithNoMoreData];
}
break;
case LSRefreshError: {
[self.mainTableView.mj_footer endRefreshing];
[self.mainTableView.mj_header endRefreshing];
}
break;
default:
break;
}
}];
}
#pragma mark - lazyLoad
- (LSCircleListViewModel *)viewModel {
if (!_viewModel) {
_viewModel = [[LSCircleListViewModel alloc] init];
}
return _viewModel;
}
- (UITableView *)mainTableView {
if (!_mainTableView) {
_mainTableView = [[UITableView alloc] init];
_mainTableView.delegate = self;
_mainTableView.dataSource = self;
_mainTableView.backgroundColor = GX_BGCOLOR;
_mainTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_mainTableView.tableHeaderView = self.listHeaderView;
[_mainTableView registerClass:[LSCircleListTableCell class] forCellReuseIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])]];
WS(weakSelf)
_mainTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakSelf.viewModel.refreshDataCommand execute:nil];
}];
_mainTableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
[weakSelf.viewModel.nextPageCommand execute:nil];
}];
}
return _mainTableView;
}
- (LSCircleListHeaderView *)listHeaderView {
if (!_listHeaderView) {
_listHeaderView = [[LSCircleListHeaderView alloc] initWithViewModel:self.viewModel.listHeaderViewModel];
_listHeaderView.frame = CGRectMake(0, 0, SCREEN_WIDTH, 160);
}
return _listHeaderView;
}
- (LSCircleListSectionHeaderView *)sectionHeaderView {
if (!_sectionHeaderView) {
_sectionHeaderView = [[LSCircleListSectionHeaderView alloc] initWithViewModel:self.viewModel.sectionHeaderViewModel];
}
return _sectionHeaderView;
}
#pragma mark - delegate
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.viewModel.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
LSCircleListTableCell *cell = [tableView dequeueReusableCellWithIdentifier:[NSString stringWithUTF8String:object_getClassName([LSCircleListTableCell class])] forIndexPath:indexPath];
if (self.viewModel.dataArray.count > indexPath.row) {
cell.viewModel = self.viewModel.dataArray[indexPath.row];
}
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.viewModel.dataArray.count > indexPath.row) {
[self.viewModel.cellClickSubject sendNext:self.viewModel.dataArray[indexPath.row]];
}
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return self.sectionHeaderView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 45;
}
@end复制代码
主View分为四个模块:
ⅰ 第一个模块 : 系统函数
每一个View都会有对应的ViewModel,这样也更易复用,这里由于是主View,通常而言我都会使得VC和主V共用一个VM,这样对于跳转、数据共享等都有着极大的好处。
ⅱ 第二个模块 : 私有函数
具体做用途中已经标注,须要注意的是这些对于不一样数据的处理,是我本身写的,逻辑上确定没有那么缜密,仅供参考。
ⅲ 第三个模块 : 懒加载
这里没啥好说的,就是用的MJRefresh这个第三方库作的刷新。不过,假如你细心的话确定会发现下面那两个View都是用VM来配置初始化的,这个和主View的配置初始化的意义是同样的。
ⅳ 第四个模块 : 代理及数据源
其中使用的是自定义Cell,用ViewModel来配置,点击事件也是和以前的VC的跳转联系起来了,并将VM传过去。
一样,先上代码
//
// LSCircleListModel.h
// ZhongShui
//
// Created by 王隆帅 on 16/3/17.
// Copyright © 2016年 王隆帅. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface LSCircleListModel : NSObject
@property (nonatomic, copy) NSString *idStr;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *intro;
@property (nonatomic, copy) NSString *img;
@property (nonatomic, copy) NSString *memberCount;
@property (nonatomic, copy) NSString *topicCount;
@end复制代码
//
// LSCircleListModel.m
// ZhongShui
//
// Created by 王隆帅 on 16/3/17.
// Copyright © 2016年 王隆帅. All rights reserved.
//
#import "LSCircleListModel.h"
@implementation LSCircleListModel
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
return @{
@"idStr":@"id",
@"title":@"title",
@"intro":@"intro",
@"img":@"img",
@"memberCount":@"MemberCount",
@"topicCount":@"TopicCount",
};
}
@end复制代码
这个就不贴图介绍了,就是单纯的数据模型,使用了MJExtention这个数据模型转换框架。没有作任何其余的逻辑处理。
//
// LSCircleListViewModel.h
// ZhongShui
//
// Created by 王隆帅 on 16/3/10.
// Copyright © 2016年 王隆帅. All rights reserved.
//
#import "YCViewModel.h"
#import "LSCircleListHeaderViewModel.h"
#import "LSCircleListSectionHeaderViewModel.h"
@interface LSCircleListViewModel : YCViewModel
@property (nonatomic, strong) RACSubject *refreshEndSubject;
@property (nonatomic, strong) RACSubject *refreshUI;
@property (nonatomic, strong) RACCommand *refreshDataCommand;
@property (nonatomic, strong) RACCommand *nextPageCommand;
@property (nonatomic, strong) LSCircleListHeaderViewModel *listHeaderViewModel;
@property (nonatomic, strong) LSCircleListSectionHeaderViewModel *sectionHeaderViewModel;
@property (nonatomic, strong) NSArray *dataArray;
@property (nonatomic, strong) RACSubject *cellClickSubject;
@end复制代码
// // LSCircleListViewModel.m // ZhongShui // // Created by 王隆帅 on 16/3/10. // Copyright © 2016年 王隆帅. All rights reserved. // #import "LSCircleListViewModel.h" #import "LSCircleListCollectionCellViewModel.h" #import "LSCircleListModel.h" @interface LSCircleListViewModel () @property (nonatomic, assign) NSInteger currentPage; @end @implementation LSCircleListViewModel - (void)yc_initialize { @weakify(self); [self.refreshDataCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) { @strongify(self); if (dict == nil) { [self.refreshEndSubject sendNext:@(LSRefreshError)]; ShowErrorStatus(@"网络链接失败"); return; } if ([dict[@"status"] integerValue] == 0) { self.listHeaderViewModel.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"JoinCircles"]).rac_sequence map:^id(NSDictionary *dic) { LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic]; LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init]; viewModel.model = model; return viewModel; }] array] mutableCopy]; self.dataArray = [[[([(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]).rac_sequence map:^id(NSDictionary *dic) { LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:dic]; LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init]; viewModel.model = model; return viewModel; }] array] mutableCopy]; [self ls_setHeaderRefreshWithArray:dict[@"Circles"]]; [self ls_dismiss]; } else { [self.refreshEndSubject sendNext:@(LSRefreshError)]; ShowMessage(dict[@"mes"]); } }]; [[[self.refreshDataCommand.executing skip:1] take:1] subscribeNext:^(id x) { @strongify(self); if ([x isEqualToNumber:@(YES)]) { [self ls_showWithStatus:@"正在加载"]; } }]; [self.nextPageCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dict) { @strongify(self); if (dict == nil) { [self.refreshEndSubject sendNext:@(LSRefreshError)]; ShowErrorStatus(@"网络链接失败"); return; } if ([dict[@"status"] integerValue] == 0) { NSMutableArray *recommandArray = [[NSMutableArray alloc] initWithArray:self.dataArray]; for (NSDictionary *subDic in [(NSDictionary *)dict[@"res"] arrayForKey:@"Circles"]) { LSCircleListModel *model = [LSCircleListModel mj_objectWithKeyValues:subDic]; LSCircleListCollectionCellViewModel *viewModel = [[LSCircleListCollectionCellViewModel alloc] init]; viewModel.model = model; [recommandArray addObject:viewModel]; } self.dataArray = recommandArray; [self ls_setFootRefreshWithArray:dict[@"Circles"]]; [self ls_dismiss]; } else { [self.refreshEndSubject sendNext:@(LSRefreshError)]; ShowMessage(dict[@"mes"]); } }]; } #pragma mark - private - (NSMutableDictionary *)requestCircleListWithId:(NSString *)idStr currentPage:(NSString *)currentPage { idStr = IF_NULL_TO_STRING(idStr); currentPage = IF_NULL_TO_STRING(currentPage); NSMutableDictionary * dict = [@{@"MemberID": idStr, @"pageSize": LS_REQUEST_LIST_COUNT, @"pageIndex":currentPage} mutableCopy]; return dict; } - (void)ls_setFootRefreshWithArray:(NSArray *)array { if (array.count < LS_REQUEST_LIST_NUM_COUNT) { [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasNoMoreData)]; } else { [self.refreshEndSubject sendNext:@(LSFooterRefresh_HasMoreData)]; } } - (void)ls_setHeaderRefreshWithArray:(NSArray *)array { if (array.count < LS_REQUEST_LIST_NUM_COUNT) { [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasNoMoreData)]; } else { [self.refreshEndSubject sendNext:@(LSHeaderRefresh_HasMoreData)]; } } #pragma mark - lazyLoad - (RACSubject *)refreshUI { if (!_refreshUI) { _refreshUI = [RACSubject subject]; } return _refreshUI; } - (RACSubject *)refreshEndSubject { if (!_refreshEndSubject) { _refreshEndSubject = [RACSubject subject]; } return _refreshEndSubject; } - (RACCommand *)refreshDataCommand { if (!_refreshDataCommand) { @weakify(self); _refreshDataCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { @strongify(self); return [RACSignal createSignal:^RACDisposable *(idsubscriber) { @strongify(self); self.currentPage = 1; [self.request POST:LS_URL_CIRCLE_MEMBER_LIST parameters:[self requestCircleListWithId:@"1" currentPage:[NSString stringWithFormat:@"%d",self.currentPage]] success:^(CMRequest *request, NSString *responseString) { NSDictionary *dict = [responseString objectFromJSONString]; [subscriber sendNext:dict]; [subscriber sendCompleted]; } failure:^(CMRequest *request, NSError *error) { ShowErrorStatus(@"网络链接失败"); [subscriber sendCompleted]; }]; return nil; }]; }]; } return _refreshDataCommand; } - (RACCommand *)nextPageCommand { if (!_nextPageCommand) { @weakify(self); _nextPageCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { @strongify(self); return [RACSignal createSignal:^RACDisposable *(id 复制代码subscriber) { @strongify(self); self.currentPage ++; [self.request POST:LS_URL_CIRCLE_TOPIC_LIST parameters:nil success:^(CMRequest *request, NSString *responseString) { NSDictionary *dict = [responseString objectFromJSONString]; [subscriber sendNext:dict]; [subscriber sendCompleted]; } failure:^(CMRequest *request, NSError *error) { @strongify(self); self.currentPage --; ShowErrorStatus(@"网络链接失败"); [subscriber sendCompleted]; }]; return nil; }]; }]; } return _nextPageCommand; } - (LSCircleListHeaderViewModel *)listHeaderViewModel { if (!_listHeaderViewModel) { _listHeaderViewModel = [[LSCircleListHeaderViewModel alloc] init]; _listHeaderViewModel.title = @"已加入的圈子"; _listHeaderViewModel.cellClickSubject = self.cellClickSubject; } return _listHeaderViewModel; } - (LSCircleListSectionHeaderViewModel *)sectionHeaderViewModel { if (!_sectionHeaderViewModel) { _sectionHeaderViewModel = [[LSCircleListSectionHeaderViewModel alloc] init]; _sectionHeaderViewModel.title = @"推荐圈子"; } return _sectionHeaderViewModel; } - (NSArray *)dataArray { if (!_dataArray) { _dataArray = [[NSArray alloc] init]; } return _dataArray; } - (RACSubject *)cellClickSubject { if (!_cellClickSubject) { _cellClickSubject = [RACSubject subject]; } return _cellClickSubject; } @end
ViewModel也是分为三个模块,因为代码太多摘重要的讲
ⅰ 第一个模块 : 处理数据、逻辑模块
处理数据这块,先用字典转为Model,在用Model配置ViewModel,ViewModel再去与UI及其逻辑对应。
ⅱ 第二个模块 : 私有函数
对于请求参数字典,能够放在VM中,便于模块化移植,也能够放在公共API中便于管理,看我的选择了,没有绝对的好位置,只有更适合我的的位置。
另外两个函数就是处理下拉及上拉时有没有更多数据的私有函数。
ⅲ 第三个模块 : 懒加载
此数据请求用的是AFN再度封装的一个工具类,实际上就是AFN。
通常而言,咱们正式项目中会遇到不少须要启动项目时就加载的,因此很快APPDelegate就会愈来愈庞大,既然其余的代码都简化解耦了,这里也能够作一下处理。
目录以下:
简化后的AppDelegate以下:
其余代码存放的位置以下:
当类对象被引入项目时, runtime 会向每个类对象发送 load 消息. load 方法仍是很是的神奇的, 由于它会在每个类甚至分类被引入时仅调用一次, 调用的顺序是父类优先于子类, 子类优先于分类. 并且 load 方法不会被类自动继承, 每个类中的 load 方法都不须要像 viewDidLoad 方法同样调用父类的方法。
这是利用了这个算是黑魔法的玩意,哈哈,就简化了APPDelegate!
当初原本想干掉基类来着,想利用Category + Protocol并利用Runtime的Methode Swizzle 来给系统函数添加本身的私有函数,当初VC已经搞定了,然而发现这样牵涉面太广,你对VC作了Category,UINavigationController 也会受到影响,假如你对View作了Category,其余继承View的也会有影响,并且当时交换方法都是在一个Category里管事,到第二个就覆盖了。。。不造为啥,由于知道这条路走不通就没继续搞下去了。。。
写到这里,你们应该都对我笔下的设计模式有了一些了解,由于里面涉及的东西确实太多,主要是这些玩意须要站在巨人的肩膀,遇到文中没有提到并且不懂得能够:
哈哈哈!别怪我...不是我不负责,由于你能够看看写到这里篇幅已经超出常人所能接受的了,并且我感受我把各个细节已经都照顾到了吧(๑乛◡乛๑ 磨人的小妖精)!你们有什么疑惑咱们能够在评论区交流!
最后,真的很但愿各位大神指出不足的地方,能让你们共同进步!
本文由做者 王隆帅 编写,转载请保留版权网址,感谢您的理解与分享,让生活变的更美好!