【HELLO WAKA】WAKA iOS客户端 之二 架构设计与实现篇
上一篇主要做了MAKA APP的需求分析,功能结构分解,架构分析,API分析,API数据结构分析。
这篇主要讲如何从零做iOS应用架构。
全系列
【HELLO WAKA】WAKA iOS客户端 之一 APP分析篇
【HELLO WAKA】WAKA iOS客户端 之二 架构设计与实现篇
【HELLO WAKA】WAKA iOS客户端 之三 创作模块分析与实现篇(上)
【HELLO WAKA】WAKA iOS客户端 之三 创作模块分析与实现篇(下)
【HELLO WAKA】WAKA iOS客户端 之四 服务器架构设计
1. iOS客户端架构
按照功能模块划分。这里可以使用二层设计也可以使用三层设计。MVC, MVCS, MVVM, MVP, VIPER, DDD, 洋葱模型等。理论补充可以自行google。
个人倾向三层设计。由于PL层使用DDD方式还没完全掌握,所以暂时使用VM来替代DDD。降级为二层设计+MVVM。
1) DAL。使用ReactiveCocoa。采用响应式编程。
2) BLL。这层使用DDD。但是还没有使用熟练,所以暂时还是使用VM来替代DDD。这样其实降级为二层设计
3) PL。使用MVVM+MVC模式。比较复杂的界面使用MVVM模式,简单界面还是使用MVC模式。

下图是按照功能结构的划分。

2. 工程结构
二层设计 + 按模块划分 + MVVM

3. DAL层之API设计
1. 库使用:AFNetworking + ReactiveCocoa + AFNetworking-RACExtensions。采用响应式编程方式。
2. 类设计。
1)使用单件模式。只通过访问MKAPIClient类来访问接口。保持接口统一访问,参数统一配置。
2)使用类扩展的方式。既保证各模块代码分类又保证了访问的统一性,并且容易横向扩展。
3) 面向函数编程。
4)面向响应编程方式。参考:http://reactivex.io
5)面向轨道编程方式(应该是非正式名称)。参考:面向轨道编程 - Swift中的异常处理

用户接口模块定义
@interface MKAPIClient (User) /**
* 用户注册
*
* @param email 邮箱
* @param password 密码
*
* @return 信号
*/
- (RACSignal *)registWithEmail:(NSString *)email password:(NSString *)password; /**
* 用户登陆
*
* @param email 邮箱
* @param password 密码
*
* @return 信号
*/
- (RACSignal *)loginWithEmail:(NSString *)email password:(NSString *)password; /**
* 忘记密码
*
* @param email 邮箱
*
* @return 信号
*/
- (RACSignal *)forgetPasswordWithEmail:(NSString *)email; /**
* 用户信息
*
* @return 信号
*/
- (RACSignal *)userInfo; /**
* 修改用户信息
*
* @param key 字段
* @param value 值
*
* @return 信号
*/
- (RACSignal *)updateUserInfoWithKey:(NSString *)key value:(NSString *)value; @end
用户模块定义
登陆接口实现
/**
* 用户登陆
*
* @param email 邮箱
* @param password 密码
*
* @return 信号
*/
- (RACSignal *)loginWithEmail:(NSString *)email password:(NSString *)password {
NSParameterAssert(email);
NSParameterAssert(password); NSDictionary *params = @{@"email" : email, @"password" : password}; @weakify(self);
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
@strongify(self);
return [[self.client rac_POST:@"/app/user/login" parameters:params] subscribeNext:^(RACTuple *x) {
NSDictionary *result = x.first; @try {
if ([result[@"code"] intValue] == ) {
NSDictionary *data = result[@"data"];
self.uid = [data[@"uid"] intValue];
self.token = data[@"token"]; [subscriber sendNext:data];
} else {
NSError *err = [NSError errorWithDomain:MKAPIClientErrorDomain code:[result[@"code"] intValue] userInfo:nil];
[subscriber sendError:err];
}
} @catch (NSException *exception) {
NSError *err = [NSError errorWithDomain:MKAPIClientErrorDomain code: userInfo:nil];
[subscriber sendError:err];
}
} error:^(NSError *error) {
[subscriber sendError:error];
} completed:^{
[subscriber sendCompleted];
}];
}] setNameWithFormat:@"%s", __FUNCTION__];
}
登陆接口实现
3. BLL - 业务逻辑层
这层还没想好怎么做比较好。暂时使用MVVM的VM来替代业务逻辑层。
4. PL - UI模块实现
主要采用MVVM模式,简单界面还是使用MVC实现。
说明:
1. 下图中的MKPublicEventItem为MKPublicEventCell的属性,不是Domain。参考:UINavigationItem设计。

2. Domain与Item关系。Item为PL层数据。
说明:MKItem为所有表现层数据的基类,提供与Domain映射的基本功能。 参考Three20的Item设计和UIView tag值设计。
1 @interface MKItem : NSObject
2
3 @property(nonatomic, weak)NSObject *weakRef;
4 @property(nonatomic, strong)NSObject *ref;
5 @property(nonatomic, strong)NSIndexPath *indexPath;
6 @property(nonatomic, assign)int tag;
7
8 @end
XXXItem只提供UI显示的数据。属于贫血模型。
1 @interface MKPublicEventItem : MKItem
2
3 @property(nonatomic, copy)NSString *title;
4 @property(nonatomic, copy)NSString *cover;
5 @property(nonatomic, copy)NSString *username;
6 @property(nonatomic, copy)NSString *publishTime;
7
8 @end
MKPublicEventItem+Event。该扩展用于从Domain创建Item方法。功能与reformer相同。参考: iOS应用架构谈 网络层设计方案
1 @implementation MKPublicEventItem (Event)
2
3
4 + (instancetype)itemWithDictionary:(NSDictionary *)event {
5 MKPublicEventItem *item = [[MKPublicEventItem alloc] init];
6 item.title = event[@"title"];
7 item.cover = event[@"firstImgUrl"];
8 item.username = event[@"author"];
9 item.publishTime = event[@"publishTime"];
10 item.ref = event;
11
12 return item;
13 }
14
15 - (NSString *)eventId {
16 return [(NSDictionary *)self.ref objectForKey:@"id"];
17 }
18
19 @end
MKPublicEventCell
说明:
1. 属性使用lazy load方式创建。
1 @interface MKPublicEventCell : UICollectionViewCell
2
3
4 @property(nonatomic, strong)MKPublicEventItem *item;
5
6 + (float)cellHeightWithWidth:(float)width;
7
8 @end
9
10
11 @interface MKPublicEventCell ()
12
13 @property(nonatomic, strong)UIImageView *imageView;
14 @property(nonatomic, strong)MKPublicEventToolbar *toolbar;
15
16 @end
17
18 @implementation MKPublicEventCell
19
20 + (float)cellHeightWithWidth:(float)width {
21 return width * 504/320 + [MKPublicEventToolbar toolbarHeight];
22 }
23
24 - (instancetype)initWithFrame:(CGRect)frame {
25 if (self = [super initWithFrame:frame]) {
26 [self setup];
27 }
28
29 return self;
30 }
31
32 - (UIImageView *)imageView {
33 if (!_imageView) {
34 UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
35 imageView.backgroundColor = [UIColor randomLightColor];
36 imageView.contentMode = UIViewContentModeScaleAspectFill;
37 imageView.clipsToBounds = YES;
38 _imageView = imageView;
39 }
40
41 return _imageView;
42 }
43
44 - (MKPublicEventToolbar *)toolbar {
45 if (!_toolbar) {
46 MKPublicEventToolbar *toolbar = [[MKPublicEventToolbar alloc] initWithFrame:CGRectZero];
47 _toolbar = toolbar;
48 }
49
50 return _toolbar;
51 }
52
53 - (void)setup {
54 [self.contentView addSubview:self.imageView];
55 [self.contentView addSubview:self.toolbar];
56 }
57
58 - (void)layoutSubviews {
59 [super layoutSubviews];
60 // h'/w' = h/w
61 self.imageView.frame = CGRectMake(0, 0, self.bounds.size.width, [MKPublicEventCell cellHeightWithWidth:self.bounds.size.width] - [MKPublicEventToolbar toolbarHeight]);
62 self.toolbar.frame = CGRectMake(0, self.imageView.bounds.size.height, self.bounds.size.width, [MKPublicEventToolbar toolbarHeight]);
63 }
64
65 - (void)setItem:(MKPublicEventItem *)item {
66 _item = item;
67
68 [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.cover] placeholderImage:nil];
69 self.toolbar.usernameLabel.text = item.username;
70 self.toolbar.titleLabel.text = item.title;
71 self.toolbar.dateLabel.text = item.publishTime;
72 }
73
74 @end
5. 单元测试
使用Specta + Expecta+ReactiveCocoa
1 SpecBegin(User)
2
3 describe(@"用户", ^{
4
5 __block MKAPIClient *client;
6 beforeAll(^{
7 client = [MKAPIClient defaultClient];
8 });
9
10 beforeEach(^{
11
12 });
13
14 context(@"当登陆", ^{
15 it(@"应该成功", ^{
16 RACSignal *signal = [client loginWithEmail:@"test@test.com" password:@"password"];
17 expect(signal).will.complete();
18 });
19 });
20
21 afterEach(^{
22
23 });
24
25 afterAll(^{
26
27 });
28 });
29
30 SpecEnd
6. 效果
周末花了2天时间做分析并且实现。
1. API层对接完毕。
2. 基础框架搭建完毕。
3. 实现热门基本UI。

7. 总结
以上为架构设计与实现。
从功能来说整体还是相对简单。
由于时间比较仓促。只实现了热门模块的部分功能。
另外,还没有对创作模块做详细分析。下篇会做更深入的了解。
【HELLO WAKA】WAKA iOS客户端 之二 架构设计与实现篇的更多相关文章
- 浅谈iOS中MVVM的架构设计与团队协作
说到架构设计和团队协作,这个对App的开发还是比较重要的.即使作为一个专业的搬砖者,前提是你这砖搬完放在哪?不只是Code有框架,其他的东西都是有框架的,比如桥梁等等神马的~在这儿就不往外扯了.一个好 ...
- IOS中 浅谈iOS中MVVM的架构设计与团队协作
今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...
- 浅谈iOS中MVVM的架构设计与团队协作【转载】
今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...
- iOS中MVVM的架构设计与团队协作
对MVVM的理解主要是借鉴于之前的用过的MVC的Web框架,之前用过ThinkPHP框架,和SSH框架,都是MVC的架构模式,今天MVVM与传统的MVC可谓是极为相似,也可以说是兄弟关系,也就是一家人 ...
- XMPP iOS客户端实现二:xcode项目配置
1.下载XMPPFramework,下载地址:https://github.com/robbiehanson/XMPPFramework 2.创建项目并将XMPP库引入: 3.添加需要的库文件: 4. ...
- 环信 之 iOS 客户端集成二:配置库
1. 添加依赖库 Build Phases → Link Binary With Libraries MobileCoreServices.framework CFNetwork.framework ...
- 高性能网站架构设计之缓存篇(1)- Redis C#客户端
一.什么 RedisREmote DIctionary Server,简称 Redis,是一个类似于Memcached的Key-Value存储系统.相比Memcached,它支持更丰富的数据结构,包括 ...
- 高性能网站架构设计之缓存篇(2)- Redis C#客户端
在上一篇中我简单的介绍了如何利用redis自带的客户端连接server并执行命令来操作它,但是如何在我们做的项目或产品中操作这个强大的内存数据库呢?首先我们来了解一下redis的原理吧. 官方文档上是 ...
- jquery源码分析(二)——架构设计
要学习一个库首先的理清它整体架构: 1.jQuery源码大致架构如下:(基于 jQuery 1.11 版本,共计8829行源码)(21,94) 定义了一些变量和函数jQu ...
随机推荐
- 编程利用利用curses库编程开始
时间紧张,先记一笔,后续优化与完善. curses库常用函数: 注意编译时要用这样的格式:gcc xxx.c -l curses -o xxx 第一个小例子: include <stdio.h& ...
- Android Apk获取包名和Activity名称
一.使用aapt(Android Asset Packaging Tool)工具获取: 1.配置Android环境: a.添加build-tools/android路径到系统环境变量的中Path中,注 ...
- Java开发工具箱-JDK的安装与配置
一.JDK.JRE 术语名 缩写 解释 Java Development Kit JDK Java程序员用的工具包 Java Runtime Enviroment JRE Java程序的运行环境 二. ...
- 年末整理git和svn的使用心得
实习加毕业工作也一年多了,用过svn 也用过git,现在也是两种版本管理工具交替不同的项目再用. 趁年末放假之际,来梳理下. 对于SVN常用命令: .svn cp svn-trunk地址 svn-br ...
- 【转】关于JVM CPU资源占用过高的问题排查
http://my.oschina.net/shipley/blog/520062 一.背景: 先执行一个java程序里面开了两个线程分别都在while循环做打印操作. ? 1 # java -cp ...
- 批处理+组策略 实现规定时间段无法开机and定时关机
某爱熬夜的人对付自己的东西 1.shutdown命令 shutdown -a #取消现有的shutdown计划 shutdown -s -t [time] #设定时间关机 shutdown -r -t ...
- 同时操作两个数据库:报错Illegal attempt to associate a collection with two open sessions
今天我在一个操作两个数据库的SSH里 同时插入1条数据 报错 Illegal attempt to associate a collection with two open sessions 在这里有 ...
- CUDA学习ing..
0.引言 本文记载了CUDA的学习过程~刚开始接触GPU相关的东西,包括图形.计算.并行处理模式等,先从概念性的东西入手,然后结合实践开始学习.CUDA感觉没有一种权威性的书籍,开发工具变动也比较快, ...
- 安装Genymotion与集成eclipse,最后有集成android studio
本安装过程从不用到VPN 一切国内网络都可以解决. 首先下载Genymotion,网址 https://www.genymotion.com/account/login/ 首先需要注册,我使用163 ...
- quagga源码分析--通用库command
quagga作为一个路由器软件,自然要提供人机接口. quagga提供snmp管理接口,而且,自然就会有对应的命令行管理格式,当然一般路由软件不会提供界面形式的,也许有webui,然而quagga并没 ...