扣丁書屋

淘寶iOS掃一掃架構升級 - 設計模式的應用

本文在“掃一掃功能的不斷迭代,基于設計模式的基本原則,逐步采用設計模式思想進行代碼和架構優化”的背景下,對設計模式在掃一掃中新的應用進行了總結。

背景

掃一掃是淘寶鏡頭頁中的一個重要組成,功能運行久遠,其歷史代碼中較少采用面向對象編程思想,而較多采用面向過程的程序設計。

隨著掃一掃功能的不斷迭代,我們基于設計模式的基本原則,逐步采用設計模式思想進行代碼和架構優化。本文就是在這個背景下,對設計模式在掃一掃中新的應用進行了總結。

掃一掃原架構

掃一掃的原架構如圖所示。其中邏輯&展現層的功能邏輯很多,并沒有良好的設計和拆分,舉幾個例子:

  1. 所有碼的處理邏輯都寫在同一個方法體里,一個方法就接近 2000 多行。
  2. 龐大的碼處理邏輯寫在 viewController 中,與 UI 邏輯耦合。

按照現有的代碼設計,若要對某種碼邏輯進行修改,都必須將所有邏輯全量編譯。如果繼續沿用此代碼,掃一掃的可維護性會越來越低。

因此我們需要對代碼和架構進行優化,在這里優化遵循的思路是:

  1. 了解業務能力
  2. 了解原有代碼邏輯,不確定的地方通過埋點等方式線上驗證
  3. 對原有代碼功能進行重寫/重構
  4. 編寫單元測試,提供測試用例
  5. 測試&上線

掃碼能力綜述

掃一掃的解碼能力決定了掃一掃能夠處理的碼類型,這里稱為一級分類?;谝患壏诸?,掃一掃會根據碼的內容和類型,再進行二級分類。之后的邏輯,就是針對不同的二級類型,做相應的處理,如下圖為技術鏈路流程。

設計模式

? 責任鏈模式

上述技術鏈路流程中,碼處理流程對應的就是原有的 viewController 里面的巨無霸邏輯。通過梳理我們看到,碼處理其實是一條鏈式的處理,且有前后依賴關系。優化方案有兩個,方案一是拆解成多個方法順序調用;方案二是參考蘋果的 NSOperation 獨立計算單元的思路,拆解成多個碼處理單元。方案一本質還是沒解決開閉原則(對擴展開放,對修改封閉)問的題。方案二是一個比較好的實踐方式。那么怎么設計一個簡單的結構來實現此邏輯呢?

碼處理鏈路的特點是,鏈式處理,可控制處理的順序,每個碼處理單元都是單一職責,因此這里引出改造第一步:責任鏈模式。

責任鏈模式是一種行為設計模式, 它將請求沿著處理者鏈進行發送。收到請求后, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者。

本文設計的責任鏈模式,包含三部分:

  1. 創建數據的 Creator
  2. 管理處理單元的 Manager
  3. 處理單元 Pipeline

?三者結構如圖所示

  • 創建數據的 Creator

包含的功能和特點:

  1. 因為數據是基于業務的,所以它只被聲明為一個 Protocol ,由上層實現。
  2. Creator 對數據做對象化,對象生成后 self.generateDataBlock(obj, Id) 即開始執行

API 代碼示例如下


/// 數據產生協議 <CreatorProtocol>
@protocol TBPipelineDataCreatorDelegate <NSObject>
@property (nonatomic, copy) void(^generateDataBlock)(id data, NSInteger dataId);
@end

上層業務代碼示例如下

@implementation TBDataCreator
@synthesize generateDataBlock;
- (void)receiveEventWithScanResult:(TBScanResult *)scanResult
                     eventDelegate:(id <TBScanPipelineEventDeletate>)delegate {
    //對數據做對象化                
    TBCodeData *data = [TBCodeData new];
    data.scanResult = scanResult;
    data.delegate = delegate;

    NSInteger dataId = 100;
    //開始執行遞歸
    self.generateDataBlock(data, dataId);
}
@end
  • 管理處理單元的 Manager

包含的功能和特點:1. 管理創建數據的 Creator 2. 管理處理單元的 Pipeline 3. 采用支持鏈式的點語法,方便書寫

API 代碼示例如下


@interface TBPipelineManager : NSObject
/// 添加創建數據 Creator
- (TBPipelineManager *(^)(id<TBPipelineDataCreatorDelegate> dataCreator))addDataCreator;
/// 添加處理單元 Pipeline
- (TBPipelineManager *(^)(id<TBPipelineDelegate> pipeline))addPipeline;
/// 拋出經過一系列 Pipeline 的數據。當 Creator 開始調用 generateDataBlock 后,Pipeline 就開始執行
@property (nonatomic, strong) void(^throwDataBlock)(id data);
@end

實現代碼示例如下



@implementation TBPipelineManager
- (TBPipelineManager *(^)(id<TBPipelineDataCreatorDelegate> dataCreator))addDataCreator {
    @weakify
    return ^(id<TBPipelineDataCreatorDelegate> dataCreator) {
        @strongify
        if (dataCreator) {
            [self.dataGenArr addObject:dataCreator];
        }
        return self;
    };
}

- (TBPipelineManager *(^)(id<TBPipelineDelegate> pipeline))addPipeline {
    @weakify
    return ^(id<TBPipelineDelegate> pipeline) {
        @strongify
        if (pipeline) {
            [self.pipelineArr addObject:pipeline];

            //每一次add的同時,我們做鏈式標記(通過runtime給每個處理加Next)
            if (self.pCurPipeline) {
                NSObject *cur = (NSObject *)self.pCurPipeline;
                cur.tb_nextPipeline = pipeline;
            }
            self.pCurPipeline = pipeline;
        }
        return self;
    };
}

- (void)setThrowDataBlock:(void (^)(id _Nonnull))throwDataBlock {
    _throwDataBlock = throwDataBlock;

    @weakify
    //Creator的數組,依次對 Block 回調進行賦值,當業務方調用此 Block 時,就是開始處理數據的時候
    [self.dataGenArr enumerateObjectsUsingBlock:^(id<TBPipelineDataCreatorDelegate>  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        obj.generateDataBlock = ^(id<TBPipelineBaseDataProtocol> data, NSInteger dataId) {
            @strongify
            data.dataId = dataId;
            //開始遞歸處理數據
            [self handleData:data];
        };
    }];
}

- (void)handleData:(id)data {
    [self recurPipeline:self.pipelineArr.firstObject data:data];
}

- (void)recurPipeline:(id<TBPipelineDelegate>)pipeline data:(id)data {
    if (!pipeline) {
        return;
    }

    //遞歸讓pipeline處理數據
    @weakify
    [pipeline receiveData:data throwDataBlock:^(id  _Nonnull throwData) {
        @strongify
        NSObject *cur = (NSObject *)pipeline;
        if (cur.tb_nextPipeline) {
            [self recurPipeline:cur.tb_nextPipeline data:throwData];
        } else {
            !self.throwDataBlock?:self.throwDataBlock(throwData);
        }
    }];
}
@end
  • 處理單元 Pipeline

包含的功能和特點:

  1. 因為數據是基于業務的,所以它只被聲明為一個 Protocol ,由上層實現。

API 代碼示例如下

@protocol TBPipelineDelegate <NSObject>
//如果有錯誤,直接拋出
- (void)receiveData:(id)data throwDataBlock:(void(^)(id data))block;
@end

上層業務代碼示例如下


//以A類型碼碼處理單元為例
@implementation TBGen3Pipeline
- (void)receiveData:(id <TBCodeDataDelegate>)data throwDataBlock:(void (^)(id data))block {
    TBScanResult *result = data.scanResult;
    NSString *scanType = result.resultType;
    NSString *scanData = result.data;

    if ([scanType isEqualToString:TBScanResultTypeA]) {
        //跳轉邏輯
        ...
        //可以處理,終止遞歸
        BlockInPipeline();
    } else {
        //不滿足處理條件,繼續遞歸:由下一個 Pipeline 繼續處理
        PassNextPipeline(data);
    }
}
@end
  • 業務層調用

有了上述的框架和上層實現,生成一個碼處理管理就很容易且能達到解耦的目的,代碼示例如下

- (void)setupPipeline {
    //創建 manager 和 creator
    self.manager = TBPipelineManager.new;
    self.dataCreator = TBDataCreator.new;

    //創建 pipeline
    TBCodeTypeAPipelie *codeTypeAPipeline = TBCodeTypeAPipelie.new;
    TBCodeTypeBPipelie *codeTypeBPipeline = TBCodeTypeBPipelie.new;
    //...
    TBCodeTypeFPipelie *codeTypeFPipeline = TBCodeTypeFPipelie.new;

    //往 manager 中鏈式添加 creator 和 pipeline
    @weakify
    self.manager
    .addDataCreator(self.dataCreator)
    .addPipeline(codeTypeAPipeline)
    .addPipeline(codeTypeBPipeline) 
    .addPipeline(codeTypeFPipeline) 
    .throwDataBlock = ^(id data) {
        @strongify
        if ([self.proxyImpl respondsToSelector:@selector(scanResultDidFailedProcess:)]) {
            [self.proxyImpl scanResultDidFailedProcess:data];
        }
    };
}

狀態模式

回頭來看下碼展示的邏輯,這是我們用戶體驗優化的重要一項內容。碼展示的意思是對于當前幀/圖片,識別到的碼位置,我們進行錨點的高亮并跳轉。這里包含三種情況:1. 未識別到碼的時候,無錨點展示 2. 識別到單碼的時候,展示錨點并在指定時間后跳轉 3. 識別到多碼額時候,展示錨點并等待用戶點擊

可以看到,這里涉及到簡單的展示狀態切換,這里就引出改造的第二步:狀態模式

狀態模式是一種行為設計模式, 能在一個對象的內部狀態變化時改變其行為, 使其看上去就像改變了自身所屬的類一樣。

本文設計的狀態模式,包含兩部分:

  1. 狀態的信息 StateInfo
  2. 狀態的基類 BaseState

兩者結構如圖所示

? 狀態的信息 StateInfo

包含的功能和特點:

  1. 當前上下文僅有一種狀態信息流轉
  2. 業務方可以保存多個狀態鍵值對,狀態根據需要執行相應的代碼邏輯。

狀態信息的聲明和實現代碼示例如下


@interface TBBaseStateInfo : NSObject {
    @private
    TBBaseState<TBBaseStateDelegate> *_currentState; //記錄當前的 State
}
//使用當前的 State 執行
- (void)performAction;
//更新當前的 State
- (void)setState:(TBBaseState <TBBaseStateDelegate> *)state;
//獲取當前的 State
- (TBBaseState<TBBaseStateDelegate> *)getState;
@end

@implementation TBBaseStateInfo
- (void)performAction {
    //當前狀態開始執行
    [_currentState perfromAction:self];
}
- (void)setState:(TBBaseState <TBBaseStateDelegate> *)state {
    _currentState = state;
}
- (TBBaseState<TBBaseStateDelegate> *)getState {
    return _currentState;
}
@end

上層業務代碼示例如下


typedef NS_ENUM(NSInteger, TBStateType) {
    TBStateTypeNormal, //空狀態
    TBStateTypeSingleCode, //單碼展示態
    TBStateTypeMultiCode, //多碼展示態
};

@interface TBStateInfo : TBBaseStateInfo
//以 key-value 的方式存儲業務 type 和對應的狀態 state
- (void)setState:(TBBaseState<TBBaseStateDelegate> *)state forType:(TBStateType)type;
//更新 type,并執行 state
- (void)setType:(TBStateType)type;
@end

@implementation TBStateInfo

- (void)setState:(TBBaseState<TBBaseStateDelegate> *)state forType:(TBStateType)type {
    [self.stateDict tb_setObject:state forKey:@(type)];
}

- (void)setType:(TBStateType)type {
    id oldState = [self getState];
    //找到當前能響應的狀態
    id newState = [self.stateDict objectForKey:@(type)];
    //如果狀態未發生變更則忽略
    if (oldState == newState)
        return;
    if ([newState respondsToSelector:@selector(perfromAction:)]) {
        [self setState:newState];
        //轉態基于當前的狀態信息開始執行
        [newState perfromAction:self];
    }
}
@end

? 狀態的基類 BaseState

包含的功能和特點:

  1. 定義了狀態的基類
  2. 聲明了狀態的基類需要遵循的 Protocol

Protocol 如下,基類為空實現,子類繼承后,實現對 StateInfo 的處理。-


@protocol TBBaseStateDelegate <NSObject>
- (void)perfromAction:(TBBaseStateInfo *)stateInfo;
@end

上層(以單碼 State 為例)代碼示例如下

@interface TBSingleCodeState : TBBaseState
@end

@implementation TBSingleCodeState

//實現 Protocol
- (void)perfromAction:(TBStateInfo *)stateAction {
    //業務邏輯處理 Start
    ...
    //業務邏輯處理 End
}

@end

? 業務層調用

以下代碼生成一系列狀態,在合適時候進行狀態的切換。


//狀態初始化
- (void)setupState {
    TBSingleCodeState *singleCodeState = TBSingleCodeState.new; //單碼狀態
    TBNormalState *normalState = TBNormalState.new; //正常狀態
    TBMultiCodeState *multiCodeState = [self getMultiCodeState]; //多碼狀態

    [self.stateInfo setState:normalState forType:TBStateTypeNormal];
    [self.stateInfo setState:singleCodeState forType:TBStateTypeSingleCode];
    [self.stateInfo setState:multiCodeState forType:TBStateTypeMultiCode];
}

//切換常規狀態
- (void)processorA {
    //...
    [self.stateInfo setType:TBStateTypeNormal];
    //...
}

//切換多碼狀態
- (void)processorB {
    //...
    [self.stateInfo setType:TBStateTypeMultiCode];
    //...
}

//切換單碼狀態
- (void)processorC {
    //...
    [self.stateInfo setType:TBStateTypeSingleCode];
    //...
}

最好根據狀態機圖編寫狀態切換代碼,以保證每種狀態都有對應的流轉。

次態→初態↓ 狀態A 狀態B 狀態C
狀態A 條件A ... ...
狀態B ... ... ...
狀態C ... ... ...

代理模式

在開發過程中,我們會在越來越多的地方使用到上圖能力,比如「淘寶拍照」的相冊中、「掃一掃」的相冊中,用到解碼、碼展示、碼處理的能力。

因此,我們需要把這些能力封裝并做成插件化,以便在任何地方都能夠使用。這里就引出了我們改造的第三步:代理模式。

代理模式是一種結構型設計模式,能夠提供對象的替代品或其占位符。代理控制著對于原對象的訪問, 并允許在將請求提交給對象前后進行一些處理。

本文設計的狀態模式,包含兩部分:

  1. 代理單例 GlobalProxy
  2. 代理的管理 ProxyHandler

兩者結構如圖所示

? 代理單例 GlobalProxy

單例的目的主要是減少代理重復初始化,可以在合適的時機初始化以及清空保存的內容。單例模式對于 iOSer 再熟悉不過了,這里不再贅述。

? 代理的管理 Handler

維護一個對象,提供了對代理增刪改查的能力,實現對代理的操作。這里實現 Key - Value 的 Key 為 Protocol ,Value 為具體的代理。

代碼示例如下



+ (void)registerProxy:(id)proxy withProtocol:(Protocol *)protocol {
    if (![proxy conformsToProtocol:protocol]) {
        NSLog(@"#TBGlobalProxy, error");
        return;
    }
    if (proxy) {
        [[TBGlobalProxy sharedInstance].proxyDict setObject:proxy forKey:NSStringFromProtocol(protocol)];
    }
}

+ (id)proxyForProtocol:(Protocol *)protocol {
    if (!protocol) {
        return nil;
    }
    id proxy = [[TBGlobalProxy sharedInstance].proxyDict objectForKey:NSStringFromProtocol(protocol)];
    return proxy;
}

+ (NSDictionary *)proxyConfigs {
    return [TBGlobalProxy sharedInstance].proxyDict;
}

+ (void)removeAll {
    [TBGlobalProxy sharedInstance].proxyDict = [[NSMutableDictionary alloc] init];
}

? 業務層的調用

所以不管是什么業務方,只要是需要用到對應能力的地方,只需要從單例中讀取 Proxy, 實現該 Proxy 對應的 Protocol, 如一些回調、獲取當前上下文等內容,就能夠獲取該 Proxy 的能力。


//讀取 Proxy 的示例
- (id <TBScanProtocol>)scanProxy {
    if (!_scanProxy) {
        _scanProxy = [TBGlobalProxy proxyForProtocol:@protocol(TBScanProtocol)];
    }
    _scanProxy.proxyImpl = self;
    return _scanProxy;
}

//寫入 Proxy 的示例(解耦調用)
- (void)registerGlobalProxy {
    //碼處理能力
    [TBGlobalProxy registerProxy:[[NSClassFromString(@"TBScanProxy") alloc] init]
                    withProtocol:@protocol(TBScanProtocol)];
    //解碼能力
    [TBGlobalProxy registerProxy:[[NSClassFromString(@"TBDecodeProxy") alloc] init]
                    withProtocol:@protocol(TBDecodeProtocol)];
}

掃一掃新架構?

基于上述的改造優化,我們將原掃一掃架構進行了優化:將邏輯&展現層進行代碼分拆,分為展現層、邏輯層,接口層。以達到層次分明、職責清晰、解耦的目的。

總結

上述沉淀的三個設計模式作為掃拍業務的 Foundation 的 Public 能力,應用在鏡頭頁的業務邏輯中。

通過此次重構,提高了掃碼能力的復用性,結構和邏輯的清晰帶來的是維護成本的降低,不用再大海撈針從代碼“巨無霸”中尋找問題,降低了開發人日。


https://mp.weixin.qq.com/s/v3MWjZ4AcVxzVW6HfY9IiQ

最多閱讀

iOS 性能檢測新方式?——AnimationHitches 1年以前  |  22531次閱讀
快速配置 Sign In with Apple 3年以前  |  6305次閱讀
APP適配iOS11 4年以前  |  5059次閱讀
App Store 審核指南[2017年最新版本] 4年以前  |  4899次閱讀
所有iPhone設備尺寸匯總 4年以前  |  4796次閱讀
使用 GPUImage 實現一個簡單相機 3年以前  |  4561次閱讀
開篇 關于iOS越獄開發 4年以前  |  4186次閱讀
在越獄的iPhone設置上使用lldb調試 4年以前  |  4093次閱讀
給數組NSMutableArray排序 4年以前  |  4009次閱讀
使用ssh訪問越獄iPhone的兩種方式 4年以前  |  3708次閱讀
UITableViewCell高亮效果實現 4年以前  |  3705次閱讀
關于Xcode不能打印崩潰日志 4年以前  |  3632次閱讀
iOS虛擬定位原理與預防 1年以前  |  3629次閱讀
使用ssh 訪問越獄iPhone的兩種方式 4年以前  |  3447次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>