上一篇主要做了MAKA APP的需求分析,功能结构分解,架构分析,API分析,API数据结构分析。
这篇主要讲如何从零做iOS应用架构。
【HELLO MAKA】MAKA iOS客户端 之一 APP分析篇
【HELLO MAKA】MAKA iOS客户端 之二 架构设计与实现篇
【HELLO MAKA】MAKA 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中的异常处理
用户接口模块定义
登陆接口实现
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 : NSObject2 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 : MKItem2 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 @end17 18 @implementation MKPublicEventCell19 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/w61 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. 总结
以上为架构设计与实现。
从功能来说整体还是相对简单。
由于时间比较仓促。只实现了热门模块的部分功能。
另外,还没有对创作模块做详细分析。下篇会做更深入的了解。
联系客服