起源
隨著產(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
- 進行CocoaPods
pod repo add CoreLib git@git.test/CoreLib.git
pod repo push CoreLib CoreLib.podspec --allow-warnings
模塊拆分

基礎組件庫
基礎組件庫存放一些最基本的工具類,比如金額格式化、手機號/身份證/郵箱的有效校驗、加密工具等,實質(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è)務線非常方便,希望能夠幫助到大家。最后住大家新年快樂。