记一次组件化的实践过程

起源

截至目前组件化在 iOS 也已经有了几年的讨论和应用了,笔者从去年开始公司项目也开始慢慢有意识的往组件化靠拢。因不可能组件化而停止业务的开发所以前期准备是在业务开发的同时有意识的封装和抽取整理一些独立于业务的类等。在接下来的几篇文章会大家分享笔者在组件化过程中的想法和遇到的问题,欢迎大家留言讨论。

0x0 前期准备

当你想要进行组件化开发的时候,第一步是考虑将公司的项目进行一个大的拆解,了解到自己公司项目的业务模块,做到拆分时心中有数。而很少有公司直接停滞业务的开发而将人员全量的投入到组件化中,所以我们可以在业务开发的同时就将组件化的需求考虑进去,例如将无关业务的代码单独剥离,

组件化的分层主要是分为三层(抛开三方库),笔者也是采取了此种结构。

在顶层是业务层,这块和公司业务相关每个公司业务层分出来的业务模块都不是完全一样的。这个自行结合公司业务拆分。

中间层是弱业务层(基础业务层),在这层和公司的主业务的关联性应该是弱的,弱业务也就是说和业务的关联性低,但是顶层直接依赖的下层。

底层是与业务完全无关的代码,主要是对三方库的封装及开发过程中的工具的封装。这就意味这套代码不会依赖上层的任何业务,是独立运行的,我们可以对它进行迁移到任意项目中直接使用。这层其实是生命力最强的一层,可以在开发周期中不断的完善,但也是牵一发而动全身的一层,所以我们需要在拆解的时候就要尽可能考虑到上层如何能达到最佳使用(少埋点坑)。

以上就是大概的一个项目的分层情况,各个公司业务不尽相同,但是思路都是大同小异的。

在组件化的前期的过程是很痛苦的,因为项目历史原因,可能会碰到你要抽取项目中有很多底层业务中依赖业务的代码。但是当完成了组件化之后你会感觉整个项目划分的很明确,各层之间依赖关系明确,这对以后的开发有这很好的正面导向。

下面给出一些组件化前期准备的建议:

  • 了解cocoapods的私有库建立及podspec如何编写(可参考cocoapods官网)。

  • 了解组件化中不同中间件的优缺点,技术选型。

  • 开始规避使用 pch 文件。

  • 有意识的规整不同业务的代码。抽离相对独立的下层代码。

  • 下层组件中参数是图片的话,可以考虑直接传递 图片 而非 imageName, 参考我之前写过一个 UIButton 的 Category 方法快速创建按钮,未组件化时代码如下:

+ (UIButton *)buttonWithFrame:(CGRect)frame normalImageName:(NSString *)imageName normalTitle:(NSString *)normalTitle normalTitleColor:(UIColor *) normalTitleColor font:(UIFont *)font;

imageName: 方法会从 mainBundle 中寻找图片,但是当我们组件化后我们的资源会放在不同的 bundle 中所以如果直接使用 imageName 去加载图片的话很大可能是找不到图片。

0x1 cocoapods的私有库

有关如何使用cocoapods建立私有库并上传到自己的git仓库,网上有很多教程。读者可以自行 baidu 或 google 到,一般按照教程建立完一个私有库对整个过程都会有所了解。重复几遍基本上都没有问题。也许在验证spec pod spec lint 过程中后碰到许许多多的问题,但大都可以通过搜索引擎解决。个人建议可以将每个私有库的 pod spec lint ...pod repo push 命令行代码粘贴复制一份放在备忘录中因为之后很长一段时间会和它们打交道。ps: 组件的过程一般都是自底向上的进行的,所以我们可以先从底层开始,同时这个也不会影响到业务的开发。

这里给出制作cocoapods的私有库主要的流程:

  1. pod lib create <组件名> 创建本地代码组件模版库
  2. 将代码放入到 classes文件夹内,资源类文件(图片、Xib)放入 Assets 文件夹内。(注:你也可以使用自定义文件夹,但需要在 spec 中指定。推荐使用默认文件夹)。
  3. cd Example下 执行 pod install, 在编译项目是否通过,如未通过再做修改
  4. 编译通过后修改 podspec 文件
  5. 编译运行通过后,提交代码到远程代码库并打tag.
(可以通过以下命令行或者直接使用类似 sourceTree 的GUI来实现)
  -  git add .
  -  git commit -m “xxx"
  -  git remote add origin 远程代码仓库地址
  -  git push origin master
  -  git tag 版本号 (注:这里的版本号必须和podspec里写的版本号一致)
  -  git push --tags
  1. 通过pod spec lint --verbose --allow-warnings 命令验证podspec索引文件
  2. 验证通过后,pod repo push <本地索引库> <索引文件名.podspec> --verbose --allow-warnings 提交索引文件到远程索引

0x2 项目运行后的问题解决

一般来说在你制作某个组件的时候,编译过程会有很多依赖的报错,因为可能在老的工程中因为使用 pch 文件,很多类都被隐式的引入。而当我们剥离原来的代码,然后就要针对每个编译错误导入不同的头文件,如果是同一个组件/模块时用 #import "xx",如果是依赖其它的模块则使用 #import <xx/xx.h>的方式引入。当我们都编译通过后,可能还会有其它的坑需要我们填,这里给出一些笔者碰到问题的解决方法。如果podspec中使用的是 s.resource_bundles 就会遇到下面几个问题

  • ViewController是xib的时候,如果直接通过 [[xx alloc] init]创建的话,出来的ViewController是空白不会显示任何内容,解决方法时重写 init 方法
- (instancetype)init {

    //在这个路径下找到子bundle的路径 (xxx指的是在podspec文件中的s.name)
    NSBundle *bundle = [NSBundle subBundleWithBundleName:@"xxx" targetClass:[self class]];
    self = [super initWithNibName:NSStringFromClass([self class]) bundle:bundle];
    if (self != nil) {
    // init method
    }
    return self;
}

// 此处是笔者的 NSBundle 的 Category
+ (instancetype)subBundleWithBundleName:(NSString *)bundleName targetClass:(Class)targetClass{
    //并没有拿到子bundle
    NSBundle *bundle = [NSBundle bundleForClass:targetClass];
    //在这个路径下找到子bundle的路径
    NSString *path = [bundle pathForResource:bundleName ofType:@"bundle"];
    //根据路径拿到子bundle
    return path?[NSBundle bundleWithPath:path]:[NSBundle mainBundle];
    
    [bundle pathForResource:@"BXSocialModule" ofType:@"bundle"];
    [bundle pathsForResourcesOfType:@"bundle" inDirectory:bundle.bundlePath];
}
  • 代码中加载图片,前文提过imageName: 方法会从 mainBundle 中寻找图片,所以需要我们指定bundle去加载模块内的图片。
 UIImage *image = [UIImage bundle_imagePathWithName:@"about_phone_icon" bundle:@"BXUserCenterModule" targetClass:self.class];
 
 /// 笔者项目中 UIImage 的 Category
+ (instancetype)bundle_imagePathWithName:(NSString *)imageName bundle:(NSString *)bundle targetClass:(Class)targetClass {
    NSBundle *currentBundle = [NSBundle bundleForClass:targetClass];
    NSString *bundlePath = [currentBundle pathForResource:bundle ofType:@"bundle"];
    return [UIImage imageNamed:imageName inBundle:[NSBundle bundleWithPath:bundlePath] compatibleWithTraitCollection:nil];
}
  • 加载 Xib 的 UIView 或者 Cell,同理我们需要从每个模块对应的bundle中加载。
XX *view = [XX loadCustomNibFromBundle:@"bundleName"];

/// UIView 的 Category方法,
+ (instancetype) loadCustomNibFromBundle:(NSString *)bundleName {
    NSBundle *bundle = [NSBundle subBundleWithBundleName:bundleName targetClass:[self class]];
    return [[bundle loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
}

/// TableViewCell
 NSBundle *bundle = [NSBundle subBundleWithBundleName:@"XX" targetClass:self.class];
 [_tableView registerNib:[UINib nibWithNibName:@"cellName" bundle: bundle] forCellReuseIdentifier:kBlockMachineDetailCellIdentifier];

0x3 组件完成后的开发流程

美团外卖iOS多端复用的推动、支撑与思考

随着一个个组件的拆分,我们的整体复用度有明显提升,但开发效率却意外的受到了影响。多库开发在版本的发布与集成中增加了很多人工操作:依赖冲突、lock文件冲突等问题都阻碍了我们的开发效率进一步提升,而这就是之前“关于组件化”中提到的副作用。

笔者原来项目开发流程采用的是 gitFlow 的开发流程,所以组件化后的开发流程也将会继续采用 gitFlow。关于 gitFlow 讨论可以参考 阮一峰老师的Git 工作流程

目前开发流程中会在组件化后项目的壳工程中的 podfile 文件中将项目组件所有的依赖项目指定 tag 版本。当开发需求过来之后,针对需求属于哪个业务模块创建对应的业务 Feature 分支。而在壳工程的 podfile 中改变业务模块依赖本地路径。等开发测试通过后业务模块打 tag。壳工程在改动 podfile 依赖tag。

以上都是手动打包发布的流程。笔者之前项目使用的是 Jenkins + FastLane 自动打测试包,但组件化完成后脚本的编写可能更加复杂所以目前还停留在手动上。其实组件化也是倒逼 自动发版与自动集成 的演进。这块可能会在以后继续完善。

0x4待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FY_Chao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值