iOS 項(xiàng)目搭建總結(jié)2016

記錄開(kāi)發(fā)認(rèn)知的一步步。

第一步,開(kāi)始接觸開(kāi)發(fā)也沒(méi)什么模式,就是 model view control 散落一地,模塊分下文件夾,當(dāng)時(shí)大部分項(xiàng)目起步都是這樣。

第二步來(lái)到這邊接手項(xiàng)目,因?yàn)榻唤拥娜耸亲鲞^(guò)7年的后臺(tái),所以對(duì)代碼的分層很看重。然后我也就跟著習(xí)慣了 API-service-control model view 的分層方式,感覺(jué)好爽,這種做小規(guī)模的APP還是很可行的。

第三步,看到大神寫(xiě)的文章都在強(qiáng)調(diào)代碼之間的耦合性,以及各種組件化,就想嘗試下。自己實(shí)現(xiàn)了一套 function-business-main 模式,也就是底層function是實(shí)現(xiàn)各個(gè)功能的工具層,業(yè)務(wù)層business調(diào)用function層各種工具實(shí)現(xiàn)具體業(yè)務(wù),最后由 main 層組合各個(gè)業(yè)務(wù)最終實(shí)現(xiàn)完整的應(yīng)用,各個(gè)模塊之間的通信通過(guò)一個(gè)中間者去調(diào)用,配合OC 的runtime特性通信很easy。中間也嘗試過(guò)pod庫(kù)的方式實(shí)現(xiàn)組件化,無(wú)奈一個(gè)人玩太麻煩,沒(méi)堅(jiān)持下來(lái)放棄了。

casa 大神博客地址
現(xiàn)在的項(xiàng)目結(jié)構(gòu)

項(xiàng)目結(jié)構(gòu)圖

BaseKit 系統(tǒng)的基礎(chǔ)工具庫(kù)(基礎(chǔ)數(shù)據(jù)提供)
Function 提供各種功能性工具,比如分享、推送、各種常用擴(kuò)展等等,

工具層

Business 組合各種功能性工具實(shí)現(xiàn)各個(gè)業(yè)務(wù)模塊 具體的模塊實(shí)現(xiàn)使用MVC 還是 MVVM 對(duì)整體沒(méi)有影響。

業(yè)務(wù)層預(yù)覽

Main 就是組合各個(gè)業(yè)務(wù)模塊,為應(yīng)用提供一個(gè)完整的入口


主入口

具體開(kāi)發(fā)實(shí)現(xiàn)

1.確定一些原則

1.1 去除項(xiàng)目對(duì)基類的依賴,所有的功能可以通過(guò) Category 的方式進(jìn)行實(shí)現(xiàn) , 例如實(shí)現(xiàn)指示器的功能,在需要的地方引入 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 確定代碼編寫(xiě)結(jié)構(gòu) 例如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 進(jìn)行庫(kù)管理,在進(jìn)行 OC swift 混編到時(shí)候發(fā)現(xiàn)一個(gè)小技巧,使用 pod 管理 OC 的庫(kù),使用 Carthage 管理 swift 的庫(kù),這樣省了很多事,對(duì)于我一個(gè)人搞的項(xiàng)目來(lái)說(shuō)還是很方便的。

1.4 布局使用 Storyboard + Masonry , 這個(gè)看個(gè)人喜好

1.5 確定模塊化、組件化開(kāi)發(fā)。主要針對(duì)業(yè)務(wù)層模塊規(guī)定,禁止同層直接調(diào)用,杜絕耦合其他業(yè)務(wù)模塊代碼,解決方案為調(diào)用調(diào)度者模塊(思路介紹來(lái)自 casa 大神系列教程),A 業(yè)務(wù)模塊編寫(xiě)一個(gè)向外部暴露的方法供其他模塊的調(diào)用,B 業(yè)務(wù)模塊就可以通過(guò)調(diào)度者去調(diào)用 A 暴露的方法。(也有一種方案是蘑菇街通過(guò)路由的方式實(shí)現(xiàn)的組件件調(diào)用,受大神影響,覺(jué)得使用 Mediator 的方式更適合我現(xiàn)在的應(yīng)用。)具體實(shí)現(xiàn):

訂單模塊向外暴露方法

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TargetOrder : NSObject

/**
 *  獲取訂單支付頁(yè)面
 *
 *  @param orderID 訂單ID
 *
 *  @return 訂單支付頁(yè)面
 */
- (UIViewController *)ActionFetchStidumOrderPayVC:(NSDictionary *)orderDic;

調(diào)度者模塊編寫(xiě) category,通過(guò)運(yùn)行時(shí)調(diào)用訂單模塊的方法

@implementation YMMediator (OrderActions)

- (UIViewController *)MediatorViewControllerForStidumOrderPayVC:(NSString *)orderID{
    NSAssert(orderID != nil, @"請(qǐng)檢查訂單號(hào)");
    UIViewController *vc = [self performTarget:kMediatorTargetOrder
                                        action:ActionFetchStidumOrderPayVC
                                        params:@{@"orderID":orderID}];
    
    if ([vc isKindOfClass:[UIViewController class]]) {
        return vc;
    }else{
        //異常
        return [[UIViewController alloc] init];
    }
    
}
-------分割--------------

YMMediator 調(diào)度者的關(guān)鍵代碼

- (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) {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方之一,這個(gè)demo做得比較簡(jiǎn)單,如果沒(méi)有可以響應(yīng)的target,就直接return了。實(shí)際開(kāi)發(fā)過(guò)程中是可以事先給一個(gè)固定的target專門(mén)用于在這個(gè)時(shí)候頂上,然后處理這種請(qǐng)求的
        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 {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方,如果無(wú)響應(yīng),則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
        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 {
            // 這里也是處理無(wú)響應(yīng)請(qǐng)求的地方,在notFound都沒(méi)有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開(kāi)發(fā)過(guò)程中,可以用前面提到的固定的target頂上的。
            return nil;
        }
    }
}


其他模塊調(diào)用 支付模塊的 支付界面

- (void)goPayVC:(NSString *)orderID{
    if (orderID == nil || orderID.length <1) {
        [self showFastToastHUD:@"訂單號(hào)不能為空"];
        return;
    }
    UIViewController *vc = [[YMMediator sharedInstance] MediatorViewControllerForStidumOrderPayVC:orderID];
    [self.navigationController pushViewController:vc animated:YES];
}

2.網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求方式

2.1 主要是為了對(duì)網(wǎng)絡(luò)請(qǐng)求的過(guò)程提供盡可能多的監(jiān)控感知,通過(guò)切片設(shè)計(jì)對(duì)網(wǎng)絡(luò)請(qǐng)求過(guò)程添加想要的處理函數(shù),可以通過(guò)代理的方式實(shí)現(xiàn),也可以通過(guò) block 方式實(shí)現(xiàn)。封裝屬于自己的一套網(wǎng)絡(luò)請(qǐng)求,切勿過(guò)于依賴某一種網(wǎng)絡(luò)庫(kù)

/************************************************
        APIManagerCallBackDelegate
                回調(diào)協(xié)議
 ************************************************/

@protocol APIManagerCallBackDelegate <NSObject>

@required
- (void)apiManagerCallBackDidSuccess:(BaseAPIManager *)manager;
- (void)apiManagerCallBackDidFailed:(BaseAPIManager *)manager;

@end

/************************************************
        APIManagerCallBackDateReformer
                    DateReformer協(xié)議
 ************************************************/

@protocol APIManagerCallBackDateReformer <NSObject>

@required
- (id)manager:(BaseAPIManager *)manager reformData:(NSObject *)data;

@end

/************************************************
        APIManagerParamSourceDelegate
                參數(shù)源
 ************************************************/
@protocol APIManagerParamSourceDelegate <NSObject>

@required
- (NSDictionary *)paramsForAPi:(BaseAPIManager *)manager;

- (NSDictionary *)headerFieldsForAPi:(BaseAPIManager *)manager;

@end

/************************************************
        APIManagerValidator
                驗(yàn)證協(xié)議
 ************************************************/
@protocol APIManagerValidator <NSObject>

@required
- (BOOL)manager:(BaseAPIManager *)manager isCorrectWithCallBackData:(id)data;
- (BOOL)manager:(BaseAPIManager *)manager isCorrectWithParamsData:(NSDictionary *)data;

@end

/************************************************
        APIManagerInterceptor
                攔截器協(xié)議
 ************************************************/
@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.引入單元測(cè)試

3.1 觀看過(guò)李智維的自動(dòng)化測(cè)試直播后,學(xué)習(xí)了一下單元測(cè)試接入,通過(guò)查找資料也找到了他簡(jiǎn)單封裝的測(cè)試工具宏,非常好用。


//
//  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
}

以上是這一段時(shí)間對(duì)項(xiàng)目架構(gòu)的思考。

最近看了一個(gè)視頻講移動(dòng)應(yīng)用架構(gòu)主要就兩個(gè)部分,數(shù)據(jù)+UI,覺(jué)得也是挺有道理的,待慢慢沉淀多參悟這種簡(jiǎn)單的事情吧。

casa 大神博客地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容