转载

MAKA - iOS版本分析与模仿实现

Maka上周三发布了iOS客户端1.0。于是做了竞品分析与模仿实现。

1. 功能结构分解

工具:iPhone,MindNode。

以下是主要的功能结构分解。从功能与模块来看,实现比较完整,工作量分布也比较平衡。非重要模块都采用最简单的实现方式。

MAKA - iOS版本分析与模仿实现

2. API分析

工具:Charles Proxy

以下是主要的API。

模块

名称

URL

Method

说明

用户

注册

/app/user/register

POST

http://api.maka.im

登陆

/app/user/login

POST

忘记密码

/app/user/forgetpassword

POST

用户信息

/app/user/{$user_id}

GET

修改用户信息

/app/user/{$user_id}

PUT

事件

我的事件列表

/app/events

GET

创建

/app/event

POST

更新

/app/event/{$event_id}

PUT

发布

/app/event/{$event_id}

POST

创作

主分类

/app/specialCategories

GET

模板分类

/app/templates

GET

模板页

/app/template/{$template_id}

GET

图集分类

/app/pictureIndex

GET

图片列表

/app/pictures

GET

热门

公开事件

/app/publicEvents

GET

分类

/app/tagCategories

GET

3. 架构分析

通过数据分析,与客户端通信的主要服务器有

MAKA - iOS版本分析与模仿实现

4. 模仿实现

4.1 iOS架构

按照功能模块划分。

MAKA - iOS版本分析与模仿实现

4.2 工程结构

二层设计 + 按模块划分 + MVVM

MAKA - iOS版本分析与模仿实现

4.3 API层设计

使用网络库:AFNetworking-RACExtensions。

使用单件模式 + OC扩展的方式实现接口的模块划分。采用响应式编程方式。显式参数+信号返回。

MAKA - iOS版本分析与模仿实现

用户模块接口定义

 1 @interface MKAPIClient (User)  2   3 /**  4  *  用户注册  5  *  6  *  @param email    邮箱  7  *  @param password 密码  8  *  9  *  @return 信号 10  */ 11 - (RACSignal *)registWithEmail:(NSString *)email password:(NSString *)password; 12  13  14 /** 15  *  用户登陆 16  * 17  *  @param email    邮箱 18  *  @param password 密码 19  * 20  *  @return 信号 21  */ 22 - (RACSignal *)loginWithEmail:(NSString *)email password:(NSString *)password; 23  24  25 /** 26  *  忘记密码 27  * 28  *  @param email 邮箱 29  * 30  *  @return 信号 31  */ 32 - (RACSignal *)forgetPasswordWithEmail:(NSString *)email; 33  34  35 /** 36  *  用户信息 37  * 38  *  @return 信号 39  */ 40 - (RACSignal *)userInfo; 41  42 /** 43  *  修改用户信息 44  * 45  *  @param key   字段 46  *  @param value 值 47  * 48  *  @return 信号 49  */ 50 - (RACSignal *)updateUserInfoWithKey:(NSString *)key value:(NSString *)value; 51  52 @end

eg. 登陆接口实现

 1 /**  2  *  用户登陆  3  *  4  *  @param email    邮箱  5  *  @param password 密码  6  *  7  *  @return 信号  8  */  9 - (RACSignal *)loginWithEmail:(NSString *)email password:(NSString *)password { 10     NSParameterAssert(email); 11     NSParameterAssert(password); 12      13     NSDictionary *params = @{@"email" : email, @"password" : password}; 14      15     @weakify(self); 16     return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 17         @strongify(self); 18         return [[self.client rac_POST:@"/app/user/login" parameters:params] subscribeNext:^(RACTuple *x) { 19             NSDictionary *result = x.first; 20              21             @try { 22                 if ([result[@"code"] intValue] == 200) { 23                     NSDictionary *data = result[@"data"]; 24                     self.uid = [data[@"uid"] intValue]; 25                     self.token = data[@"token"]; 26                      27                     [subscriber sendNext:data]; 28                 } else { 29                     NSError *err = [NSError errorWithDomain:MKAPIClientErrorDomain code:[result[@"code"] intValue] userInfo:nil]; 30                     [subscriber sendError:err]; 31                 } 32             } @catch (NSException *exception) { 33                 NSError *err = [NSError errorWithDomain:MKAPIClientErrorDomain code:10001 userInfo:nil]; 34                 [subscriber sendError:err]; 35             } 36         } error:^(NSError *error) { 37             [subscriber sendError:error]; 38         } completed:^{ 39             [subscriber sendCompleted]; 40         }]; 41     }] setNameWithFormat:@"%s", __FUNCTION__]; 42 }

4.3 业务逻辑层

这层还没想好怎么做比较好。暂时使用MVVM的VM来替代业务逻辑层。

4.4 UI层实现

主要采用MVVM模式,简单界面还是使用MVC实现。

说明:

1. 下图中的MKPublicEventItem为MKPublicEventCell的属性,不是Domain。参考:UINavigationItem设计。

MAKA - iOS版本分析与模仿实现

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。

MAKA - iOS版本分析与模仿实现

7. 总结

由于时间比较仓促。还没有对创作模块做详细分析。后续在补上。

正文到此结束
Loading...