iOS新聞客戶端開發教程3--基類編寫

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章