记录开发认知的一步步。
第一步,开始接触开发也没什么模式,就是 model view control 散落一地,模块分下文件夹,当时大部分项目起步都是这样。
第二步来到这边接手项目,因为交接的人是做过7年的后台,所以对代码的分层很看重。然后我也就跟着习惯了 API-service-control model view 的分层方式,感觉好爽,这种做小规模的APP还是很可行的。
第三步,看到大神写的文章都在强调代码之间的耦合性,以及各种组件化,就想尝试下。自己实现了一套 function-business-main 模式,也就是底层function是实现各个功能的工具层,业务层business调用function层各种工具实现具体业务,最后由 main 层组合各个业务最终实现完整的应用,各个模块之间的通信通过一个中间者去调用,配合OC 的runtime特性通信很easy。中间也尝试过pod库的方式实现组件化,无奈一个人玩太麻烦,没坚持下来放弃了。
casa 大神博客地址
现在的项目结构
BaseKit 系统的基础工具库(基础数据提供)
Function 提供各种功能性工具,比如分享、推送、各种常用扩展等等,
Business 组合各种功能性工具实现各个业务模块 具体的模块实现使用MVC 还是 MVVM 对整体没有影响。
Main 就是组合各个业务模块,为应用提供一个完整的入口
具体开发实现
1.确定一些原则
1.1 去除项目对基类的依赖,所有的功能可以通过 Category 的方式进行实现 , 例如实现指示器的功能,在需要的地方引入 category 即可。
//
// UIViewController+YMHUD.h
// YYDApp
//
// Created by YM on 16/8/1.
// Copyright © 2016年 YM. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController (YMHUD)
- (void)showHUD;
- (void)showHUDInView:(UIView *)view;
- (void)hideHUD;
- (void)hideHUDForView:(UIView *)view;
- (void)showErrMsg:(NSString *)errmsg;
- (void)showFastToastHUD:(NSString *)msg;
- (void)showFastToastHUD:(NSString *)msg complite:(void(^)())complite;
@end
使用
#import "UIViewController+YMHUD.h"
- (void)appScanHandlerStart:(TJAPPScanHandler *)handler{
[self showHUDInView:self.discernCodeView];
}
1.2 确定代码编写结构 例如Controller
@interface MSatisticVC ()<APIManagerCallBackDelegate>
@property (nonatomic,strong) MSatisticViewModel *vm;
@end
@implementation MSatisticVC
- (void)viewDidLoad {
[super viewDidLoad];
[self prepareForUI];
}
#pragma mark - Event
- (void)rightBtnClick:(id)sender{}
#pragma mark - Delegate
- (void)apiManagerCallBackDidFailed:(BaseAPIManager *)manager{}
- (void)apiManagerCallBackDidSuccess:(BaseAPIManager *)manager{}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {}
#pragma mark - Private Method
- (void)prepareForUI{}
#pragma mark - Publich Method
#pragma mark - Getter and Setter
- (MSatisticViewModel *)vm{
if (!_vm) {
_vm = [[MSatisticViewModel alloc] init];
}
return _vm;
}
@end
1.3 使用 cocoapods 进行库管理,在进行 OC swift 混编到时候发现一个小技巧,使用 pod 管理 OC 的库,使用 Carthage 管理 swift 的库,这样省了很多事,对于我一个人搞的项目来说还是很方便的。
1.4 布局使用 Storyboard + Masonry , 这个看个人喜好
1.5 确定模块化、组件化开发。主要针对业务层模块规定,禁止同层直接调用,杜绝耦合其他业务模块代码,解决方案为调用调度者模块(思路介绍来自 casa 大神系列教程),A 业务模块编写一个向外部暴露的方法供其他模块的调用,B 业务模块就可以通过调度者去调用 A 暴露的方法。(也有一种方案是蘑菇街通过路由的方式实现的组件件调用,受大神影响,觉得使用 Mediator 的方式更适合我现在的应用。)具体实现:
订单模块向外暴露方法
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TargetOrder : NSObject
/**
* 获取订单支付页面
*
* @param orderID 订单ID
*
* @return 订单支付页面
*/
- (UIViewController *)ActionFetchStidumOrderPayVC:(NSDictionary *)orderDic;
调度者模块编写 category,通过运行时调用订单模块的方法
@implementation YMMediator (OrderActions)
- (UIViewController *)MediatorViewControllerForStidumOrderPayVC:(NSString *)orderID{
NSAssert(orderID != nil, @"请检查订单号");
UIViewController *vc = [self performTarget:kMediatorTargetOrder
action:ActionFetchStidumOrderPayVC
params:@{@"orderID":orderID}];
if ([vc isKindOfClass:[UIViewController class]]) {
return vc;
}else{
//异常
return [[UIViewController alloc] init];
}
}
-------分割--------------
YMMediator 调度者的关键代码
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params
{
NSString *targetClassString = [NSString stringWithFormat:@"Target%@", targetName];
NSString *actionString = [NSString stringWithFormat:@"Action%@:", actionName];
Class targetClass = NSClassFromString(targetClassString);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
return nil;
}
if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
return nil;
}
}
}
其他模块调用 支付模块的 支付界面
- (void)goPayVC:(NSString *)orderID{
if (orderID == nil || orderID.length <1) {
[self showFastToastHUD:@"订单号不能为空"];
return;
}
UIViewController *vc = [[YMMediator sharedInstance] MediatorViewControllerForStidumOrderPayVC:orderID];
[self.navigationController pushViewController:vc animated:YES];
}
2.网络数据请求方式
2.1 主要是为了对网络请求的过程提供尽可能多的监控感知,通过切片设计对网络请求过程添加想要的处理函数,可以通过代理的方式实现,也可以通过 block 方式实现。封装属于自己的一套网络请求,切勿过于依赖某一种网络库
/************************************************
APIManagerCallBackDelegate
回调协议
************************************************/
@protocol APIManagerCallBackDelegate <NSObject>
@required
- (void)apiManagerCallBackDidSuccess:(BaseAPIManager *)manager;
- (void)apiManagerCallBackDidFailed:(BaseAPIManager *)manager;
@end
/************************************************
APIManagerCallBackDateReformer
DateReformer协议
************************************************/
@protocol APIManagerCallBackDateReformer <NSObject>
@required
- (id)manager:(BaseAPIManager *)manager reformData:(NSObject *)data;
@end
/************************************************
APIManagerParamSourceDelegate
参数源
************************************************/
@protocol APIManagerParamSourceDelegate <NSObject>
@required
- (NSDictionary *)paramsForAPi:(BaseAPIManager *)manager;
- (NSDictionary *)headerFieldsForAPi:(BaseAPIManager *)manager;
@end
/************************************************
APIManagerValidator
验证协议
************************************************/
@protocol APIManagerValidator <NSObject>
@required
- (BOOL)manager:(BaseAPIManager *)manager isCorrectWithCallBackData:(id)data;
- (BOOL)manager:(BaseAPIManager *)manager isCorrectWithParamsData:(NSDictionary *)data;
@end
/************************************************
APIManagerInterceptor
拦截器协议
************************************************/
@protocol APIManagerInterceptor <NSObject>
@optional
- (void)manager:(BaseAPIManager *)manager beforePerformSuccessWithResponse:(APIURLResponse *)response;
- (void)manager:(BaseAPIManager *)manager afterPerformSuccessWithResponse:(APIURLResponse *)response;
- (void)manager:(BaseAPIManager *)manager beforePerformFailWithResponse:(APIURLResponse *)response;
- (void)manager:(BaseAPIManager *)manager afterPerformFailWithResponse:(APIURLResponse *)response;
- (BOOL)manager:(BaseAPIManager *)manager shouldCallAPIWithParams:(NSDictionary *)params;
- (void)manager:(BaseAPIManager *)manager afterCallingAPIWithParams:(NSDictionary *)params;
@end
3.引入单元测试
3.1 观看过李智维的自动化测试直播后,学习了一下单元测试接入,通过查找资料也找到了他简单封装的测试工具宏,非常好用。
//
// YYDBaseTest.h
// YaoYD
//
// Created by TJ on 2016/11/28.
// Copyright © 2016年 oneyd.me. All rights reserved.
//
#import <XCTest/XCTest.h>
#define assertTrue(expr) XCTAssertTrue((expr), @"")
#define assertFalse(expr) XCTAssertFalse((expr), @"")
#define assertNil(a1) XCTAssertNil((a1), @"")
#define assertNotNil(a1) XCTAssertNotNil((a1), @"")
#define assertEqual(a1, a2) XCTAssertEqual((a1), (a2), @"")
#define assertEqualObjects(a1, a2) XCTAssertEqualObjects((a1), (a2), @"")
#define assertNotEqual(a1, a2) XCTAssertNotEqual((a1), (a2), @"")
#define assertNotEqualObjects(a1, a2) XCTAssertNotEqualObjects((a1), (a2), @"")
#define assertAccuracy(a1, a2, acc) XCTAssertEqualWithAccuracy((a1),(a2),(acc))
#define WAIT \
do { \
[self expectationForNotification:@"LCUnitTest" object:nil handler:nil]; \
[self waitForExpectationsWithTimeout:60 handler:nil]; \
} while(0);
#define NOTIFY \
do { \
[[NSNotificationCenter defaultCenter] postNotificationName:@"LCUnitTest" object:nil]; \
} while(0);
@interface YYDBaseTest : XCTestCase
- (void)loginBeforSetup;
@end
-----------
#import "UserServiceDTO.h"
@implementation YYDBaseTest
- (void)loginBeforSetup{
NSDictionary *parm = @{@"username" : @"platform",
@"password" : @"tianjian.123",
@"grant_type": @"password"};
[[UserServiceDTO shareInstance] loginWithParams:parm completeBlock:^(BOOL isSuccess , NSString *errmsg) {
assertTrue(isSuccess);
NOTIFY
}];
WAIT
}
@end
使用
@interface AssetsAPITests : YYDBaseTest
@end
@implementation AssetsAPITests
- (void)setUp {
[super setUp];
// [self loginBeforSetup];
}
- (void)testPingAlipay{
NSString *orderInfo = @"snzitUjE%2FoURBVLXluOVheOmv8R%2Fjt4cB6neqXDONLArHtrQ%3D";
NSDictionary *charge = @{@"credential":@{
@"alipay":@{
@"orderInfo":orderInfo,
}
}
};
[Pingpp createPayment:charge appURLScheme:@"TJUrl" withCompletion:^(NSString *result, PingppError *error) {
NOTIFY
}];
WAIT
}
- (void)testRequest{
NSDictionary *params = @{
@"outTradeNo":@"222333",
@"refundNo":@"088788",
@"refundTotalAmount":@"1",
@"actualAmount":@"1",
@"reason":@"订单退款",
@"notifyUrl":@"https://www.baidu.com",
@"tradeType":@"APP"
};
NSMutableURLRequest *request = [[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:urlString parameters:params error:nil];
[[TJNetService shareInstance] baseRequest:request success:^(id result) {
NOTIFY
} failure:^(id result) {
NOTIFY
}];
WAIT
}
以上是这一段时间对项目架构的思考。
最近看了一个视频讲移动应用架构主要就两个部分,数据+UI,觉得也是挺有道理的,待慢慢沉淀多参悟这种简单的事情吧。