上一篇主要做了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客户端 之二 架构设计与实现篇的更多相关文章

  1. 浅谈iOS中MVVM的架构设计与团队协作

    说到架构设计和团队协作,这个对App的开发还是比较重要的.即使作为一个专业的搬砖者,前提是你这砖搬完放在哪?不只是Code有框架,其他的东西都是有框架的,比如桥梁等等神马的~在这儿就不往外扯了.一个好 ...

  2. IOS中 浅谈iOS中MVVM的架构设计与团队协作

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  3. 浅谈iOS中MVVM的架构设计与团队协作【转载】

    今天写这篇文章是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇文章的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

  4. iOS中MVVM的架构设计与团队协作

    对MVVM的理解主要是借鉴于之前的用过的MVC的Web框架,之前用过ThinkPHP框架,和SSH框架,都是MVC的架构模式,今天MVVM与传统的MVC可谓是极为相似,也可以说是兄弟关系,也就是一家人 ...

  5. XMPP iOS客户端实现二:xcode项目配置

    1.下载XMPPFramework,下载地址:https://github.com/robbiehanson/XMPPFramework 2.创建项目并将XMPP库引入: 3.添加需要的库文件: 4. ...

  6. 环信 之 iOS 客户端集成二:配置库

    1. 添加依赖库 Build Phases → Link Binary With Libraries MobileCoreServices.framework CFNetwork.framework ...

  7. 高性能网站架构设计之缓存篇(1)- Redis C#客户端

    一.什么 RedisREmote DIctionary Server,简称 Redis,是一个类似于Memcached的Key-Value存储系统.相比Memcached,它支持更丰富的数据结构,包括 ...

  8. 高性能网站架构设计之缓存篇(2)- Redis C#客户端

    在上一篇中我简单的介绍了如何利用redis自带的客户端连接server并执行命令来操作它,但是如何在我们做的项目或产品中操作这个强大的内存数据库呢?首先我们来了解一下redis的原理吧. 官方文档上是 ...

  9. jquery源码分析(二)——架构设计

    要学习一个库首先的理清它整体架构: 1.jQuery源码大致架构如下:(基于 jQuery 1.11 版本,共计8829行源码)(21,94)                定义了一些变量和函数jQu ...

随机推荐

  1. C++中#和##的特殊使用

    1.用#号将输入的内容转换为字符串. 用##号将两个参数合并. #include <iostream> using namespace std; //将输入的内容转换成字符串 #defin ...

  2. (十一)if...else&for循环&while循环

    ----------------------------------if else------------------------------1.最基本的if语句:if name =="Al ...

  3. scapy流量嗅探简单使用

    官方文档:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html 参考链接:http://blog.csdn.net/Jeanphorn/ar ...

  4. javascript实现页面右侧在线客服始终跟随鼠标滚动而上下滚动且始终位于屏幕中间

    效果如图,右侧的联系一栏始终位于页面的中间位置,且随着页面的上下滚动而滚动跟随 css的话没什么好说的,看图 代码 window.onload=window.onresize=window.onscr ...

  5. getResourceAsStream和getResource的用法

    用JAVA获取文件,听似简单,但对于很多像我这样的新人来说,还是掌握颇浅,用起来感觉颇深,大家最经常用的,就是用JAVA的File类,如要取得 D:/test.txt文件,就会这样用File file ...

  6. mysql数据库主从搭建

    一.最近一直在学习mysql的东西,刚好看到mysql如何搭建主从数据库,搜集了很多资料后大致了解了mysql主从复置的原理.以下是我的理解: 举例master为主数据库,slave为从数据库. sl ...

  7. B树、B-树、B+树、B*树详解

    注:本文为个人学习摘录,原文地址:http://www.blogjava.net/supercrsky/articles/185167.html B树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿 ...

  8. ios在项目中打开word文档、ppt等总结

    最近在项目开发中遇到下载附件文档预览需求,在这里总结一下我的实现方法,本文最后会附带我写的demo下载地址 这里我总结了三种实现方法(1)用webView预览(2)通过UIDocumentIntera ...

  9. Linux Curl常用命令使用【转】

    Curl是Linux下一个很强大的http命令行工具,其功能十分强大. 1)读取网页 $ curl linuxidc.com">http://www.linuxidc.com 2)保存 ...

  10. 查找页面中最大的z-index 的值

    var divs = document.getElementsByTagName("div");for(var i=0, max=0; i<divs.length; i++) ...