在面向對象編程中,常用的就是抽象和封裝,今天來完成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基類
在本項目中,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基類
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基類
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
個人技術分享微信公衆號,歡迎關注一起交流