iOS老司機可落地在中大型iOS專案中的5大接地氣設計模式合集

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第1天,點選檢視活動詳情

1. 前言: 設計模式可以在大型專案中有哪些可落地的優化?

  • 筆者目前負責一箇中大型iOS專案,用PPRows跑一下專案根檔案,目前程式碼量約28W。
  • 在這樣一個大型專案中,我們組用了哪些方法對業務邏輯及程式碼架構進行解耦呢?
  • 在這種程式碼量級的專案中,有哪些接地氣的,可落地的優化經驗呢?
  • 在此拋磚引玉,歡迎大家一起相互探討。

image.png

2. 落地

2.1 採用"單例模式", 做一個更加單一職責的廣告管理類.

2.1.1 單例模式導圖

image.png

2.1.2 單例模式, 核心類檔案構成

image.png

2.1.3 採用單例模式解耦, 核心程式碼檔案講解

2.1.3.1 定義一個繼承自NSObject的單例廣告管理類ADManager

  • ADManager.h檔案 ```

import

NS_ASSUME_NONNULL_BEGIN

@interface ADManager : NSObject

/// 供外部統一使用的單例類方法 + (instancetype)sharedInstance;

@end

NS_ASSUME_NONNULL_END - ADManager.m檔案

import "ADManager.h"

@implementation ADManager

  • (instancetype)sharedInstance {     // 靜態區域性變數     static ADManager *adManager = nil;

// 通過dispatch_once方式, 確保instance在多執行緒環境下只被建立一次     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         // 呼叫父類的方法建立例項, 防止跟重寫自身的allocWithZone發生迴圈呼叫         adManager = [[super allocWithZone:NULL] init];     });     return adManager; }

// 重寫自身的allocWithZone, 應對不使用sharedInstance方法直接通過alloc建立物件的情況 + (instancetype)allocWithZone:(struct _NSZone *)zone {     return [self sharedInstance]; }

// MRC下重寫copyWithZone, 應對通過copy複製物件的情況, OBJC_ARC_UNAVAILABLE + (id)copyWithZone:(struct _NSZone *)zone {     return self; }

@end ```

2.1.3.2 實際業務使用單例模式示例

``` // //  Viewontroller.m //  appDesignPattern // //  Created by JackLee on 2022/9/21. //

import "ViewController.h"

import "ADManager.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {     [super viewDidLoad]; }

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     // 測試單例模式     [self testSingleADManager]; }

/// 測試單例模式 - (void)testSingleADManager {     ADManager adManager = [ADManager sharedInstance];     ADManager adManager2 = [ADManager new];

NSLog(@"adManager === %@ ptr === %p", adManager, adManager);     NSLog(@"adManager2 === %@ ptr === %p", adManager2, adManager2); } ```

image.png

2.2 採用"命令模式", 使行為引數化, 降低程式碼重合度

2.2.1 命令模式導圖

image.png

2.2.2 命令模式, 核心類檔案構成

image.png

2.2.3 命令模式對行為進行引數化處理以解耦, 核心程式碼檔案講解

2.2.3.1 定義一個抽象命令類Command

  • Command.h檔案 ```

import

@class Command; typedef  void(^CommandCompletionCallBack)(Command *cmd);

@interface Command : NSObject

// 命令完成的回撥 @property (nonatomic, copy) CommandCompletionCallBack completion;

/// 執行命令 - (void)execute; /// 取消命令 - (void)cancel;

/// 完成命令 - (void)done;

@end - Command.m檔案

import "Command.h"

import "CommandManager.h"

@implementation Command

  • (void)execute {     // override to subclass, 交給子類複寫具體實現 //    [self done]; }

  • (void)cancel {     self.completion = nil; }

  • (void)done {     // 考慮到多執行緒的情況, 非同步回到主佇列     dispatch_async(dispatch_get_main_queue(), ^{         if (self.completion) {             self.completion(self);         }                  // 釋放         self.completion = nil;         [[CommandManager sharedInstance].arrayCommands removeObject:self];     }); }

@end ```

  • 具體的點贊命令LikedCommand.h ```

import "Command.h"

NS_ASSUME_NONNULL_BEGIN

@interface LikedCommand : Command

@end

NS_ASSUME_NONNULL_END ```

  • 具體的點贊命令LikedCommand.m ```

import "LikedCommand.h"

@implementation LikedCommand

  • (void)execute {     NSLog(@"執行點贊操作 ====");     [self done]; }

@end ```

  • 具體的分享命令ShareCommand.h ```

import "Command.h"

NS_ASSUME_NONNULL_BEGIN

@interface ShareCommand : Command

@end

NS_ASSUME_NONNULL_END ```

  • 具體的分享命令ShareCommand.m ```

import "ShareCommand.h"

@implementation ShareCommand

  • (void)execute {     NSLog(@"執行分享操作 ====");     [self done]; }

@end ```

  • 命令管理類CommandManager.h ```

import

import "Command.h"

NS_ASSUME_NONNULL_BEGIN

@interface CommandManager : NSObject

/// 命令管理容器 @property(nonatomic, copy) NSMutableArray *arrayCommands;

/// 命令管理者提供單例方法供使用者呼叫 + (instancetype)sharedInstance;

/// 執行命令 + (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion;

/// 取消命令 + (void)cancelCommand:(Command *)cmd;

@end

NS_ASSUME_NONNULL_END ```

  • 命令管理類CommandManager.m ```

import "CommandManager.h"

@implementation CommandManager

  • (instancetype)sharedInstance {     static CommandManager *instance = nil;     static dispatch_once_t onceToken;     dispatch_once(&onceToken, ^{         instance = [[super allocWithZone:NULL] init];     });

return instance; } + (instancetype)allocWithZone:(struct _NSZone *)zone {     return [self sharedInstance]; }

  • (void)executeCommand:(Command *)cmd completion:(CommandCompletionCallBack)completion {     if (cmd) {         // 如果命令正在執行不做處理, 否則新增並執行命令         if (![self _isExecutingCommand:cmd]) {             // 新增到命令容器當中             [[[self sharedInstance] arrayCommands] addObject:cmd];                        // 設定具體命令執行完成後的回撥             cmd.completion = completion;                          // 呼叫具體命令執行方法             [cmd execute];         }     } }

  • (void)cancelCommand:(Command *)cmd {     if (cmd) {         // 從命令容器當中移除         [[[self sharedInstance] arrayCommands] removeObject:cmd];

// 取消命令執行         [cmd cancel];     } }

  • (BOOL)_isExecutingCommand:(Command )cmd {     if (cmd) {         NSArray cmds = [[self sharedInstance] arrayCommands];              for (Command *aCmd in cmds) {             // 當前命令正在執行             if (cmd == aCmd) {                 return YES;             }         }     }     return NO; }

@end ```

2.2.3.2 實際業務使用命令模式示例

``` // //  Viewontroller.m //  appDesignPattern // //  Created by JackLee on 2022/9/21. //

import "ViewController.h"

import "LikedCommand.h"

import "ShareCommand.h"

import "CommandManager.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {     [super viewDidLoad]; }

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     // 測試命令模式     [self testCommand]; }

/// 測試命令模式 - (void)testCommand {     LikedCommand liked = [LikedCommand new];     ShareCommand share = [ShareCommand new];

[CommandManager executeCommand:liked completion:^(Command *cmd) {         NSLog(@"點贊命令完成回撥 cmd === %@", cmd);     }];

[CommandManager executeCommand:share completion:^(Command *cmd) {         NSLog(@"分享命令完成回撥 cmd === %@", cmd);     }]; }

@end ```

image.png

2.3 採用"介面卡"模式, 更優雅的為陳舊業務程式碼擴充套件新功能.

2.3.1 介面卡模式導圖

image.png

2.3.2 介面卡模式解耦, 核心類檔案構成

image.png

2.3.3 介面卡模式解耦, 核心程式碼檔案講解

2.3.3.1 定義一個繼承自NSObject的介面卡類AdapterTarget

  • AdapterTarget.h檔案 ```

import "OldTarget.h"

NS_ASSUME_NONNULL_BEGIN

/// 適配物件 @interface AdapterTarget : NSObject

/// 被適配老業務物件 @property (nonatomic, strong) OldTarget *oldTarget;

/// 對原有方法進行介面卡包裝 - (void)adapertRequest;

@end

NS_ASSUME_NONNULL_END - AdapterTarget.m檔案 // //  AdapterTarget.m //  appDesignPattern // //  Created by JackLee on 2022/10/12. //

import "AdapterTarget.h"

@implementation AdapterTarget

  • (OldTarget *)oldTarget {     if (!_oldTarget) {         _oldTarget = [[OldTarget alloc] init];     }     return _oldTarget; }

  • (void)adapertRequest {     NSLog(@"新增額外處理A");

[self.oldTarget oldOperation];

NSLog(@"新增額外處理B"); }

@end ```

  • 久經考驗的處理業務邏輯的舊類 OldTarget
  • OldTarget.h檔案 ```

import

NS_ASSUME_NONNULL_BEGIN

@interface OldTarget : NSObject

/// 久經考驗的舊處理方法 - (void)oldOperation;

@end

NS_ASSUME_NONNULL_END ```

  • OldTarget.m檔案 ```

import "OldTarget.h"

@implementation OldTarget

  • (void)oldOperation {     NSLog(@"久經考驗的舊處理方法"); }

@end ```

2.3.3.2 實際業務使用介面卡模式示例

``` // //  ViewController.m //  appDesignPattern // //  Created by JackLee on 2022/9/21. //

import "ViewController.h"

import "AdapterTarget.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {     [super viewDidLoad]; }

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     // 測試介面卡模式     [self testAdapter]; }

  • (void)testAdapter {     AdapterTarget *adapterTarget = [AdapterTarget new];     [adapterTarget adapertRequest]; } ```

2.4 採用"橋接模式", 應對同一頁面網路資料介面來回變動的場景, 進行邏輯解耦.

2.4.1 橋接模式導圖

image.png

2.4.2 橋接模式解耦, 核心類檔案構成

image.png

2.4.3 橋接模式解耦, 核心程式碼檔案講解

2.4.3.1 定義一個抽象的基類BaseObjectA

  • BaseObjectA.h檔案 ```

import

import "BaseObjectB.h"

NS_ASSUME_NONNULL_BEGIN

@interface BaseObjectA : NSObject

/// 橋接模式的核心實現 @property (nonatomic, strong) BaseObjectB *objB;

/// 獲取資料 - (void)handle;

@end

NS_ASSUME_NONNULL_END - BaseObjectA.m檔案

import "BaseObjectA.h"

@implementation BaseObjectA

/*  A1 --> B1、B2 2種對應  A2 --> B1、B2 2種對應  / - (void)handle {     // override to subclass 交給具體的子類複寫     [self.objB fetchData]; }

@end ```

  • 具體的頁面A1 ObjectA1
  • ObjectA1.m檔案 ```

import "ObjectA1.h"

@implementation ObjectA1

  • (void)handle {     // before 業務邏輯操作

[super handle];

// after 業務邏輯操作 }

@end ```

  • 不同介面的抽象父類BusinessB.h檔案 ```

import

NS_ASSUME_NONNULL_BEGIN

@interface BaseObjectB : NSObject

  • (void)fetchData;

@end

NS_ASSUME_NONNULL_END ```

  • 不同介面的抽象父類BusinessB.m檔案 ```

import "BaseObjectB.h"

@implementation BaseObjectB

  • (void)fetchData {     // override to subclass 交給子類實現 }

@end ```

  • 具體使用的介面B2的實現ObjectB.m檔案 ```

import "ObjectB2.h"

@implementation ObjectB2

  • (void)fetchData {     // B2具體的邏輯處理     NSLog(@"B2介面獲取資料具體的邏輯處理 === "); }

@end ```

2.4.3.2 實際業務使用橋接模式示例

``` // //  ViewController.m //  appDesignPattern // //  Created by JackLee on 2022/9/21. //

import "ViewController.h"

import "ObjectA1.h"

import "ObjectA2.h"

import "ObjectB1.h"

import "ObjectB2.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {     [super viewDidLoad]; }

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     // 測試橋接模式     [self testBridgeFetchData]; }

  • (void)testBridgeFetchData {     /*      根據實際業務判斷使用哪套具體資料      A1 --> B1、B2 2種對應      A2 --> B1、B2 2種對應      /

// 建立一個具體的ClassA     BaseObjectA objA = [ObjectA1 new]; //    BaseObjectA objA = [ObjectA2 new];

// 建立一個具體的ClassB //    BaseObjectB objB2 = [ObjectB1 new];     BaseObjectB objB2 = [ObjectB2 new];     // 將一個具體的ClassB2 指定給抽象BaseClassB     objA.objB = objB2;

// A列表使用B2的介面獲取資料     [objA handle]; } ```

2.5 採用"責任鏈模式", 應對產品大大提出的業務變更, 對業務程式碼進行解耦.

2.5.1 責任鏈模式導圖

image.png

2.5.2 責任鏈模式解耦, 核心類檔案構成

image.png

2.5.3 責任鏈模式解耦, 核心程式碼檔案講解

2.5.3.1 定義一個抽象的基類BusinessObject

  • BusinessObject.h檔案 ```

import

//NS_ASSUME_NONNULL_BEGIN

@class BusinessObject;

/// 某一業務完成之後, 返回的結果是否有處理掉這個業務 typedef void(^CompletionBlock)(BOOL handled); /// 這個業務對應的處理者, 有沒有處理好這個業務 typedef void(^ResultBlock)(BusinessObject *handler, BOOL handled);

@interface BusinessObject : NSObject

/// 下一個響應者(響應鏈構成的關鍵) @property (nonatomic, strong) BusinessObject *nextBusiness;

/// 響應者的處理方法 - (void)handle:(ResultBlock)result; /// 各個業務在該方法當中做實際業務處理, 完成之後結果返回給呼叫方 - (void)handleBusiness:(CompletionBlock)completion;

@end - BusinessObject.m檔案

import "BusinessObject.h"

@implementation BusinessObject

/// 責任鏈入口方法 -(void)handle:(ResultBlock)result {     CompletionBlock completion = ^(BOOL handled){         // 當前業務處理掉了,上拋結果         if (handled) {             result(self, handled);         }         else {             // 沿著責任鏈,指派給下一個業務處理             if (self.nextBusiness) {                 [self.nextBusiness handle:result];             }             else {                 // 沒有業務處理,上拋                 result(nil, NO);             }         }     };    

// 當前業務進行處理     [self handleBusiness:completion]; }

  • (void)handleBusiness:(CompletionBlock)completion {

/*      業務邏輯處理      例如非同步網路請求、非同步本地照片查詢等      交給子類複寫      /

}

@end ```

  • 例如需要處理網路請求的業務A BusinessA
  • 業務A的BusinessObject.h檔案 ```

import "BusinessObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface BusinessA : BusinessObject

@end

NS_ASSUME_NONNULL_END ```

  • 業務A的BusinessObject.m檔案 ```

import "BusinessA.h"

@implementation BusinessA

  • (void)handleBusiness:(CompletionBlock)completion {     NSLog(@"處理業務A");

// 業務順序: A -> B -> C //    completion(NO);

// 業務順序: C -> B -> A     completion(YES); }

@end ```

  • 業務B的BusinessObjectB.h檔案 ```

import "BusinessObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface BusinessB : BusinessObject

@end

NS_ASSUME_NONNULL_END ```

  • 業務B的BusinessObjectB.m檔案 ```

import "BusinessB.h"

@implementation BusinessB

  • (void)handleBusiness:(CompletionBlock)completion {

NSLog(@"處理業務B");

// 業務順序: A -> B -> C //    completion(NO);

// 業務順序: C -> B -> A     completion(NO); }

@end ```

  • 業務C的BusinessObjectC.h檔案 ```

import "BusinessObject.h"

NS_ASSUME_NONNULL_BEGIN

@interface BusinessC : BusinessObject

@end

NS_ASSUME_NONNULL_END ```

  • 業務C的BusinessObjectC.m檔案 ```

import "BusinessC.h"

@implementation BusinessC

  • (void)handleBusiness:(CompletionBlock)completion {

NSLog(@"處理業務C");

// 業務順序: A -> B -> C //    completion(YES);

// 業務順序: C -> B -> A     completion(NO); }

@end ```

2.5.3.2 實際業務使用責任鏈模式方法

``` // //  ViewController.m //  appDesignPattern // //  Created by JackLee on 2022/9/21. //

import "ViewController.h"

import "BusinessA.h"

import "BusinessB.h"

import "BusinessC.h"

import "appDesignPattern-Swift.h"

@interface ViewController ()

@end

@implementation ViewController

  • (void)viewDidLoad {     [super viewDidLoad]; }

  • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {     [self testBusiness]; }

  • (void)testBusiness {

BusinessA businessObjA = [BusinessA new];     BusinessB businessObjB = [BusinessB new];     BusinessC *businessObjC = [BusinessC new];     // 業務順序: A -> B -> C //    businessObjA.nextBusiness = businessObjB; //    businessObjB.nextBusiness = businessObjC;

// 業務順序: C -> B -> A     businessObjC.nextBusiness = businessObjB;     businessObjB.nextBusiness = businessObjA;

// 響應者的處理方法, 責任鏈入口方法     /      1. 當前業務處理掉了, 上拋結果      2. 當前業務沒處理掉, 沿著責任鏈, 指派給下一個業務處理, 如果沒有業務處理, 繼續上拋      3. 對當前業務進行處理      handler:      handled: 業務處理結果      /

// 業務順序: A -> B -> C //    [businessObjA handle:^(BusinessObject *handler, BOOL handled) { // // //    }];

// 業務順序: C -> B -> A     [businessObjC handle:^(BusinessObject *handler, BOOL handled) {         // handler:         // handled: YES     }];    

} ```

發文不易, 喜歡點讚的人更有好運氣👍 :), 定期更新+關注不迷路~

ps:歡迎加入筆者18年建立的研究iOS稽核及前沿技術的三千人扣群:662339934,坑位有限,備註“掘金網友”可被群管通過~