在面向对象编程中,常用的就是抽象和封装,今天来完成Model,ViewModel,ViewController的编写。
为了更好查看日志信息,我们封装了一些宏定义,取代了NSLog,让日志信息更方便我们调试
新建Defineds.h
#if (DEBUG || TESTCASE)
#define Log(format, ...) NSLog(format, ## __VA_ARGS__)
#else
#define Log(format, ...)
#endif
// 日志输出宏
#define BASE_LOG(cls,sel) Log(@"%@-%@",NSStringFromClass(cls), NSStringFromSelector(sel))
#define BASE_ERROR_LOG(cls,sel,error) Log(@"ERROR:%@-%@-%@",NSStringFromClass(cls), NSStringFromSelector(sel), error)
#define BASE_INFO_LOG(cls,sel,info) Log(@"INFO:%@-%@-%@",NSStringFromClass(cls), NSStringFromSelector(sel), info)
// 日志输出函数
#if (DEBUG || TESTCASE)
#define BASE_LOG_FUN() BASE_LOG([self class], _cmd)
#define BASE_ERROR_FUN(error) BASE_ERROR_LOG([self class],_cmd,error)
#define BASE_INFO_FUN(info) BASE_INFO_LOG([self class],_cmd,info)
#else
#define BASE_LOG_FUN()
#define BASE_ERROR_FUN(error)
#define BASE_INFO_FUN(info)
#endif
在本项目中,Model命名规则都以”对象名称”+Info来命名,我们编写Model基类,新建BaseInfo类
BaseInfo.h
#import <Foundation/Foundation.h>
@interface BaseInfo : NSObject
@property(nonatomic,strong) NSString *ID;
@property(nonatomic,strong) NSString *name;
/// 初始化方法
-(instancetype) initWithDict:(NSDictionary *) dict;
/// 初始化方法
+(instancetype) infoWithDict:(NSDictionary *) dict;
/// 字典转NSArray
+(NSArray *) arrayFromDict:(NSDictionary *) dict;
/// 将数组转成对象数组
+(NSArray *) arrayFromArray:(NSArray *) array;
@end
BaseInfo.m
#import "BaseInfo.h"
@implementation BaseInfo
-(instancetype) initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
self.ID = [dict objectForKey:@"id"];
self.name = [dict objectForKey:@"name"];
//[self setValuesForKeysWithDictionary:dict];简洁写法
}
return self;
}
+(instancetype) infoWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
+(NSArray *) arrayFromDict:(NSDictionary *) dict
{
NSArray *array = [dict objectForKey:NetData];
return [self arrayFromArray:array];
}
+(NSArray *) arrayFromArray:(NSArray *) array
{
NSMutableArray *infos = [[NSMutableArray alloc] init];
for (NSDictionary *dict in array) {
[infos addObject:[self infoWithDict:dict]];
}
if(infos.count == 0){
return nil;
}
return infos;
}
@end
Model一般是负责数据传输,充当DTO的角色,它往往是请求服务器接口的返回结果的封装,假设我们服务器的程序接口返回的数据包含ID和name参数,那么我们定义类时,也同样定义2个属性ID和name。
在BaseInfo中,定义了以NSDictionary初始化对象的方法infoWithDict,arrayFromDict,也是OC中常用的初始化对象方法之一。
ViewModel负责View和Model间的数据转换,常用的业务操作等。我们在这里定义它是的使命是负责网络请求,并把返回结果封装成Model类。
定义BaseOpertation类
BaseOpertation.h
#import <Foundation/Foundation.h>
@protocol BaseOperationDelegate;
@interface BaseOperation : NSObject{//声明变量,不能通过点语法设置和获取变量
id<BaseOperationDelegate> _delegate;
NSURLConnection *_connection;
NSMutableData *_receiveData;//NSMutableData可变的字节操作类
NSInteger _statusCode;
long long _totalLength;//双长整型
//默认是@protected
@public NSDictionary *_opInfo;
}
-(id)initWithDelegate:(id<BaseOperationDelegate>) delegate opInfo:(NSDictionary *) opInfo;
-(NSMutableURLRequest *) urlRequest;
- (void)executeOp;
- (void)cancelOp;
- (void)parseData:(id)data;
- (void)parseSuccess:(NSDictionary *)dict jsonString:(NSString *)jsonString;
- (void)parseFail:(id)dict;
- (void)parseProgress:(long long)receivedLength;
- (NSTimeInterval)timeoutInterval;
@end
@protocol BaseOperationDelegate <NSObject>
-(void)opSuccess:(id)data;
-(void)opFail:(NSString *) errorMsg;
@optional
- (void)opSuccessEx:(id)data opinfo:(NSDictionary *)dictInfo;
- (void)opFailEx:(NSString *)errorMessage opinfo:(NSDictionary *)dictInfo;
- (void)opSuccessMatch:(id)data;
- (void)opUploadSuccess;
@end
BaseOperation.m
#import "BaseOperation.h"
@implementation BaseOperation
-(id)initWithDelegate:(id<BaseOperationDelegate>)delegate opInfo:(NSDictionary *)opInfo
{
if(self = [super init]){
_delegate = delegate;
_opInfo = opInfo;
_totalLength = 0;
}
return self;
}
- (void)cancelOp
{
if (_connection != nil) {
BASE_INFO_FUN(@"_connection dealloc cancel");
[_connection cancel];
}
_connection = nil;
}
- (void)dealloc
{
if (_connection != nil) {
BASE_INFO_FUN(@"_connection dealloc cancel");
[_connection cancel];
}
_connection = nil;
_delegate = nil;
}
- (NSTimeInterval)timeoutInterval
{
return FxRequestTimeout;
}
-(NSMutableURLRequest *)urlRequest
{
NSString *urlString = [_opInfo objectForKey:@"url"];
BASE_INFO_FUN(urlString);
id body = [_opInfo objectForKey:@"body"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (body != nil) {
[request setHTTPMethod:HTTPPOST];
if ([body isKindOfClass:[NSString class]]) {
[request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
}else{
[request setHTTPBody:body];
}
}else{
[request setHTTPMethod:HTTPGET];
}
return request;
}
//使用delegate异步发送请求
- (void)executeOp
{
_connection = [[NSURLConnection alloc] initWithRequest:[self urlRequest] delegate:self];
}
- (void)parseData:(NSData *)data
{
if (data.length == 0) {
[self parseSuccess:nil jsonString:nil];
return;
}
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSMutableDictionary *dict = [JsonUtility jsonValueFromString:jsonString];
NSString *result = [dict objectForKey:NetResult];
if ([result isEqualToString:NetOk]) {
[self parseSuccess:dict jsonString:jsonString];
}else{
[self parseFail:dict];
}
_receiveData = nil;
}
- (void)parseSuccess:(NSDictionary *)dict jsonString:(NSString *)jsonString
{
[_delegate opSuccess:dict];
}
- (void)parseFail:(id)dict
{
if ([dict isKindOfClass:[NSString class]]) {
[_delegate opFail:(NSString *)dict];
return;
}
if ([[dict objectForKey:NetResult] isEqualToString:NetInvalidateToken]) {
BASE_ERROR_FUN(NetInvalidateToken);
}
[_delegate opFail:[dict objectForKey:NetMessage]];
}
- (void)parseProgress:(long long)receivedLength
{
}
#pragma mark NSURLConnectionDelegage methods
// 收到回应
- (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSURLResponse *)aResponse
{
NSHTTPURLResponse *response = (NSHTTPURLResponse *)aResponse;
NSString *statusCode = [NSString stringWithFormat:@"%ld",(long)[response statusCode]];
_statusCode = [statusCode intValue];
_receiveData = [[NSMutableData alloc] init];
if (_statusCode == 200 || _statusCode == 206) {
_totalLength = [response expectedContentLength];
}
BASE_INFO_FUN(statusCode);
}
// 接收数据
- (void)connection:(NSURLConnection *)aConn didReceiveData:(NSData *)data
{
BASE_INFO_FUN(([NSString stringWithFormat:@"%lu", (unsigned long)data.length]));
[_receiveData appendData:data];
[self parseProgress:_receiveData.length];
}
// 数据接收完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)aConn
{
BASE_INFO_FUN([[NSString alloc] initWithData:_receiveData encoding:NSUTF8StringEncoding]);
// 成功接受:200有数据,204没有数据,206断点续传
if (_statusCode == 200 || _statusCode == 204 || _statusCode == 206) {
[self parseData:_receiveData];
}
else {
NSString *errorMessage = [[NSString alloc] initWithData:_receiveData encoding:NSUTF8StringEncoding];
if (errorMessage.length <= 0) {
errorMessage = [[NSString alloc] initWithFormat:@"ResponseCode:%ld", (long)_statusCode];
}
[self parseFail:errorMessage];
}
_connection = nil;
_receiveData = nil;
}
// 返回错误
- (void)connection:(NSURLConnection *)aConn didFailWithError:(NSError *)error
{
[self parseFail:[error localizedDescription]];
_connection = nil;
_receiveData = nil;
}
@end
在BaseOperation类中,使用了OC原生的网络请求类URL,URLRequest,URLConnection来异步请求网络,并将请求的结果封装成NSMutableDictionary传递给OpSuccess方法。在子类中,我们可以复写OpSuccess方法来接收参数完成对具体Model的封装。
ViewController基类负责将ViewModel和View(xib)之间交互,完成UI界面展示,数据填充,事件响应等功能。
新建BaseController类
BaseController.h
//
// BaseController.h
// NewsReader
//
// Created by 唐有欢 on 15/10/13.
// Copyright © 2015年 tangtech. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BaseOperation.h"
#import "Activity.h"
@interface BaseController : UIViewController<BaseOperationDelegate>{
BaseOperation *_operation;
Activity *_activity;
}
- (void)showIndicator:(NSString *)tipMessage
autoHide:(BOOL)hide
afterDelay:(BOOL)delay;
- (void)hideIndicator;
// 导航栏设置
- (void)setNavigationTitleImage:(NSString *)imageName;
- (void)setNavigationLeft:(NSString *)imageName sel:(SEL)sel;
- (void)setNavigationRight:(NSString *)imageName;
- (void)setStatusBarStyle:(UIStatusBarStyle)style;
- (void)opSuccess:(id)data;
@end
BaseController.m
#import "BaseController.h"
#import "ActivityIndicator.h"
@implementation BaseController
-(void)viewDidLoad
{
[super viewDidLoad];
[self setNavigationLeft:@"NavigationBell.png" sel:nil];
[self setNavigationRight:@"NavigationSquare.png"];
//[self setNavigationTitleImage:@"NavBarIcon.png"];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNavBarImage];
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[self setStatusBarStyle:UIStatusBarStyleLightContent];
}
- (Activity *)showActivityInView:(UIView *)view
{
Activity *activity = [[ActivityIndicator alloc] initWithView:view];
CGRect frame = view.bounds;
activity.frame = frame;
[view addSubview:activity];
activity.labelText = @"";
return activity;
}
- (void)showIndicator:(NSString *)tipMessage
autoHide:(BOOL)hide
afterDelay:(BOOL)delay
{
if (_activity == nil) {
_activity = [self showActivityInView:self.view];
}
if (tipMessage != nil) {
_activity.labelText = tipMessage;
[_activity show:NO];
}
if (hide && _activity.alpha>=1.0) {
if (delay)
[_activity hide:YES afterDelay:AnimationSecond];
else
[_activity hide:YES];
}
}
- (void)hideIndicator
{
[_activity hide:YES];
}
- (void)setNavBarImage
{
UIImage *image = [UIImage imageNamed:[Global isSystemLowIOS7]?@"NavigationBar44.png":@"NavigationBar64.png"];
[self.navigationController.navigationBar setBackgroundImage:image forBarMetrics:UIBarMetricsDefault];
[self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];
NSDictionary *attribute = @{
NSForegroundColorAttributeName:[UIColor whiteColor],
NSFontAttributeName:[UIFont systemFontOfSize:18]
};
[self.navigationController.navigationBar setTitleTextAttributes:attribute];
}
- (UIButton *)customButton:(NSString *)imageName
selector:(SEL)sel
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(0, 0, 44, 44);
[btn setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
[btn addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
return btn;
}
- (void)setNavigationTitleImage:(NSString *)imageName
{
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
self.navigationItem.titleView = imageView;
}
- (void)setNavigationLeft:(NSString *)imageName sel:(SEL)sel
{
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:[self customButton:imageName selector:sel]];
self.navigationItem.leftBarButtonItem = item;
}
- (void)setNavigationRight:(NSString *)imageName
{
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:[self customButton:imageName selector:@selector(doRight:)]];
self.navigationItem.rightBarButtonItem = item;
}
- (void)setStatusBarStyle:(UIStatusBarStyle)style
{
[[UIApplication sharedApplication] setStatusBarStyle:style];
}
- (void)opSuccess:(id)data
{
[self hideIndicator];
}
- (IBAction)doBack:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
}
@end
至此,我们定义好了MVVM开发模式的基类,包括:
Model
|
|_ BaseInfo
ViewModel
|
|_BaseOperation
ViewController
|
|_BaseController
在后面的开发过程中就可以更加关注具体功能了。
github源码:https://github.com/tangthis/NewsReader
个人技术分享微信公众号,欢迎关注一起交流