第一次重构的架构设计总结

前言:

每一个开发心中都有一个架构的梦,虽然不能像大佬们同样直接直接给出系统级的架构,可是咱们在平常的编码过程当中,也能够慢慢积累一些本身的架构的看法,慢慢提升~ios

由于在学校本身一我的在写整个App,加之需求也不明确,时常需求变动(在学校的组织写项目的通病了),因此编写过程真的是越写越糟心,因此,不得已对已经开发的一小部分作了重构,如下是本小白在重构过程当中总结的一些看法(不得不说,本科阶段讲的那些设计模式什么的,是真的颇有用,只是当时根本理解不了这些精髓,等到重构时才发现均可以套原型)。git

架构的几个方向:

  • view层的组织和调用设计
  • 本地持久化
  • 网络层设计(网络层会说的比较笼统)
  • 动态部署(Web App/Hybrid App/React-Native,这块也没咋说,由于目前没有涉猎)

架构设计的步骤:

  • 问题分类,分模块(这个很重要)
  • 搞清楚各个模块之间的依赖关系,设计好一套模块的交流规范并设计模块
  • 为架构保持必定量的超前性(血的教训)
  • 先实现基础模块,再组合基础模块造成初期架构

主要就是:自顶向下设计,自底向上实现,先量化数据再优化github

敏捷原则:对扩展开放-对修改封闭sql


什么样app的架构叫好架构?

  • 代码整齐,分类明确:每一个模块只负责模块内的事务
  • 不用文档,或不多文档,就能让业务方上手
  • 思路和方法要统一,尽可能不要多元
  • 没有横向依赖,万不得已不出现跨层访问:(大概就是拓扑排序的原理)

1,当一个需求须要多业务合做开发时,若是直接依赖,会致使某些依赖层上端的业务工程师在前期空转,依赖层下端的工程师任务繁重,致使延期(就会挤压QA老铁的时间,而后再找PM撕*。。。。。别问我是怎么知道的) 2,当要开辟一个新业务时,若是已有各业务间直接依赖,新业务又依赖某个旧业务,就致使新业务的开发环境搭建困难,由于必需要把全部相关业务都塞入开发环境,新业务才能进行开发。 3,当某一个被其余业务依赖的页面有所修改时,好比更名,涉及到的修改面就会特别大。影响的是形成任务量和维护成本都上升的结果。数据库

对应解决方法:依赖下沉,假如A、B、C三个模块存在横向依赖,这样的话引入新节点D,对A、B、C实现依赖下沉,当A调用B的某个页面的时候,将请求交给Mediater,而后由Mediater经过某种手段获取到B业务页面的实例,交还给A就好了。设计模式

  • 对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活- - 实现的条件
  • 易测试,易拓展
  • 保持必定量的超前性
  • 接口少,接口参数少
  • 高性能

关于不跨层访问说下:api

跨层访问是指数据流向了跟本身没有对接关系的模块。有的时候跨层访问是不可避免的,好比网络底层里面信号从2G变成了3G变成了4G,这是有可能须要跨层通知到View的。但这种状况很少,一旦出现就要想尽一切办法在本层搞定或者交给上层或者下层搞定,尽可能不要出现跨层的状况。跨层访问一样也会增长耦合度,当某一层须要总体替换的时候,牵涉面就会很大。缓存

易测试性:安全

尽量减小依赖关系,便于mock。另外,若是是高度模块化的架构,拓展起来将会是一件很是容易的事情。bash


架构分层:

梗概:

常常有‘三层架构MVC’这样的说法,以致于不少人就会认为三层架构就是MVC,MVC就是三层架构。其实不是的。三层架构里面其实没有Controller的概念,并且三层架构描述的侧重点是模块之间的逻辑关系。MVC有Controller的概念,它描述的侧重点在于数据流动方向。

三层架构

全部的模块角色只会有三种:

  • 数据管理者
  • 数据加工者
  • 数据展现者 意思也就是,笼统说来,软件只会有三层,每一层扮演一个角色。其余的第四层第五层,通常都是这三层里面的其中之一分出来的,最后都能概括进这三层的某一层中去,因此用三层架构来描述就比较广泛。

View层设计:

View层的架构一旦实现或定型,在App发版后可修改的余地就已经很是之小了。由于它跟业务关联最为紧密,作决策时要拿捏好尺度。

View层架构是影响业务方迭代周期的因素之一:

由于View层架构是最贴近业务的底层架构

view层架构知识点主要包括:

  • 良好的编码/实现规范
  • 合适的设计模式(MVC、MVCS、MVVM、VIPER)
  • 根据业务状况针对ViewController作好拆分(瘦身),提供一些小工具方便开发

view层代码规范:(第4点不必定)

1 viewDidload:作addSubview的事情

2 viewWillAppear:严格来讲这里一般不作视图位置的修改,而用来更新Form数据。缘由见下一点:

3 布局(添加约束)时机:首先,Autolayout发生在viewWillAppear以后,因此我通常选择放到 - viewWilllayoutSubview或者- viewDidLayoutSubviews中。由于viewWillAppear在每次页面即将显示都会调用,viewWillLayoutSubviews虽然在lifeCycle里调用顺序在viewWillAppear以后,可是只有在页面元素须要调整时才会调用,避免了Constraints的重复添加

4 viewDidAppear里面作添加监听之类的事情

5 属性的初始化,则交给getter(懒加载)去作,这也就要求:全部的属性都使用getter和setter,而且getter,setter方法放到.m文件的最后写,这样能够提升开发效率。另一种思路是将全部属性都放到 setUpPropertyConfig方法中,而后setUpPropertyConfig放到viewDidLoad中,二者都可,没有什么区别。

#pragma mark - life cycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.view addSubview:self.label];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.label.frame = CGRectMake(1, 2, 3, 4);
}

#pragma mark - getters and setters

- (UILabel *)label
{
    if (_label == nil) {
        _label = [[UILabel alloc] init];
        _label.text = @"1234";
        _label.font = [UIFont systemFontOfSize:12];
        ... ...
    }
    return _label;
}
@end
复制代码

6 每一个delegate方法写到一块区域里面去,使用#pragma mark - UITableViewDelegate进行分割(这个是猪场搬砖看到黄师傅的代码学到的。。。以前一直没有这个意识,感谢)

7 VC里面尽可能不要有私有方法

不是delegate方法的,不是event response(相应用户操做)方法的,不是life cycle(view didload这些方法)方法的,就是private method了,这些private methods通常是用于日期换算、图片裁剪啥的这种辅助的小功能。这些小功能通常都是单独抽出来写成模块的tool类或者系统Util类。

8 关于View的布局方法:

无外乎就是 storyboard+xib+代码撸的组合

借鉴一下@唐巧的分析脚本: 传送门: https://gist.github.com/tangqiaoboy/b149d03cfd0cd0c2f7a1 可见这个原本就是有争议的。

其实,实现简单的东西,用Code同样简单,实现复杂的东西,Code比StoryBoard更简单。

因此本渣通常采用: 1,复杂页面主体手撸代码(用的是masonry) 2,简单、静态的Cell以及封装的一些自定义小控件使用xib。

还有几点本人目前能力不够,不可以给出正确的看法:

A.是否须要让业务方统一派生ViewController。 B.


#MVC MVC架构基础请看象印笔记。

各个模块须要负责的事物:

  • M应该作的事: 1,给ViewController提供数据(网络获取API+本地的缓存获取API) 2,给ViewController存储数据提供接口(本地缓存的存储/更新新API) 3,提供通过抽象的业务基本组件(通常我会抽一个Manager(包括1,2)出来专门负责),供Controller调度
  • C应该作的事:(其中VC自带的View至关于C所管理的View的一个容器) 1,管理View Container的生命周期 2,负责生成全部的View实例,并放入View Container(就是C.view) 3,监听来自View与业务有关的事件,经过与Model的合做,来完成对应事件的业务。
  • V应该作的事: 1,响应与业务无关的事件,并所以引起动画效果,点击反馈(若是合适的话,尽可能仍是放在View去作)等。 2,界面元素表达

下面是MVCS、MVVM两个MVC设计模式的变种

可能还有一些别的设计模式,可是本人能力有限啊啊啊,因此只先介绍这俩

先说下:胖Model&瘦Model:

  • 胖Model:(MVVM的基本思想) 包含了部分弱业务逻辑。胖Model要达到的目的是,Controller从胖Model这里拿到数据以后,不用额外作操做或者只要作很是少的操做,就可以将数据直接应用在View上。

  • 瘦Model:(MVCS的基本思想) 瘦Model只负责业务数据的表达,全部业务不管强弱一概扔到Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,而后配套各类helper类或方法来对弱业务作抽象,强业务依旧交给Controller。

前言:首先无论MVVM也好,MVCS也好,他们的共识都是Controller会随着软件的成长,变很大很难维护很难测试。只不过两种架构思路的前提不一样,MVCS是认为Controller作了一部分Model的事情,要把它拆出来变成Store,MVVM是认为Controller作了太多数据加工的事情,因此MVVM把数据加工的任务从Controller中解放了出来,使得Controller只须要专一于数据调配的工做,ViewModel则去负责数据加工并经过通知机制让View响应ViewModel的改变。

MVCS:

从概念上来讲,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操做的角度上讲,它拆开的是Controller。

MVCS使用的前提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去作。因此对应到MVCS,它在一开始就是拆分的Controller。由于Controller作了数据存储的事情,就会变得很是庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另外一个对象去作,这个对象就是Store。这么调整以后,整个结构也就变成了真正意义上的MVCS。

MVVM:

ReactiveCocoa成熟以后,ViewModel和View的信号机制在iOS下终于有了一个相对优雅的实现(MVC中View和Model是不能直接通讯的,须要Controller作一个协调者的身份)。MVVM本质上也是从MVC中派生出来的思想,MVVM着重想要解决的问题是尽量地减小Controller的任务。

MVVM是基于胖Model的架构思路创建的,而后在胖Model中拆出两部分:Model和ViewModel。关于这个观点我要作一个额外解释:胖Model作的事情是先为Controller减负,而后因为Model变胖,再在此基础上拆出ViewModel,跟业界广泛认知的MVVM本质上是为Controller减负这个说法并不矛盾,由于胖Model作的事情也是为Controller减负。

另外,MVVM把数据加工的任务从Controller中解放出来,跟MVVM拆分的是胖Model也不矛盾。要作到解放Controller,首先你得有个胖Model,而后再把这个胖Model拆成Model和ViewModel。

在MVVM中,Controller扮演的角色:

  • MVVM的名称里没有C形成了MVVM不须要Controller的错觉,其实MVVM是必定须要Controller的参与的,虽然MVVM在必定程度上弱化了Controller的存在感,而且给Controller作了减负瘦身(这也是MVVM的主要目的)。其实MVVM应该是Model-ViewModel-Controller-View这样的架构,并非不须要Controller。

  • Controller夹在View和ViewModel之间作事情: 1,最主要事情就是将View和ViewModel进行绑定。在逻辑上,Controller知道应当展现哪一个View,Controller也知道应当使用哪一个ViewModel,然而View和ViewModel它们之间是互相不知道的,因此Controller就负责控制他们的绑定关系。 2,常规的UI逻辑处理

一句话总结:

在MVC的基础上,把Controller拆出一个ViewModel专门负责数据处理的事情,就是MVVM。

  • 关于MVVM是否必需要使用ReactiveCocoa? 固然不是,只是由于苹果自己并无提供一个比较适合这种状况的绑定方法。虽然有KVO,Notification,block,delegate用来作数据通讯,从而来实现绑定,但都不如ReactiveCocoa提供的RACSignal来的优雅简单,若是不用ReactiveCocoa,绑定关系可能就作不到那么松散那么好,但并不影响它仍是MVVM。

再深层次的我就不能很好解释了:若是须要了解,能够细看: https://www.teehanlax.com/blog/model-view-viewmodel-for-ios/

关于项目究竟使用哪一种设计模式:

MVC实际上是很是高Level的抽象,意思也就是,在MVC体系下还能够再衍生无数的架构方式,但万变不离其宗的是,它必定符合MVC的规范。 因此个人建议是:

  • 只要不是Controller的核心逻辑,均可以考虑拆出去,而后在架构的时候做为一个独立模块去定义,以及设计实现,可是不要为了拆分而拆分。
  • 拆分出的模块尽可能提升复用性,下降强业务相关性。
  • 拆分的粒度要尽量大一点,封装得要透明一些。

网络层:

首先先说下跨层访问:

关于跨层数据流通:

当存在A<-B<-C这样的结构时。当C有事件,经过某种方式告知B,而后B执行相应的逻辑。一旦告知方式不合理,让A有了跨层知道C的事件的可能,你 就很难保证A层业务工程师在未来不会对这个细节做处理。一旦业务工程师在A层产生处理操做,有多是补充逻辑,也有多是执行业务,那么这个细节的相关处理代码就会有一部分散落在A层。然而前者是不该该散落在A层的,后者有多是需求。另外,由于B层是对A层抽象的,执行补充逻辑的时候,有可能和B层针对这个事件的处理逻辑产生冲突,这是咱们很不但愿看到的。 但有时跨层数据流通也是不可避免的: 好比,信号从2G变成3G变成4G变成Wi-Fi,这个就是须要跨层数据交流的。

再考虑下文:

数据以什么方式交付给业务层:

大多数App在网络层所采用的方案主要集中于这三种:Delegate,Notification,Block。 通常都是组合使用,这里我只能说下我的的选择,毕竟我的涉猎有限,因此只能结合自身所采用的模式说下好处: 以前在猪场某部门实习,网络层采用的是block为主进行数据交付。

当回调以后要作的任务在每次回调时都是一致的状况下,选择delegate,在回调以后要作的任务在每次回调时没法保证一致,选择block。

  • Delegate为主,Notification为辅(苹果的原生网络请求就是delegate。。可是AFN采用block作回调)。 因此我通常都是采用AFN作网络(毕竟方便省事),因此通常采用以下形式进行请求:
//请求发起采用AFN的Block,回调使用delegate方式,这样在业务方这边回调函数就可以比较统一,便于维护。
  [AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
        if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
            [self.delegate successedWithResponse:response];
        }
    } failed:^(Request *request, NSError *error){
        if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
            [self failedWithRequest:request error:error];
        }
    }];
复制代码

缘由:使用Delegate可以很好地避免跨层访问,同时限制了响应代码的形式,相比Notification而言有更好的可维护性,而Notification则解决了跨层数据流通的相应需求。

可是使用Notification必定要约定好命名规范,否则会引起后期维护的灾难。

集约型API调用方式和离散型API调用方式的选择:

  • 集约型API调用其实就是全部API的调用只有一个类,而后这个类接收API名字,API参数,以及回调着陆点(block,或者delegate等各类模式的着陆点)做为参数。而后执行相似startRequest这样的方法,它就会去根据这些参数起飞去调用API了,而后得到API数据以后再根据指定的着陆点去着陆。

  • 离散型API调用是这样的,一个API对应于一个APIManager,而后这个APIManager只须要提供参数就能起飞,API名字、着陆方式都已经集成入APIManager中。

交付什么样的数据给业务层?【(Adapator适配器)其实这就属于MVVM的VM层作的事了,真的是每一处都是设计模式。。。。】

理想状况是但愿API的数据下发以后就可以不须要进一步处理直接被View所展现。首先要说的是,这种状况很是少。另外,这种作法使得View和API联系紧密,也是不该该发生的。 举个栗子:

先定义一个protocol:
@protocol AdapatorProtocol <NSObject>
- (NSDictionary)reformDataWithManager:(APIManager *)manager;
@end

在Controller里是这样:
@property (nonatomic, strong) id< AdapatorProtocol > XAdapator;

#pragma mark - APIManagerDelegate
- (void)apiManagerDidSuccess:(APIManager *)manager
{
    NSDictionary *XData = [manager fetchDataWithReformer:self. XAdapator];
    [self.XView configWithData:XData];
}

在APIManager里面,fetchDataWithReformer是这样:
- (NSDictionary)fetchDataWithReformer:(id< AdapatorProtocol >)adapator{
    if (adapator == nil) {
        return self.rawData;
    } else {
//adapaor进行处理数据
        return [adapator reformDataWithManager:self];
    }
}
复制代码

使用适配器模式带来的好处:

  • 减轻Controller压力,下降了代码复杂度,同时提升了灵活性,任什么时候候切换reformer而没必要切换业务逻辑就能够应对不一样View对数据的须要
  • 在处理单View对多API,以及在单API对多View的状况时,reformer提供了很是优雅的手段来响应这种需求,隔离了转化逻辑和主体业务逻辑,避免了维护灾难。
  • 转化逻辑集中,且将转化次数转为只有一次。使用数据原型的转化逻辑至少有两次,第一次是把JSON映射成对应的原型,第二次是把原型转变成能被View处理的数据。reformer一步到位。另外,转化逻辑在Adapator里面,未来若是API数据有变,就只要去找到对应Adapator而后改掉就行了,方便后期维护
  • 业务数据和业务有了适当的隔离。这么作的话,未来若是业务逻辑有修改,换一个Adapator就行了。若是其余业务也有相同的数据转化逻辑,其余业务直接拿这个Adapator就能够用了,不用重写。另外,若是controller有修改(好比UI交互方式改变),能够放心换controller,彻底不用担忧业务数据的处理。

网络层优化:

1,使用缓存(本地混存+URL缓存)进行请求次数的减小,能不发请求的就尽可能不发请求,必需要发请求时,能合并请求的就尽可能合并请求。 2,须要上传的日志,积满必定数量再上传 3,通常项目都有多个服务器,应用启动的时候得到本地列表中全部IP的ping值,而后将Dev_URL中的HOST修改成咱们找到的最快的IP。另外,这个本地IP列表也会须要经过一个API来维护,通常是天天第一次启动的时候ping一下,而后更新到本地。 4,比较大的数据压缩再上传。


数据持久化:

首先有很是多的方案可供选择: 一、NSUserDefault: 通常是小规模数据,弱业务相关数据,NSUserDefault变大会影响App启动的时间,敏感数据不要放NSUserDefault,虽然NSUserDefault的存取真的是很方便。 二、KeyChain:Keychain是苹果提供的带有可逆加密的存储机制,广泛用在各类存密码的需求上。另外,因为App卸载只要系统不重装,Keychain中的数据依旧可以获得保留,以及可被iCloud同步的特性,你们都会在这里存储用户惟一标识串。因此有须要加密、须要存iCloud的敏感小数据,通常都会放在Keychain。 三、File:主要包括:

1,Plist 2,archive(归档):只适合存一些不常用的,大量的数据,读取以后直接换变为对象/直接将对象存储(须要支持) 3,Stream(直接存文件):适合数据较大且常用,可是文件通常都是遍历才能拿到,因此建议为文件创建数据库索引

四、基于数据库的无数子方案(YYCache,FMDB,sqlite,CoreData[本人不喜欢用])。数据库中的数据应该都是强业务相关的,而且不能是很大的文件,好比一个大图片或者视频之类的,通常是存文件,而后数据中存放文件路径这样配合,而推荐直接使用YYCache,一站式服务。

所以,当有须要持久化的需求的时候,咱们首先考虑的是应该采用什么手段去进行持久化。

数据库记得作线程处理(原理跟iOS的属性的线程安全同样) 好比SQLite库就推荐使用Serialized(默认):串行队列访问,虽然会慢一丢丢,可是方便易维护。

持久层有专门负责对接View层模块或业务的DataCenter,它们之间经过Record来进行交互。DataCenter向上层提供业务友好的接口,这通常都是强业务:好比根据用户筛选条件返回符合要求的数据等。而后DataCenter在这个接口里面调度各个Table,作一系列的业务逻辑,最终生成record对象,交付给View层业务。

DataCenter为了要完成View层交付的任务,会涉及数据组装和跨表的数据操做。数据组装由于View层要求的不一样而不一样,所以是强业务。跨表数据操做本质上就是各单表数据操做的组合,DataCenter负责调度这些单表数据操做从而得到想要的基础数据用于组装。那么,这时候单表的数据操做就属于弱业务,这些弱业务就由Table映射对象来完成。 Table对象经过QueryCommand来生成相应的SQL语句,并交付给数据库引擎去查询得到数据,而后交付给DataCenter。

差很少就这些,等重构完再补充~

相关文章
相关标签/搜索