【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 ...
随机推荐
- C++ 全局构造函数调用的顺序
C++的全局类和静态类的构造函数是在main函数之前调用的.但是,不同的类的构造函数以什么顺序调用呢? 对于g++编译器来说,这个顺序是由链接时,文件顺序决定的. 我们用一个例子来说明这一点. 我们有 ...
- 如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?
我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供了扩展点使我们可以与第三方DI框架进行整合.对此比较了解的读 ...
- 用vue实现简单实时汇率计算功能
最近在自己摸索vue的使用,因为相对于只是去看教程和实例,感觉不如自己动手写一个demo入门来的快.刚好看到小程序中有一个简单但是很精致的应用极简汇率,而且它的表现形式和vue的表现形式很像,于是想着 ...
- [bzoj2957][楼房重建] (线段树)
Description 小A的楼房外有一大片施工工地,工地上有N栋待建的楼房.每天,这片工地上的房子拆了又建.建了又拆.他经常无聊地看着窗外发呆,数自己能够看到多少栋房子. 为了简化问题,我们考虑这些 ...
- 【转】关于MySQL函数GROUP_CONCAT的使用
GROUP_CONCAT()是MySQL数据库提供的一个函数,通常跟GROUP BY一起用,具体可参考MySQL官方文挡:http://dev.mysql.com/doc/refman/5.0/en/ ...
- Windows/Linux 生成iOS证书及p12文件
操作步骤 生成csr文件(通过OpenSSL命令) 生成mobileprovision文件(通过Apple开发者后台) 生成cer文件(通过Apple开发者后台) 生成P12文件(通过OpenSSL命 ...
- 论JSON的重要性☞异步上传过程中data取多组值的方法
异步上传取多组值一起post改变属性 如图: 看图说话
- CHD 2015迎新杯题解
A.预防流感的拉面女神 简析:计算 n 的二进制表示里面 1 的个数 #include <cstdio> #include <cstring> #include <alg ...
- 第17章 使用PHP和MySQL实现身份验证
1.对密码进行加密:sha1(string str,bool raw_output) //将返回一个40个字符的伪随机字符串,若raw_output为true,着得到一个20个字符的二进制字符串数据 ...
- 更新UI
//1. this.Invoke(new ThreadStart(delegate { textBox1.AppendText(" + "\r\n"); })); //2 ...