組件化開發(fā)實踐

起源

隨著產(chǎn)品線和業(yè)務的增加,公司的業(yè)務的發(fā)展需要,現(xiàn)在的一個項目都多個產(chǎn)品線和業(yè)務線,每個產(chǎn)品線和業(yè)務線有一部分相同的流程,也有部分不同的。但是不同的產(chǎn)品線和業(yè)務線要在整個工程內(nèi),代碼上的管理、開發(fā)效率、分支管理、產(chǎn)品上線的時間處理都會有所限制。這個時候傳統(tǒng)的MVC或者MVVM架構已經(jīng)無法高效的管理工程代碼,需要用一種技術來更好地管理工程,需要對代碼進行重構,在主工程中進行組裝拆分,模塊化管理,在形成一個完整的項目。

認識組件化

由于產(chǎn)品線和業(yè)務的增多、業(yè)務的復雜性高,導致代碼的邏輯性也增加了。為了減輕后期的開發(fā)和維護成本,這時候要進行業(yè)務的分類。
組件化開發(fā)就是講龐大繁瑣的工程項目,進行系統(tǒng)性的分析。根據(jù)功能或者熟悉進行分解,拆分成單一獨立的功能模塊或者組件。再根據(jù)項目和業(yè)務的需求,按照某種方式,進行組裝成一個完整的業(yè)務邏輯工程。

優(yōu)點
  • 解決代碼耦合的問題
  • 增強代碼的復用性
  • 工程的易管理性
  • 項目結構和代碼邏輯清晰
  • 拆分粒度小
  • 快速集成
  • 迭代效率高
  • 能做單元測試
缺點
  • 對開發(fā)人員,只能使用相同的開發(fā)模式
  • 難以做集成測試
  • 不適合單一的產(chǎn)品線和業(yè)務項目

組件化的最終目的是將項目進行基礎、功能、業(yè)務的邏輯拆分,形成一個組件庫,宿主工程中能在組件庫中查找需要的功能,組裝成一個完整的項目。

使用組件化工具

組件以每個pod庫的方式的存在的。組合組件是以CocoaPods方式添加各個組件。這時候我們需要用到CocoaPods遠程私有庫,制作遠程私有庫,發(fā)布到公司的gitlab或者GitHub上,使工程能夠pod下載下來。

  • Git的基礎命令
echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/c/test.git
git push -u origin master
  • 遠程私有庫制作
    1.創(chuàng)建組件項目
pod lib create ProjectName

2.使用git管理

echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/c/test.git
git push -u origin master

3.編輯 podspec文件

vim CoreLib.podspec
Pod::Spec.new do |s|
  s.name             = '組件工程名'
  s.version          = '0.0.1'
  s.summary          = 'summary'

  s.description      = <<-DESC
  description
                       DESC

  s.homepage         = '遠程倉庫地址'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '作者' => '作者' }
  s.source           = { :git => '遠程倉庫地址', :tag => s.version.to_s }

  s.ios.deployment_target = '8.0'

  s.source_files = 'Classes/**/*.{swift,h,m,c}'
  s.resources = 'Assets/*'
  
  s.dependency 'AFNetworking', '~> 2.3'
end

3.創(chuàng)建tag

//create local tag
git tag '0.0.1'
或
git tag 0.0.1

//local tag push to remote
git push --tags
或
git push origin 0.0.1

//delete local tag
git tag -d 0.0.1

//delete remote tag
git tag origin :0.0.1

4.驗證組件項目

pod lib lint --allow-warnings --no-clean
  1. 進行CocoaPods
pod repo add CoreLib git@git.test/CoreLib.git
pod repo push CoreLib CoreLib.podspec --allow-warnings

模塊拆分

模塊拆分&組裝圖.png
基礎組件庫

基礎組件庫存放一些最基本的工具類,比如金額格式化、手機號/身份證/郵箱的有效校驗、加密工具等,實質(zhì)就是不會依賴業(yè)務,不會和業(yè)務牽扯的文件。

功能組件庫

分享的封裝,支付、定位、統(tǒng)計、推送二次封裝、支付等,一次開發(fā),方便以后快速基礎的功能。

業(yè)務組件庫

登錄組件、實名組件、搜索組件、借款組件、還款組件、各條產(chǎn)品線組件等。

中間件(組件通訊)

各個業(yè)務組件拆分出來后,組件之間的通訊、傳參、回調(diào)就要考慮了,此時就需要一個組件通訊的工具類來處理。

CocoaPods遠程私有庫:

每個拆分出去的組件存在的形式都是以Pod的形式存在的,并能達到單獨運行成功。

宿主工程

宿主工程就是一個殼,在組件庫中尋找這個工程所需要的組件,然后拿過來組裝成一個工程。

組件之間的通訊

在將業(yè)務控制器拆分出去后,如果一個組件要調(diào)用另一個組件里面的控制器,平常的做法是直接==#import "控制器頭文件"==,現(xiàn)在在不同的組件里面是無法import的,那該怎么做呢?答案就是使用==消息發(fā)送機制==。

思路:
  • 每個業(yè)務組件庫里面會有一個控制器的配置文件(路由配置文件),標記著每個控制器的key;
  • 在App每次啟動時,組件通訊的工具類里面需要解析控制器配置文件(路由配置文件),將其加載進內(nèi)存;
  • 在內(nèi)存中查詢路由配置,找到具體的控制器并動態(tài)生成類,然后使用==消息發(fā)送機制==進行調(diào)用函數(shù)、傳參數(shù)、回調(diào),都能做到。
((id (*)(id, SEL, NSDictionary *)) objc_msgSend)((id) cls, @selector(load:), param);
((void(*)(id, SEL,NSDictionary*))objc_msgSend)((id) vc, @selector(callBack:), param);

Or

[vc performSelector:@selector(load:) withObject:param];
[vc performSelector:@selector(callBack:) withObject:param];
好處:

解除了控制器之間的依賴;
使用iOS的消息發(fā)送機制進行傳參數(shù)、回調(diào)參數(shù)、透傳參數(shù);
路由表配置文件,能實現(xiàn)界面動態(tài)配置、動態(tài)生成界面;
路由表配置文件放到服務端,還可以實現(xiàn)線上App的跳轉邏輯;
將控制器的key提供給H5,還可以實現(xiàn)H5跳轉到Native界面;

組件化后的資源加載

新項目已開始就采用組件化開發(fā),還是特別容易的,如果是老項目重構成組件化,那就比較悲劇了,OC項目重構后,app包里面會有一個==Frameworks==文件夾,所有的組件都在這個文件夾下,并且以==.framework==(比如:WDComponentLogin.framework)結尾。在工程中使用的==xib、圖片==,使用正常的方式加載,是加載不到的,原因就是xib、圖片的路徑==(工程.app/Frameworks/WDComponentLogin.framework/LoginViewController.nib、工程.app/Frameworks/WDComponentLogin.framework/login.png)==發(fā)生了變化。

/**
 從主工程mainBundle或從所有的組件(組件名.framework)中加載圖片

 @param imageName 圖片名稱
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName;

/**
 從指定的組件中加載圖片,主要用于從當前組件加載其他組件中的圖片

 @param imageName 圖片名稱
 @param frameworkName 組件名稱
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName frameworkName:(NSString *_Nonnull)frameworkName;

/**
 從指定的組件的Bundle文件夾中加載圖片,主要用于從當前組件加載其他組件Bundle文件夾中的圖片

 @param imageName 圖片名稱
 @param bundleName Bundle文件夾名
 @param frameworkName 組件名稱
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName bundleName:(NSString *_Nonnull)bundleName frameworkName:(NSString *_Nonnull)frameworkName;

/**
 從主工程mainBundle的指定Bundle文件夾中去加載圖片

 @param imageName 圖片名稱
 @param bundleName Bundle文件夾名
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName bundleName:(NSString *_Nonnull)bundleName;

/**
 從指定的組件(組件名.framework)中加載圖片
 說明:加載組件中的圖片,必須指明圖片的全名和圖片所在bundle的包名

 @param imageName 圖片名稱
 @param targetClass 當前類
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName targetClass:(Class _Nonnull)targetClass;

/**
 從指定的組件(組件名.framework)中的Bundle文件夾中加載圖片
 說明:加載組件中的圖片,必須指明圖片的全名和圖片所在bundle的包名

 @param imageName 圖片名稱
 @param bundleName Bundle文件夾名
 @param targetClass 當前類
 @return 返回查找的圖片結果
 */
+ (UIImage *_Nullable)loadImageNamed:(NSString *_Nonnull)imageName bundleName:(NSString *_Nonnull)bundleName targetClass:(Class _Nonnull)targetClass;

/**
 加載工程中的nib文件
 eg:[_tableview registerNib:[WDLoadResourcesUtil loadNibClass:[WDRepaymentheaderView class]] forHeaderFooterViewReuseIdentifier:kWDRepaymentheaderView]
 @param class nib文件名
 @return 返回所需要的nib對象
 */
+ (UINib *_Nullable)loadNibClass:(NSObject *_Nonnull)targetClass;
控制器加載方式
@implementation BaseViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSString *classString = [[NSStringFromClass(self.class) componentsSeparatedByString:@"."] lastObject];
    if ([[NSBundle bundleForClass:[self class]] pathForResource:classString ofType:@"nib"] != nil) {
        //有xib
          return [super initWithNibName:classString bundle:[NSBundle bundleForClass:[self class]]];
    }else if ([[NSBundle mainBundle] pathForResource:classString ofType:@"nib"] == nil) {
        //沒有xib
        return [super initWithNibName:nil bundle:nibBundleOrNil];
    } else {
        return [super initWithNibName:(nibNameOrNil == nil ? classString : nibNameOrNil) bundle:nibBundleOrNil];
    }
}
@end
UIView視圖加載方式:

OC版本

+ (id)loadFromNIB {
    if ([[NSFileManager defaultManager] fileExistsAtPath:[NSBundle bundleForClass:[self class]].bundlePath]) {
        return [[[NSBundle bundleForClass:[self class]] loadNibNamed:[self description]
                                                               owner:self
                                                             options:nil] lastObject];
    }else{
        return [[[NSBundle mainBundle] loadNibNamed:[self description] owner:self options:nil] lastObject];
    }
    
}

+ (id)loadFromNIB:(NSInteger)index {
    if ([[NSFileManager defaultManager] fileExistsAtPath:[NSBundle bundleForClass:[self class]].bundlePath]) {
        return [[NSBundle bundleForClass:[self class]] loadNibNamed:[self description]
                                                              owner:self
                                                            options:nil][index];
    }else{
        return [[NSBundle mainBundle] loadNibNamed:[self description] owner:self options:nil][index];
    }
    
}

Swift版本

// MARK: - 通過nib加載視圖
@objc public static func loadFromNIB() -> UIView! {
    return (Bundle(for: self.classForCoder()).loadNibNamed(self.description().components(separatedBy: ".")[1], owner: self, options: nil)?.first as? UIView)!
}

總結

組件化dome 下載地址https://github.com/xiaoyang521style/MainProject
組件化開發(fā)對于多業(yè)務線非常方便,希望能夠幫助到大家。最后住大家新年快樂。

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

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

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