iOS组件化iOS开发移动知识

记一次iOS重构之路

2018-11-28  本文已影响36人  临川慕容吹雪

新入职了,前一个月陆陆续续把之前一个App重构了一下下,目前重构了一半,基本架构算是弄完了,先总结下,后面接着完善。
分以下说明下:

1: 为什么要重构
2:重构前的准备工作
3:重构之路

1:为什么要重构
1.1 代码层面

首先看到我司这个新的App之前写的代码,是用 MVC模式写的,但是发现一个类里面尤其是viewcontroller写了几百行代码,有的都是一千多行,我熟悉起来感觉好累,其次模块架构分的还是不够清晰,有些业务逻辑放到了其他模块里面,没有单独拆分出来。最后感觉里面冗余了很多无用的代码。当然了我的吐槽并不是说我自己多牛,其实我也只是个菜鸟,只是希望重构一下方便后续维护。

1.2 个人原因

因为我之前都是做SDK开发,很久没有单独开发App了,并且之前的公司代码全部都是模块化管理,我们现在这个完整的App却是耦合性很高,加之我反复熟悉了我司之前的这个App业务逻辑,前段时间也比较空闲,于是就决定重构一下。


2.重构前的准备工作

根据我之前的经验,我希望这个App模块化,耦合性尽量低一些。

2.1 设计模式的转变

我准备从之前的单纯mvc设计模式转变成MVVM With ReactiveCocoa设计模式,
MVVM的使用我参考了以下文章,供大家参考:
iOS 关于MVC和MVVM设计模式的那些事
对于结合使用ReactiveCocoa,我觉得可以更加方便view层和ViewModel层之间的交互,并且ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。对于它的使用我参考了一下文章:
最快让你上手ReactiveCocoa之基础篇
ReactiveCocoa 中 RACSignal 是如何发送信号的

2.2 组件化改造

组件化我直接使用CasaiOS应用架构谈 组件化方案,简单来说基于casa的 CTMediator
组件架构内部调用部分
通过Target+Action以及组件+类别的调用方式组成一个中介者模式

原因很简单我之前的公司就是使用这一套架构,我觉得还不错,我这边就接着借鉴使用了。
其实组件化有很多方案,强烈推荐可以参考这篇文章

部分目录截图

3.重构之路
3.1项目结构整理

首先大概看下这个App结构

效果图

从这个效果图上面我先总结了重构该项目大概所需要的组件


3.2 MVVM+RAC项目重构开始

就以这个模块为例说明下:

首先建立BaseViewmodel,遵循BaseViewModelProtocol协议。

@protocol PLBaseViewModelProtocol <NSObject>

@optional

@property (nonatomic, readonly, copy) NSDictionary *params;
@property (nonatomic, readonly, copy) NSString *title;
//error接受者
@property (nonatomic, readonly, strong) RACSubject *errors;

- (instancetype)initWithParams:(NSDictionary *)params;

- (void)initialize;

@end

所有的viewmodel都要继承它BaseViewmodel
BaseViewmodel里面结构如下:

19E6100E-64A2-432C-871A-AB7F8B0ED60D.png
通过- (instancetype)initWithParams:(NSDictionary *)params方法传递进来一些基本参数提供给Viewmodel使用,在initialize方法里面子类里面放些需要初始化的操作。
建立PLBaseView,遵循PLBaseViewProtocol,所有直接继承UIView类型的view都要继承它,其他view比如tableViewcell遵循PLBaseViewProtocol
@protocol PLBaseViewProtocol <NSObject>

@optional

@property (nonatomic, strong, readonly) PLBaseViewModel *viewModel;

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

- (void)renderViews;

- (void)bindViewModel:(id)viewModel;

- (void)bindViewModel;

@end
@implementation PLBaseView

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    PLBaseView *view = [super allocWithZone:zone];
    @weakify(view)
    [[view rac_signalForSelector:@selector(initWithViewModel:)] subscribeNext:^(RACTuple * _Nullable x) {
        @strongify(view)
        [view renderViews];
        [view bindViewModel];
    }];
    
    return view;
}

- (instancetype)initWithViewModel:(PLBaseViewModel *)viewModel {
    if (self = [super init]) {
        _viewModel = viewModel;
    }
    return self;
}
//配置子视图的操作放在这个地方
- (void)renderViews {
    
}
//与viewModel的具体交互操作
- (void)bindViewModel {
    
}

- (void)bindViewModel:(id)viewModel {
    _viewModel = viewModel;
}

建立baseControllerView,其实这个类里面类容和baseView里面类似,绑定viewModel

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    PLModelViewController *viewController = [super allocWithZone: zone];
    @weakify(viewController)
    [[viewController rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(RACTuple * _Nullable x) {
        @strongify(viewController)
        [viewController bindViewModel];
    }];
    return viewController;
}

- (instancetype)initWithViewModel:(PLBaseViewModel *)viewModel {
    if (self = [super init]) {
        self.viewModel = viewModel;
    }
    return self;
}
//通过RAC进行title绑定操作
- (void)bindViewModel {
    RAC(self,title) = RACObserve(self, viewModel.title);
    //订阅信号
    [self.viewModel.errors subscribeNext:^(NSError *error) {
        NSLog(@"viewModel 错误信息------%@",error);
    }];
}

还有在通过继承baseViewControllerbaseView分别针对tabelView做了进一步处理,新建类PLBaseTableViewController,PLBaseTableViewModel,这里代码代码就不在详细赘述。
PLBaseTableViewModel主要做了如下额外处理:

PLBaseTableViewModel.h
PLBaseTableViewController主要做了如下处理,主要添加了tabelView,集成了MJRefresh下拉刷新和上拉加载功能.
PLBaseTableViewController.h

这样的好处就是避免使用tabelveVIew的时候还导入UITableViewDataSource,降低了代码的耦合性。


上面主要的基类创建完成,就可以开始使用MVVM+RAC正式开始构建业务相关页面了。

@class FitfunBannerModel;

@interface FitfunInfoListViewModel : PLBaseTableViewModel

//滚动视图数据源
@property (nonatomic, readonly, strong) NSArray <FitfunBannerModel *> *banners;
//请求banner数据命令
@property (nonatomic, readonly, strong) RACCommand *requestBannerDataCommand;
@end

主要就是通过RAC信号源来进行数据交换和传递
.m文件里面主要用到了RAC的RACCommand

E147CEE2-5AC9-40E8-ABA5-6976245D6A62.png

RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。具体使用不熟悉的可以参考上面【设计模式的转变】我分享RAC学习的链接。
使用场景:监听按钮点击,网络请求

网络请求方面我还进行了解耦。

使用的是casa的那一套iOS应用架构谈 网络层设计方案,简单来说是使用CTNetworking,网络层上部分使用离散型设计,下部分使用集约型设计,使用delegate来做数据对接,仅在必要时采用Notification来做跨层访问,设计合理的继承机制,让派生出来的APIManager受到限制。这个具体有兴趣了解的话·请看上面共享的链接。
FitfunInfoListViewModel.m文件里面我具体的体现是遵循CTAPIManagerParamSource,CTAPIManagerCallBackDelegate协议,然后再对应协议里面传递需要的参数和相应对应的请求结果。进行网络请求就需要我们继承CTAPIBaseManager,里面设置一些网络请求相关操作。看下里面我写的大致代码:

#pragma mark - CTAPIManagerParamSource
//这里放额外拼接的参数
- (NSDictionary *)paramsForApi:(CTAPIBaseManager *)manager {
    NSMutableDictionary *dic = [[NSMutableDictionary alloc]init];
    if (manager == self.bannerImageAPIManager) {
        dic[@"id"] = (self.params[@"bannerID"]?:@"");
    } 
    return dic;
}

#pragma mark -CTAPIManagerCallBackDelegate
//这里网络请求成功的相关操作
- (void)managerCallAPIDidSuccess:(CTAPIBaseManager *)manager {
   
}
//网络请求失败相关操作
- (void)managerCallAPIDidFailed:(CTAPIBaseManager *)manager{
  
}

#pragma mark - getter&&setter

//继承于CTAPIBaseManager,这里配置网络请求地址和请求类型
- (FitfunAPIBaseManager *)topicInfoAPIManager {
    if (!_topicInfoAPIManager) {
        _topicInfoAPIManager = [[FitfunAPIBaseManager alloc] initWithMethodName:front_content_list reuquest:CTAPIManagerRequestTypePost];
        _topicInfoAPIManager.paramSource = self;
        _topicInfoAPIManager.delegate = self;
    }
    return _topicInfoAPIManager;
}
//reformer遵循`CTAPIManagerDataReformer`,用来统一解析网络请求成功数据
- (FitfunAPIBaseDataReformer *)reformer {
    if (!_reformer) {
        _reformer = [[FitfunAPIBaseDataReformer alloc]init];
    }
    return _reformer;
}

资讯的新闻列表的ViewModel写完了,就可以新建ViewFitfunInfoListTableViewCell,遵循PLBaseViewProtocol协议,
view里面主要就是进行视图搭建和解析ViewModel相关操作,这里直接跳过。
然后新建新闻列表的FitfunInfoViewController继承于PLBaseTableViewController,通过FitfunInfoListViewModel进行数据关联,RAC进行数据传递,最后我们的新闻列表Controller里面结构和代码就会特别清爽。
我只需要在绑定的ViewModel对应的数据监听方法中操作就ok了。

FitfunInfoViewController部分ViewModel处理代码
大致重构项目使用MVVM+RAC的简单说明就是这样了,
重新调整了下结构,使业务处理逻辑更加清晰,代码结构慢慢趋于完善。
为了更直观看出代码的变化我们可以对比下新闻列表页构造前后代码的变化和调整: 之前新闻列表构造
初步改造之后
3.3 AppDelegate解耦

初步改造之后,然后看了下之前项目里面AppDelegate各种注册和事件处理,Appdelegate.m代码冗余度太高了。于是考虑到如何将Appdelegate解耦下。综合之前工作经验和网上别人说的,我总结了目前有大致如下几种解耦方式:

这里还增加两个我之前公司用到的另外两个解耦方法

我这个项目中考虑了一下,我重构的项目目前不是很大,在Appdelegate对应方法里面注册的类相对有限,我想在各个类里面对应响应UIApplicationDelegate协议方法,为了简单化一些暂时参考了FRDModuleManager,在这个基础上简单改造了成了PLModuleManager,使用时需要在Appdelegate.m使用PLModuleManager注册相关方法,在PLModuleManager初始化时候持有需要注册,而注册过的遵循UIApplicationDelegateUNUserNotificationCenterDelegate协议,实现需要的协议方法即可。


后续代码调整和总结:

初步重构项目的架构就是那样了,后面还需要做的就是

疏漏和不合理之处如果各位哥哥姐姐们看到了,请不吝赐教,我只是按照自己的思路简单初步总结了下后续重构完毕这部分文章重新再整理下.

上一篇下一篇

猜你喜欢

热点阅读