CocoaPods详解5:组件化之搭建组件化项目框架

https://www.cnblogs.com/lxlx1798/p/11934939.html

一, 组件化: 将高度耦合的业务组件和功能组件,拆分成互相独立的各个组件。

二, 各个组件该如何进行拆分

关于组件该如何拆分,这个没有一个完整的标准,因为每个公司的业务场景不一样,对应衍生出来的各个业务模块也就不一样,所以业务组件间的拆分,这个根据自己公司的业务模块来进行合理的划分即可。这里我们来说下整个工程的组件大致的划分方向

  • 1,项目主工程:当我们工程完全使用组件化架构进行开发后,我们会惊奇的发现我们的主工程就成了一个空壳子工程。因为所有的主工程呈现出来的内容都被拆分成了各个独立的业务组件了,包括各个工具组件也是各自互相独立的。这样我们发现开发一个完整的APP就像是搭建乐高积木一样,各个部件都有,任我们随意的组合搭建,这样是不是感觉很爽。
  • 2,业务组件:业务组件就是我们上面示例图所示的各个独立的产品业务功能模块,我们将其封装成独立的组件。例如示例Demo中的电子发票业务组件,业务组件A,业务组件B。我们通过组装各个独立的业务组件来搭建一个完整的APP项目。
  • 3,基础工具类组件:基础工具类是各个互相独立,没有任何依赖的工具组件。它们和其它的工具组件、业务组件等没有任何依赖关系。这类组件例如有:对数组,字典进行异常保护的Safe组件,对数组功能进行扩展Array组件,对字符串进行加密处理的加密组件等等。
  • 4,中间件组件:这个组件比较特殊,这个是我们为了实现组件化开发而衍生出来的一个组件,上面示例图中的中间调度者就是一个功能独立的中间件组件。
  • 5,基础UI组件:视图组件就比较常见了,例如我们封装的导航栏组件,Modal弹框组件,PickerView组件等。
  • 6,业务工具组件:这类组件是为各个业务组件提供基础功能的组件。这类组件可能会依赖到其他的组件。例如:网络请求组件,图片缓存组件,jspatch组件等等
      
    至于组件的拆分颗粒度,这个着实不好去断定,因人而异,不同的需求功能复杂度拆分出来的组件大小也不尽相同

三, 在讲如何从零到一来实现一个组件化架构项目前,我们需要熟练掌握使用pod来制作组件库。下面我们就围绕提供的组件化示例项目来展开讲解。

首先,我们来看示例Demo中包含哪些业务组件(如下图所示:):

image

>

示例Demo中,我提供了三个业务组件来作为演示效果,其中业务模块A和业务模块B是临时业务模块组件,电子发票业务组件时真实的企业需求功能组件。

我们再来看下示例Demo中都提供了哪些工具组件(如下图所示)

注意了:这里提供的6个工具组件也都是作者已经封装好的功能组件

image.png

四,详细操作步骤

  • 第一步: 我们先创建一个空的iOS工程项目:MainProject,这个空项目作为我们的主工程项目,就是上面所说的壳子工程项目,然后初始化pod,这里不清楚pod的使用的小伙伴们请自行查阅资料。

  • 第二步: 我们创建一个空工程项目:ModuleA,这个ModuleA 项目作为我们的业务A组件。然后我们初始化pod,初始化podspec文件。

  • 第三步: 我们创建一个空工程项目:ModuleB,这个ModuleB 项目作为我们的业务B组件。然后我们初始化pod,初始化podspec文件。

  • 第四步: 我们创建一个空工程项目:ComponentMiddleware,这个项目就是我们上面所说的中间调度者。然后我们初始化pod,初始化podspec文件。

  • 第五步: 我们创建一个空工程项目: ModuleACategory,这个工程是对应业务组件A的一个分类工程。然后我们初始化pod,初始化podspec文件。

  • 第六步:

    我们创建一个空工程项目: ModuleBCategory,这个工程是对应业务组件B的一个分类工程。然后我们初始化pod,初始化podspec文件。

    好了,上面的主工程和两个业务组件工程,以及两个组件分类工程都已创建完毕,下面我们来讲解他们各个之间如何工作的。我就从主工程加载业务组件开始往下捋,顺藤摸瓜式的引出每个工程的用意。

  • 第七步:

    我们在主工程MainProject的Podfile中引入我们的业务组件B工程ModuleB,以及引入我们的ModuleB的分类工程:ModuleBCategory。然后我们pod install。这时已将这两个组件库引入到我们的主工程中了。

    示例代码如下:

# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/guangqiang-liu/GQSpec.git'

target 'GQComponentDemo' do
  
  pod 'ModuleB'
  pod 'ModuleBCategory'
end

然后我们在主工程中添加一个按钮事件,这个事件是点击 push 到业务组件B的 页面。
示例代码如下:

#import <ModuleBCategory/ComponentScheduler+ModuleB.h>

- (void)moduleB {
    UIViewController *VC = [[ComponentScheduler sharedInstance] ModuleB_viewControllerWithCallback:^(NSString *result) {
        NSLog(@"resultB: --- %@", result);
    }];
    [self.navigationController pushViewController:VC animated:YES];
}
  • 第八步:
    上面第七步中,我们用到了ModuleBCategory 这个分类工程。这个工程我们只对外暴露了两个文件。这两文件是上面的中间调度者的分类,也就是说是中间件的分类。我们先来看下这个分类文件的.h 和.m 实现。

    .h

#import "ComponentScheduler.h"

@interface ComponentScheduler (ModuleB)
 - (UIViewController *)ModuleB_viewControllerWithCallback:(void(^)(NSString *result))callback;
@end

.m

#import "ComponentScheduler+ModuleB.h"

@implementation ComponentScheduler (ModuleB)
 - (UIViewController *)ModuleB_viewControllerWithCallback:(void(^)(NSString *result))callback {
  NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
  params[@"callback"] = callback;
  return [self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];
 }
@end

我们发现这个分类实现非常的简单,就是对外暴露一个函数,然后执行

[self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];

并将执行的返回值返回出去。
这个分类的作用你可以理解为我们提前约定好Target的名字和Action的名字,因为这两个名字中间件组件中会用到。
上面的performTarget:action:params:shouldCacheTarget函数是中间件提供的函数。因为ModuleBCategory 是 ComponentScheduler(中间件)的分类文件,所以可以调用到这个函数啦。

在ModuleBCategory 工程中需要引用到了中间件工程所以我们需要在ModuleBCategory 的Podfile文件中引用 中间件组件

示例代码如下:

# Uncomment the next line to define a global platform for your project
platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/guangqiang-liu/GQSpec.git'

target 'ModuleB-Category' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
# use_frameworks!

# Pods for ModuleB-Category

pod 'ComponentScheduler'

end
  • 第九步:
    因为上面第八步中引用到中间件工程,这里我们就来看下中间件工程到底做了什么工作。还记得上面第八步中,我们调用了一个中间件提供的函数:performTarget:action:params:shouldCacheTarget吧,这个是中间件核心函数。

核心函数代码块如下:

image

还记得上面第八步中,我们调用这个函数传递的参数吧,我们在把调用代码拿过来看下

[self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];

我们可以看到 TargetName是我们传递的 ModuleBaction是我们传递的viewController,然后我们将 这两个参数传给了下面的函数:

[self safePerformAction:action target:target params:params];

我们来看下这两个参数的值具体是什么:

image

这个函数最终调用到苹果官方提供的函数:

[target performSelector:action withObject:params];

看到 performSelector: withObject:大家应该就比较熟悉了,iOS的消息传递机制。

[Target_ModuleB performSelector:Action_viewController withObject:params];

上面这行伪代码意思是: Target_ModuleB这个类 调用它的 Action_viewController:方法,然后传递的参数为 params

细心的小伙伴们就会发现,我们没有看到过哪里有这个Target_ModuleB类啊,更没有看到Target_ModuleB调用它的 Action_viewController:方法啊。

是的,这个Target_ModuleB类和类的Action_viewController方法就在第十步中讲解到。

  • 第十步:
    终于到了最后一步了,写的好艰辛,嗯,小伙们不要捉急,快了,快讲完了
    细心的小伙们发现,我们上面讲的9步中,好像都没有提业务组件B的东西。是的,业务组件B除了提供组件B的业务功能外,业务组件B还需要为我们提供一个Target文件。

我们先来看下业务组件B的业务代码:
示例代码如下:

#import "ModuleBViewController.h"
#import "PageBViewController.h"

@interface ModuleBViewController ()

@end

@implementation ModuleBViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"我是模块B业务组件";
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.frame = CGRectMake(0, 0, 300, 100);
    btn.backgroundColor = [UIColor greenColor];
    btn.center = self.view.center;
    [btn setTitle:@"模块B业务功能组件" forState: UIControlStateNormal];
    [btn addTarget:self action:@selector(push) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];
}

- (void)push {
    PageBViewController *VC = [[PageBViewController alloc] init];
    [self.navigationController pushViewController:VC animated:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

我们发现,业务组件B的业务代码也很简单,就是做一个push 跳转操作,从PageA 控制器跳转到 PageB 控制器。 这个没有什么好讲的

我们再来看上面提到的target文件

示例代码如下:

.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Target_ModuleB : NSObject

- (UIViewController *)Action_viewController:(NSDictionary *)params;

@end

.m

#import "Target_ModuleB.h"
#import "ModuleBViewController.h"

@implementation Target_ModuleB

- (UIViewController *)Action_viewController:(NSDictionary *)params {
    ModuleBViewController *VC = [[ModuleBViewController alloc] init];
    return VC;
}
@end

从上面的实现文件中,我们可以看到,Target文件的作用也很简单,就是为我们提供导航跳转的目标控制器实例对象。这里的目标控制器实例就是业务组件B的ModuleBViewController实例。

细心的小伙伴们发现,咦!我们在第九步中打印出来的targetaction不就正是Target文件的Target_ModuleBAction_viewController:

上面我们只是串讲了业务组件B的一系列流程,业务组件A的用法和业务组件B的用法一样,如果后面再有业务组件C,D,都是一样的道理,就不再一一讲解了。

好了,现在小伙伴们应该看懂了这一连串的工作流程了吧,如果还没有看懂,可以看看Casa的讲解CTMediator。作者建议直接运行提供的示例Demo项目进行调试,这样便于理解各个组件之间的关系。

五,总结

上面我们讲解的只是简单的项目组件化架构的基础框架搭建,但是在真正的企业开发中,我们只搭建这样一个简单项目框架结构还远远不能满足需求的开发,我们还需要在项目框架中添枝加叶来满足现有需求。

**最后,我们再来看张组件化完整的架构图:

image

**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容