每一个开发心中都有一个架构的梦,虽然不能像大佬们同样直接直接给出系统级的架构,可是咱们在平常的编码过程当中,也能够慢慢积累一些本身的架构的看法,慢慢提升~ios
由于在学校本身一我的在写整个App,加之需求也不明确,时常需求变动(在学校的组织写项目的通病了),因此编写过程真的是越写越糟心,因此,不得已对已经开发的一小部分作了重构,如下是本小白在重构过程当中总结的一些看法(不得不说,本科阶段讲的那些设计模式什么的,是真的颇有用,只是当时根本理解不了这些精髓,等到重构时才发现均可以套原型)。git
主要就是:自顶向下设计,自底向上实现,先量化数据再优化github
敏捷原则:对扩展开放-对修改封闭sql
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层的架构一旦实现或定型,在App发版后可修改的余地就已经很是之小了。由于它跟业务关联最为紧密,作决策时要拿捏好尺度。
由于View层架构是最贴近业务的底层架构
view层架构知识点主要包括:
#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
复制代码
不是delegate方法的,不是event response(相应用户操做)方法的,不是life cycle(view didload这些方法)方法的,就是private method了,这些private methods通常是用于日期换算、图片裁剪啥的这种辅助的小功能。这些小功能通常都是单独抽出来写成模块的tool类或者系统Util类。
无外乎就是 storyboard+xib+代码撸的组合
借鉴一下@唐巧的分析脚本: 传送门: https://gist.github.com/tangqiaoboy/b149d03cfd0cd0c2f7a1 可见这个原本就是有争议的。
其实,实现简单的东西,用Code同样简单,实现复杂的东西,Code比StoryBoard更简单。
因此本渣通常采用: 1,复杂页面主体手撸代码(用的是masonry) 2,简单、静态的Cell以及封装的一些自定义小控件使用xib。
A.是否须要让业务方统一派生ViewController。 B.
#MVC MVC架构基础请看象印笔记。
各个模块须要负责的事物:
可能还有一些别的设计模式,可是本人能力有限啊啊啊,因此只先介绍这俩
胖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的改变。
从概念上来讲,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操做的角度上讲,它拆开的是Controller。
MVCS使用的前提是,它假设了你是瘦Model,同时数据的存储和处理都在Controller去作。因此对应到MVCS,它在一开始就是拆分的Controller。由于Controller作了数据存储的事情,就会变得很是庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另外一个对象去作,这个对象就是Store。这么调整以后,整个结构也就变成了真正意义上的MVCS。
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。
再深层次的我就不能很好解释了:若是须要了解,能够细看: https://www.teehanlax.com/blog/model-view-viewmodel-for-ios/
MVC实际上是很是高Level的抽象,意思也就是,在MVC体系下还能够再衍生无数的架构方式,但万变不离其宗的是,它必定符合MVC的规范。 因此个人建议是:
首先先说下跨层访问:
当存在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。
//请求发起采用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参数,以及回调着陆点(block,或者delegate等各类模式的着陆点)做为参数。而后执行相似startRequest这样的方法,它就会去根据这些参数起飞去调用API了,而后得到API数据以后再根据指定的着陆点去着陆。
离散型API调用是这样的,一个API对应于一个APIManager,而后这个APIManager只须要提供参数就能起飞,API名字、着陆方式都已经集成入APIManager中。
理想状况是但愿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];
}
}
复制代码
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。
差很少就这些,等重构完再补充~