组件化一般是把工程分层拆成不同的组件,以达到解耦,模块复用,便于单元测试,编译速度优化等效果,最终目的是为了提高开发质量和效率。当然,组件化是有一定成本的,在组件化之前要考虑清楚当前的项目情况是否适合组件化,收益能否覆盖开发成本。规模较小,模块没太多复用需求的项目,就没必要进行组件化。
分层后,一般使用Cocoapods来封装组件,通过路由来进行不同业务模块间的解耦调用。其中系统框架层,公有pod库层是本来就有的。其它层的拆分逻辑分别是:
cocoapods只支持在Podfile中以path方式依赖pod,在podspec中是不支持的,所以本地pod最多只能有一层。(你也可以写插件支持多层)
这样分层后,当有新的业务需求时,我们只需要创建一个本地pod,写入pod依赖,就可以快速的进入业务开发状态,因为没有其它模块的干扰,编译和调试的速度会得到极大的提升,同时也避免了模块之间的耦合。
通过上述内容,可以知道有三种类型的组件:公有pod,私有pod,本地pod。不管什么类型,都建议使用podlibcreatePodName命令来创建组件,在它生成的组件模版基础上,可以很方便的进行开发。
#因为依赖的静态库不支持模拟器arm64架构,设置当前这个pod不支持arm64,以避免podliblint无法通过s.pod_target_xcconfig={'EXCLUDED_ARCHS[sdk=iphonesimulator*]'=>'arm64'}#单纯设置pod_target_xcconfig只是设置当前这个pod不支持arm64,这里把这些pod的上层设置为不支持arm64,兼容这种问题s.user_target_xcconfig={'EXCLUDED_ARCHS[sdk=iphonesimulator*]'=>'arm64'}对于公有pod,负责任的做法是没有任何错误和警告后再进行发布,如果由于各种客观原因,实在无法去除警告,可以加入--allow-warnings参数来推送:podtrunkpush[NAME.podspec]--allow-warnings。如果添加--skip-import-validation参数来逃避验证,则显得有些不负责任了。
另外,在CocoaPods的1.8版本,将默认的specrepo设为了CDN源,以提高pod的速度。刚发布的公有pod版本,可能要几个小时后才能被同步到CDN源,导致刚发布时调用podinstall--repo-update没法找到新发布的pod库,这时可以通过指定源来解决这个问题,样例如下:
私有pod一般通过私有repo来进行管理,这样才方便做版本管理和使用缓存。私有repo创建命令是podrepoaddREPO_NAMESOURCE_URL,其中SOURCE_URL就是私有repo的git地址。创建私有repo后,通过podrepopushREPO_NAMESPEC_NAME.podspec命令来发布私有pod到私有repo。需要注意的是私有pod和公有pod的发布命令并不一样,分别是:
#公有pod发布命令podtrunkpushSPEC_NAME.podspec#私有pod发布命令podrepopushREPO_NAMESPEC_NAME.podspec发布后,需要在Podfile中加入私有repo源,才能找到私有pod并安装成功。
如果私有pod中依赖了非公有源的pod,在podliblint时会出现这类报错:
本地pod与主工程一起被同一个git仓库管理,不需要单独进行版本管理,也不需要push,而是在Podfile中直接以path的方式进行引入,样例如下:
#---Podfile文件中--pod'你的本地pod',:path=>'../本地pod路径/你的本地pod目录名'Pod组件中使用资源的坑在pod中,经常会出现需要使用图片,xib,json文件等资源的场景,建议使用resource_bundles来配置使用这些资源,以名为DTVideo的pod库为例:
#--DTVideo.podspec文件中--s.resource_bundles={'DTVideoAssets'=>['DTVideo/{Assets,Classes}/**/*.{xib,xcassets}']}这样配置后,cocoapods会自动把这些资源打包成一个名叫DTVideoAssets的bundle文件,在pod中使用这些资源的方式会发生一些改变。假如这个pod中有一个类VideoPlayListCell.swift,那么我们可以创建辅助方法:
structDTVideoCommon{staticfuncassetsBundle()->Bundle{letmyBundle=Bundle(for:VideoPlayListCell.self)letpath=myBundle.path(forResource:"DTVideoAssets",ofType:"bundle")guardletpath=pathelse{returnnil}letassetsBundle=Bundle.init(path:path)returnassetsBundle}staticfuncimageWith(namedname:String)->UIImage{letassetsBundle=assetsBundle()letimage=UIImage.init(named:name,in:assetsBundle,compatibleWith:nil)returnimage}}加载图片时:
letimage=DTVideoCommon.imageWith(named:"video_play_max_nor")imageView.image=image使用xib时:
letcellNib=UINib.init(nibName:cellIdentifier,bundle:DTVideoCommon.assetsBundle())tableView.register(cellNib,forCellReuseIdentifier:cellIdentifier)xib文件中设置Module名的坑xib文件中有Module设置,如果是在工程中创建的,那么它默认是勾选上inheritModuleFromTarget,当将这个文件移动到pod中时,它的Module名就被设置成了默认名,即bundle名,这样会导致创建这个cell的时候报错:
正确的设置是不要勾选inheritModuleFromTarget,并且在Module栏输入正确的Module名。
当然你也可以空着,不输入Module名,但是这样需要修改VideoPlayListCell的类名:
@objc(VideoPlayListCell)//Module栏空着情况下,必须添加这行classVideoPlayListCell:UITableViewCell{ //...代码省略}如果不使用@objc(VideoPlayListCell)修改类名,那么会出现上面一样的报错,所以还是建议输入正确的Module名
这个和pod没有关联,是主工程的混编用法。
图示的module.modulemap指的是需要import不同framework的module,例如Apod中的swift文件需要引用Bpod中的.h/.m文件,需要:importB
struct默认生成的初始化方法是internal级别的,例如:
publicstructTestStruct{letkey:String}functest(){TestStruct(key:"aaaa")}它可以在pod里面调用,但是在pod外部被调用则会报错:'TestStruct'initializerisinaccessibledueto'internal'protectionlevel。需要手动创建它的publicinit方法。如:
publicstructTestStruct{letkey:String//需要手动添加publicinit方法publicinit(key:String){self.key=key}}设置pod库的PublicHeaders和PriveteHeaders构建产物为Framework的情况下如果podspec里未标注Public和Private的时候,会将所有文件设置为Public类型,并放在Header中。
不论podspec里如何设置public_header_files和private_header_files,相应的头文件都会被设置为Project类型。
现在常见的路由方案有:URLRoute,Protocol-Class,Target-Action等。个人偏爱URLRoute,主要有两方面的原因:
而Protocol-Class,Target-Action这些方案,没法避免硬编码,只能说是URLRoute的一种补充。URLRoute本质上就是约定一个各端通用的协议,在各端内部对协议进行正确的解析和逻辑处理。封装好了之后,不管是外部还是内部的调用者,不需要关心任何细节和区分平台,只需要传入协议就可以。综上所属,推荐使用URLRoute。
组件化后会多出很多重复简单的操作,例如一个私有pod的新版本发布,需要的流程有:gitcommit->打tag->pod验证->pod发布,这些都是可以通过编写脚本简化操作的,建议在组件化过程中多做这方面的工作。