前言
上家公司已经倒闭重组在当时技术会议上时负责编写的技术文档,技术文档所涉及的东西都是当时接手项目以后修改的架构(16年中下旬),在现在来看有很多还不够成熟的地方也有很多设计依然是非常规范的,在后面的博客会慢慢讨论,希望本文能够给您的移动项目演进提供一些借鉴。
架构设计
客户端功能基础流程图.
--------------- --------------- --------------- ---------------
| | | | | | | |
| 调用网络API | --> | 展现列表 | --> | 选择列表 | --> | 展现单页 |
| | | | | | | |
--------------- --------------- --------------- ---------------
^ |
| |
| |
------------------------------------------
架构分层
客户端使用的是四层架构方式
1.展现层:可重用功能性控件的封装及ViewController中的UI处理
2.业务层:弱业务逻辑结构的主要存放地及model层
3.网络层:统一的网络API调用及回调方式
4.本地数据层:缓存API的封装
关于View的布局
使用Autolayout布局:主要使用两大Autolayout框架
1.Masonry:成熟,使用人数多
2.SDAutolayout:国人开发,结构读写简介便利
可以根据自己的开发习惯挑选.
关于storyboard,nib,code
1.storybard禁止使用,在多人开发的场景下,容易造成难以解决的冲突.
2.大部分的场景比如:拥有复杂界面元素,核心动画需使用code去画view
3.简易View可以使用nib直接拖拽比如简易的cell或者某个悬浮控件.
注意事项:尽少使用统一派生的ViewController.
关于胖Model和瘦Model
在客户端中涉及到网络API中的Model大部分使用瘦Model业务逻辑并没有在Model层中处理,而是仅仅做了对于数据的处理.而在其他方面涉及到弱业务逻辑的时候,使用的是胖Model对比如下
Raw Data:
timestamp: 1234567
FatModel:
@property(nonatomic,assign)CGFloat timestamp;
- (NSString *)ymdDateString; //2015-04-20 15:16
- (NSString *)gapString; //3分钟前、一小时前、一天前、2015-3-13 12:34
Controller:
self.dateLabel.text = [FatModel ymdDateString];
self.gapLabel.text = [FatModel gapString];
Raw Date:
Dic: JSONDic
ThinModel:
#property(nonatomic,assign)NSString *name;
#property(nonatomic,assign)Nsstring *age;
Controller:
ThinModel *model = [ThinModel MJExtent:Dic];
self.nameLabel.text = model.name;
self.ageLabel.text = model.age;
关于ViewController中业务逻辑处理规范
viewController代码规范
@property(nonatiomic,strong)UIButton *confirmButton
...
#pragma mark --life cycle
viewDidLoad
viewWillAppear
...
#pragma mark - UITableViewDelegate
methods
...
#pragma mark - CustomDelegate
methods
...
#pragma mark - event response
- (void)didTappedConfirmButton:(UIButton *)confirmButtom
...
#pragma mark - private methods
methods
...
#pragma mark - getters and setters
- (UIButton *)confirmButton
- (UITableView *)tableView
...
注意事项:所有的属性都是用getter和setter.在viewDidload里面只做addSubview的事情.
iOS客户端统一使用XYNetWorkHeapler的API.
在于业务层对接的部分API统一使用的Delegate回调方式限制灵活性,以此来交换应用的可维护性.但是在API的内部还是使用了Block的形式回调网络请求保存灵活性。
[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];
}
}];
关于交付什么样的数据给业务层
API交付的是转换后的字典而且仅在服务器返回的code合法(为0)的情况下才会拥有回调数据.其他情况下会回调nil在viewController中做出相应的处理.并不要直接回调对应的模型,以便为以后业务的拓展做铺垫.
在业务层使用的是离散型API调用方式
离散型API调用方式.一个API对应于一个APIManager,然后这个APIManager只需要提供参数就能起飞,API名字、着陆方式都已经集成入APIManager中,例如
离散型API调用方式:
@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;
// getter
- (ItemListAPIManager *)itemListAPIManager
{
if (_itemListAPIManager == nil) {
_itemListAPIManager = [[ItemListAPIManager alloc] init];
_itemListAPIManager.delegate = self;
}
return _itemListAPIManager;
}
// 使用的时候就这么写:
[self.itemListAPIManager loadDataWithParams:params];
关于API新增接口方法命名
好的函数名:
- (NSDictionary *)exifDataOfImage:(UIImage *)image atIndexPath:(NSIndexPath *)indexPath;
坏的函数名:
- (id)exifData:(UIImage *)image position:(id)indexPath callback:(id<ErrorDelegate>)delegate;
为什么坏?
1. 不要直接返回id或者传入id.
2. 要告知业务方要传的东西是什么,比如要传Image,那就写上ofImage。如果要传位置,那就要写上IndexPath,而不是用position这么笼统的东西.
3. 没有任何理由要把delegate作为参数传进去,一定不会有任何情况不得不这么做的。而且delegate这个参数根本不是这个函数要解决的问题的充要条件.
在关于数据库操作方面iOS客户端并没有使用苹果自带的Core Data,而是使用了iOS领域比较常用FMDB.数据库方案主要是为了便于增删改查,当数据有状态和类别的时候最好还是采用数据库方案比较好,而且尤其是当这些状态和类别都是强业务相关的时候.
FMDBAPI
iOS客户端在关于数据库操作的时候统一调用XYFMDBManagerAPI XYFMDBManagerAPI已封装好关于创建新表及增删改查的方法.例如
#pragma mark 群组数据库处理
/**根据群组唯一标示创建一个数据库表*/
- (void)CreatGroupNewTable:(NSString *)TableName;
/**根据群组标示向标中插入数据*/
- (void)AddDataImageUrl:(NSString *)TableName WithImageUrl:(NSString *)imageUrl;
/**根据群组标示查询表中所有数据*/
- (NSArray *)QueryData:(NSString *)TableName;
缓存淘汰算法:
1.使用LRU(least-recently-used) 算法来淘汰使用频率较低的缓存。
2.不同的缓存给予最长缓存清除时间。
持久层与业务层的交互方式
-------------------------------------------
| |
| LogicA LogicB LogicC | -------------------------------> View Layer
| \ / | |
-------\-------/------------------|--------
\ / |
\ / Virtual | Virtual
\ / Record | Record
| |
-----------|----------------------|--------
| | | |
Strong Logics | DataCenterA DataCenterB |
| / \ | |
-----------------|-------/-----\-------------------|-------| Data Logic Layer ---
| / \ | | |
Weak Logics | Table1 Table2 Table | |
| \ / | | |
--------\-----/-------------------|-------- |
\ / | |--> Data Persistance Layer
\ / Query Command | Query Command |
| | |
-----------|----------------------|-------- |
| | | | |
| | | | |
| DatabaseA DatabaseB | Data Operation Layer ---
| |
| Database Pool |
-------------------------------------------
持久层有专门负责对接View层模块或业务的DataCenter,它们之间通过Record来进行交互。DataCenter向上层提供业务友好的接口,这一般都是强业务:比如根据用户筛选条件返回符合要求的数据等。
然后DataCenter在这个接口里面调度各个Table,做一系列的业务逻辑,最终生成record对象,交付给View层业务。
DataCenter为了要完成View层交付的任务,会涉及数据组装和跨表的数据操作。数据组装因为View层要求的不同而不同,因此是强业务。跨表数据操作本质上就是各单表数据操作的组合,DataCenter负责调度这些单表数据操作从而获得想要的基础数据用于组装。那么,这时候单表的数据操作就属于弱业务,这些弱业务就由Table映射对象来完成。
Table对象通过QueryCommand来生成相应的SQL语句,并交付给数据库引擎去查询获得数据,然后交付给DataCenter。
其他本地持久化方案使用场景规范
NSUserDeault
一般来说,小规模数据,弱业务相关数据,都可以放到NSUserDefault里面,内容比较多的数据,强业务相关的数据就不太适合NSUserDefault了。
keychain
Keychain是苹果提供的带有可逆加密的存储机制,普遍用在各种存密码的需求上。另外,由于App卸载只要系统不重装,Keychain中的数据依旧能够得到保留,以及可被iCloud同步的特性,大家都会在这里存储用户唯一标识串。所以有需要加密、需要存iCloud的敏感小数据,一般都会放在Keychain。
文件存储
文件存储包括了Plist、archive、Stream等方式,一般结构化的数据或者需要方便查询的数据,都会以Plist的方式去持久化。Archive方式适合存储平时不太经常使用但很大量的数据,或者读取之后希望直接对象化的数据,因为Archive会将对象及其对象关系序列化,以至于读取数据的时候需要Decode很花时间,Decode的过程可以是解压,也可以是对象化,这个可以根据具体<NSCoding>中的实现来决定。Stream就是一般的文件存储了,一般用来存存图片啊啥的,适用于比较经常使用,然而数据量又不算非常大的那种。
注意事项与各项指标及改进方向
内存泄漏
在iOS客户端最初版本中,因为内存泄漏所导致的error非常的多.所以关于内存泄漏方面是现如今每一次迭代开发所注意的重中之重。内存泄漏注意事项:
1.block导致的循环引用
2.timer是否持有self
3.声明delegate为strong类型
4.CoreFoundation对象(C对象)
Crash
目前使用的Crash统计工具为bugly,Crash率的指标定为千分之三.
改进方向
目前版本针对改进方向,网络层的继续优化及冗余代码的重构.
FAQ
在使用网络API的时候自己新增方法代理回调程序Crash
请检查是否对代理是否还拥有着路点可以回调进行了判断
if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
[self.delegate successedWithResponse:response];
}
项目Clone之后关于swift的类报错
请检查Xcode版本,iOS客户端部分模块使用的swift2.0进行编写,需要在Xcode7.3以上才可以支持swift2.0的语法。
无xcworkspace文件无法启动工程
如还未安装cocoapods请使用终端下载安装,因为所有的第三方库均托管给cocoapods进行托管.