上一篇主要做了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. JavaScript中的call 和apply的用途以及区别

    apply 接受两个参数,第一个参数指定了函数体内this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数: ...

  2. java String源码学习

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ...

  3. NodeJs+Express实现简单的Web增删改查

    前一段时间,公司组织了一次NodeJs的技术分享,自己有幸去听了听,第一次接触NodeJs,后来经过自己学习和探索,完成了一个很简单的Web演示项目,在这里和初学者做以分享,开发工具:WebStorm ...

  4. mybatis 入门进阶之 mapper

    由于上节 <mybatis 入门优化>中的dao实现类耦合了user.xml中的statment的id,例如:src.main.resource.userMapper.findUserBy ...

  5. [ios2]判断retina 屏幕

    判断是否retina屏幕 #define isRetina ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSize ...

  6. Mybatis学习笔记(二) 之实现数据库的增删改查

    开发环境搭建 mybatis 的开发环境搭建,选择: eclipse j2ee 版本,mysql 5.1 ,jdk 1.7,mybatis3.2.0.jar包.这些软件工具均可以到各自的官方网站上下载 ...

  7. 在VirtualBox上安装CentOS7

    文章的出处:http://jingyan.baidu.com/article/9c69d48f8ec01613c8024e58.html 工具: VirtualBox-5.1.2-108956-Win ...

  8. 清浮动,防止上下margin重叠(浏览器顶部空白崩溃)

    清浮动 父级添加类别! .clearfix{zoom:1;//兼容ie6,7} .clearfix:after{ content:"."; display: "block ...

  9. git stash让bug来的更猛烈些吧

    git stash可以用来暂存当前正在进行的工作,比如想pull最新的代码,又不想加新commit, 或者有一个紧急的bug需要修复,但是这个bug又与你已经在做的工作(还没完成)有关联.这个时候有的 ...

  10. <hdu - 1863> 畅通工程 并查集和最小生成树问题

    本题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1863  Problem Description: 省政府“畅通工程”的目标是使全省任何两个村庄间都可以 ...