上一篇主要做了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. 学习OpenCV:滤镜系列(15)——羽化(模糊边缘)

    ============================================== 版权所有:小熊不去实验室CSDN博客 ================================== ...

  2. angularjs控制器之间通信,事件通知服务

    service要记住一点就是所有的services都是singleton(单例)的,service更多的是做一些业务逻辑,数据交互.当然,利用单例这特点也可以用来做不同控制器间的通信.控制器间的通信也 ...

  3. 结构-行为-样式-angularJs笔记

    0.关于Ng-app   通过ngApp指令来引导Angularjs应用是一种简洁的方式 ,适合大多数情况.在高级开发中,例如使用脚本装载应用,您也可以使用Bootstrap手动引导AngularJs ...

  4. 点击某一按钮新增click,并切换页面

    应用场景:对于web端接收手机验证码的处理方法:1.如果有权限可以通过查询数据库来获得手机验证码,方便快捷.2.如果后台系统保存了手机验证码,可以去后台获取验证码,然后填写到前台页面,此方法有两种处理 ...

  5. 典型关联分析(CCA)原理总结

    典型关联分析(Canonical Correlation Analysis,以下简称CCA)是最常用的挖掘数据关联关系的算法之一.比如我们拿到两组数据,第一组是人身高和体重的数据,第二组是对应的跑步能 ...

  6. 【APEX初步】【2】【sObjects与数据库】

    由于apex是与数据库集成的.我们可以直接用apex访问数据库.每条记录就是一个SObject对象

  7. 果园种植系统开发App,游戏+商业模式?

    果园种植全返系统开发,英伦果园开发,微信果园种植系统开发,百果生态乐园开发,淘金农夫开发,农场果园种植游戏系统,果园种植APP系统开发,果园种植软件开发找陈牧150-1315-1740(微/电)开发者 ...

  8. 360回归A股,周鸿祎来给BAT和小米添堵了

    排着队回国内上市的公司名单里,终于多了奇虎360的名字. 奇虎360今天宣布,公司董事会收到了来自董事长周鸿祎.中信证券及其附属公司.Golden Brick Capital Private Equi ...

  9. SQL注入(四)

    参数绑定(预编译语句) 虽然数据库自带的过滤是个不错的实现,但是我们还是处在“用户输入被当成 SQL语句的一部分 ”这么个圈子里,其实要跳出这个圈子还有一个实现,就是参数绑定.基本上所有的主流数据库都 ...

  10. Linux下网卡BCM4313的安装

    我遇到的问题:打开网络管理->wifi 显示固件缺失 通过: 1 lspci | grep Wireless 显示: 1 08:00.0 Network controller: Broadcom ...