OC底层面试题-组件化的创建(上)
前言
本来想着写界面优化的,但是因为前段时间项目比较忙,就一直没弄!加上最近项目里也在整理组件化的东西,所以也就决定写篇关于组件化的文章。
组件化
谈到组件化,首先想到的是解耦
,模块化
。其实组件化就是将模块化抽离
,分层
,并制定模块间
的通讯
方式,从而实现解耦
的一种方式,主要运用在团队开发
组件化的有点
组件化主要有一下有点
- 1.
模块间解耦
- 2.
模块重用
- 3.
提高团队协作开发效率
- 4.
方便进行单元测试
当项目因为各种需求,越来越大时,如果此时的各个模块之间是互相调用
,即你中有我,我中有你
这种情况时,会造成高耦合
的情况。一旦我们需要对某一块代码进行修改
时,就会造成影响范围广
,功能多
的问题,导致项目难以维护
其问题主要体现在一下几个方面
- 1.修改某个功能时,同时需要修改其他模块的代码,因为在其他模块中有该模块的引用。可以理解为
高耦合导致代码修改困难
- 2.模块对外接口不明确,甚至暴露了本不该暴露的私有接口,修改时费时费力。可以理解为
接口不固定导致的接口混乱
- 3.高耦合代码产生的后果就是会影响团队其他成员的开发,
产生代码冲突
- 4.当模块需要重用到其他项目时,
难以单独抽离
- 5.模块间耦合的忌口导致接口和依赖关系混乱,
无法进行单元测试
所以为了解决以上问题,我们需要采用更规范的方式
来降低模块间的耦合度
,然后组件化就应运而生,组件化
也可以理解为模块化
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
组件化的适用说明
上面说了组件化的好处,但是因为组件化
也是需要
一定成本
的,需要花费时间设计接口
、分离代码
等,所以并不是所有
的项目都需要组件化
。如果你的项目有以下3个以上
特征就不需要组件化
:
- 1.
项目较小
,模块间交互简单,耦合少 - 2.项目
没有被多个外部模块引用
,只是一个单独的小模块 - 3.
模块不需要重用
,代码也很少被修改
- 4.团队
规模很小
- 5.
不需要编写单元测试
如果你的有以下3个以上
特征,说明你就必须要考虑进行组件化
了:
- 1.模块
逻辑复杂
,多个模块之间频繁互相引用
- 2.项目
规模逐渐变大
,修改代码
变的越来越困难
(这里可以理解为:修改一处代码,需要同时修改其他多个地方) - 3.团队
人数变多
,提交的代码
经常和其他成员冲突
- 4.项目
编译耗时较大
- 5.模块的
单元测试
经常由于其他模块
的修改而失败
组件化方案
组件化的指标
一个项目经过组件化后如何来评判项目组件化是否彻底或者说是否优秀,可以通过以下几个方面:
- 1.
模块之间没有耦合
,模块内部的修改不会应该其他模块 - 2.
模块可以单独编译
- 3.
模块间数据传递明确
- 4.
模块
可以随时
被另一个提供了相同功能的模块替换
- 5.
模块对外接口清晰且易维护
- 6.当
模块接口改变
时,此模块的外部代码
能够被高效重构
- 7.尽量
用最少的修改和代码
,让现有的项目实现模块化
- 8.支持
OC和Swift,以及混编
前4条主要用于衡量一个模块是否真正解耦
,后4条主要用于衡量在项目中实践中的易用程度
组件化原则
一般一个项目重要分为三层:业务层
,通用层
,基础层
,具体如下图所示:
在进行组件化时,有一下几点需要说明:
- 1.
只能上层对下层依赖
,不能下层对上层的依赖
,因为下层是对上层的抽象 - 2.项目
公共代码资源下沉
- 3.
横向的依赖尽量少有
,最好横向依·下沉至通用模块
或者基础模块
组件化方案
目前常用的组件化方案主要有两种:
- 1·
本地组件化
:主要是通过在工程中创建library
,利用cocoapods的workspec
进行本地管理
,不需要
将项目上传git
,而是直接
在项目中以framework的方式
进行调用
- 2.
cocoapods组件化
:主要是利用cocoapods
来进行模块
的远程管理
,需要将项目上传git
(这里的组件化模块
分为公有库
和私有库
,对公司而言
,一般是私有库
)
下面我们来介绍下本地组件化
本地组件化
1.创建主工程
- 首先创建主工程
- 集成cocopods,进行本地管理
$ cd 项目目录
$ pod init
复制代码
- 编辑podfile,并执行pod install
2.创建组件
可以创建自己的模块:
-
主工程
:主要实现表层业务代码 -
Base
:基类封装 -
Tools
:工具(字符串,颜色,字体等) -
Service
:服务层,封装业务工具类,例如网络层服务、持久化服务等 -
Pods
:第三方依赖
下面我们进行简单的模块创建,我们以Service
为例:
- 1.选择选择
new -> project -> iOS -> Framework
,新建一个模块
- 2.选择正确的
Group
和WorkSpace
(这里需要注意一点:创建的library最好放在主工程根目录
下,否则后续podfile执行pod install时会报错
)
- 3.将创建的
library
的Build Settings -> Mach-O Type
修改为静态库Static Library
- 4.在LjComponentA中写需要的代码
- 5.在
Build Phases -> Headers -> Public
中将新建的文件添加到public,这样主工程才能访问该文件
- 6.在主工程中,选择
target -> Linked Binary With Libraries
中添加LjComponentA,只需要build主工程,library能够自动联编
- 7.主项目调用
这里需要注意的是,子library之间的互相调用
,与主工程调用library类似
,主需要添加依赖
、暴露header
即可
- 8.使用cocoapods管理三方依赖
假设我们需要在LjComponentA中封装网络层代码
,需要用到三方库Alamofire
,在podfile中进行如下修改
然后pod install
,成功后就可以在LjComponentA使用Alamofire库
了
到此,一个本地组件化的模块就配置完成了
cocoapods组件化
除了本地组件化,还可以使用cocoapods,其原理如下图所示这里还是以本地组件化中的Service为例
创建私有仓库
- 1.在github上创建一个TestPodsComponent仓库
具体步骤:登录github-->点击右上角“+”
-->选择new repository
-->输入Repository name为TestPodsComponent,选择仓库类型为private
,点击Create repository
。
- 2.将私有仓库添加到本地~/.cocoapods/repos目录
pod repo add TestPodsComponent https://github.com/xxxx/TestPodsComponent.git
创建pods 工程,即组件化工程
- 1.使用终端创建
TestPodsComponent
模块
pod lib create TestPodsComponent
- 2.根据提示一次选择
iOS,Swift,Yes,None,No
- 3.进入模块目录,将需要的文件
拷贝
到TestPodsComponent -> Classes
中
- 4.执行
pod install
,会将Classes内容更新到pods中
配置pods工程
修改模块的配置文件,即TestPodsComponent.podspec
- 如果需要依赖第三方库,需要配置s.dependency
s.dependency 'AFNetworking' # 依赖AFNetworking
- 如果模块间需要相互引用,同样需要配置s.dependency,以LjBase为例,需要引用LjService
//********1、修改 podspec 文件
s.dependency 'LjServices'
//********2、修改 podfile 文件
pod 'LjServices', :path => '../../LjServices'
- 如果需要加载资源,例如图片、json、bundle文件等
- 1.在模块的Assets文件夹 中添加资源文件
- 2.在specs里配置资源路径(必须配置!!否则无法读取资源)
- 3.访问时需要指定资源文件路径
//*****1、修改 podspec 文件
s.resource_bundles = {
'LjBase' => ['LjBase/Assets/*']
}
//*****2、使用
let bundlePath: String = Bundle.init(for: dynamicClass.self).resourcePath! + "/LjBase.bundle"
let bundle = Bundle(path: bundlePath)
if let path = bundle?.path(forResource: "mouse", ofType: "jpg"){
self.imgView.image = UIImage(contentsOfFile: path)
}
同理,模块中的xib,json文件的获取方式也是一样的
提交至git
这里提交至git的模块是pods工程才可以,以TestPodsComponent
为例 我们刚才在git建了一个私有库:TestPodsComponent
- 执行以下终端命令
【注意】:在执行完git add .后,最好执行下git status,查看下状态
验证podspec文件
执行终端命令pod spec lint
pod spec
相对于pod lib
会更
为精确
pod lib
相当于只验证
一个本地仓库
pod spec
会同时验证本地仓库
和远程仓库
加上 ----allow-warnings为了移除警告
【如果出现报错:- ERROR | [iOS] file patterns: The ’source_files‘ pattern did not match any file.】
这是由于未在cocoapos中找到相应的文件夹
,可以执行以下操作
前往 /Users/你的用户名/Library/Caches/CocoaPods/Pods/External/你的项目名
进入最新一个日志文件,将文件放置和source_files文件路径一致
提交到私有仓库
执行以下命令:pod repo push [本地Spec Repo名称][podspec文件路径]提交成功后,下面就可以使用了
使用
- 建个新的新的工程,在新项目的podfile里添加:
- 执行pod install
至此我们对cocoapods组件化已经完成,下篇我们要介绍下组件化之间的通信
拓展
上面我们看到TestPodsComponent.podspec内容:s.name,s.version下面我们来解释下每项都是什么意思
-
s.name
= "TestPodsComponent"项目名称
-
s.version
= "0.1.0"版本号与你仓库的标签号对应
-
s.summary
= ' this is a Test Pods Component.'项目简介
-
s.license
= { :type => "MIT", :file => "LICENSE" }开源许可证
-
s.homepage
= "git.com/xxx"仓库的主页
-
s.source
= { :git => "git.com/xxx.git", :branch => 'master', :tag => "#{s.version}" }你的仓库地址,不能用SSH地址
-
s.source_files
= "TestPodsComponent/Classes/.{h,m}或者TestPodsComponent/Classes/"代码的位置
-
s.requires_arc
= true是否启用ARC
-
s.platform
= :ios, "9.0"平台及支持的最低版本
-
s.xcconfig
= { 'OTHER_LDFLAGS' => '-Objc -all_load -fobjc-arc' ,'ENABLE_BITCODE' => 'NO'}target的相关设置
-
s.author
= { "author" => "xxx.com" }作者信息, 开源上线后可能发送邮件
-
s.frameworks
= "UIKit", "Foundation"支持的框架
-
s.resource_bundles
= {'TestPodsComponent' => ['TestPodsComponent/Assets/*.png']}资源文件
-
s.dependency
= "AFNetworking"依赖库,有多少依赖库就写多少 s.dependency
-
s.dependency
'AFNetworking', '~> 4.0'可以指定版本