APP下载
报价宝  ›  科技  › 

转载---基于CocoaPods的元件化原理及私有库实践

报价宝 来源:baojiabao.com 发布时间:2019-08-14 00:48:00 10月22日更新
报价宝综合消息转载---基于CocoaPods的元件化原理及私有库实践

上古时代的轮子

从本质上说,元件是通过库的方式来进行封装从而提供给开发者使用。而库,就是一种组织一个或多个档案的方式。在 iOS 8 之前,iOS 只支援以静态库的方式来使用第三方的程式码。

静态库

静态库,在iOS中会被打包成.a档案,配合.h标头档案一起可以完成功能的呼叫。但是在在概念上,静态库是一种All In One的设计思路,因为依赖静态库的程式码会把静态库完全连结到App的可执行档案中。也就是说,静态库是在编译器被连结到App中的,因此如果多个App都引用了同一个静态库,则每个App都会把这个静态库连结一份,这其实浪费了内存。 当然,静态库的缺点不止于此。在使用静态库时,必须手动一个个连结它依赖的外部库,例如早期微信支付SDK的静态库接入方法中,必须要手动连结上:

SystemConfiguration.framework,

libz.dylib,

libsqlite3.0.dylib,

libc++.dylib,

Security.framework,

CoreTelephony.framework,

CFNetwork.framework

有没有一种需要轮流背诵蒸羊羔、蒸熊掌、蒸鹿尾儿、烧花鸭、烧雏鸡、烧子鹅、卤猪。。。的既视感。 而且,静态库的特点导致了App每次启动时都要重新载入静态库的内存,无法控制载入时机,而且每次启动都需要重新载入静态库,导致二次载入时间无法被优化。 大部分时候,还需要在Other Linker Flags里填入Objc -all_load来确保静态库正常工作。 好吧,听起来静态库很难用。 我们都知道,后期iOS支援了动态库。那动态库是不是就能完美解决问题了呢?

动态库

动态库,大部分会被打包成.tbd档案或者.dylib档案。不同于静态库在编译期连结到App,动态库是在执行时连结到App的,因此它有了三个好处:

按需载入,什么时候需要执行什么时候载入,提高了启动app的效率因为存在多个app使用同一个动态库的情况,因此一旦某个动态库被载入到内存中,下一个app使用时无需再次耗费内存载入此动态库,大家公用一个动态库。因为动态库不需要参与编译过程,因此不会产生连结时符号冲突的问题。不过,苹果对动态库的完全支援仅停留在系统的动态库上,例如UI.framework,对于第三方的动态库,还是需要embed到系统中。早期的一些热更新框架,例如JSPatch钻了漏子通过dlopen来进行热更新,不过很快被禁掉了。 不过,如果是企业证书,还是可以在自己的app里灵活的载入第三方动态库的。

Framework

在解释静态库和动态库的过程中,我并没有提framework的字眼。有些开发者觉得framework档案就是动态库,其实并不准确。 我们提到的framework,指的是.framework档案,这既不一定是静态库,也不一定是动态库。实际上这是一种打包方式,将Header(标头档案)、Binary(二进位制程式码档案)和bundle(资原始档)一起打包,方便开发者进行接入和呼叫。 因此framework到底是静态库还是动态库,取决于Binary档案(Mach-O档案)到底是静态库还是动态库。

痛点

“老一辈”的iOS开发都会记得手动引入静态库时,那无止境的编译错误。我简单总结一下,如果手动引入静态库,需要:

将静态库和标头档案引入工程新增各依赖库(不同版本下可能略有不同)修改Other_linker_flags,例如设定-ObjC,-fno-objc-arc等引数祈祷编译,如果出问题,从第一步进行检查如果没有问题,未来要手动管理更新程序员的创造力很多时候来源于“懒”,终于,CocoaPods横空出世,从此开启了一行命令列完成模组整合的时代!

CocoaPods

简介

CocoaPods是iOS平台当前最流行的包管理工具,可以将它理解为一个可以自动部署到专案的元件池,而对应的podfile档案就相当于请求元件的Request。当元件下载到工程后,cocoaPods会自动完成元件整合到现有专案的工作,并完成修改.xcodeproj档案和建立.xcworkspace档案。最终将所有元件统一打包成Pods.framework静态库,供专案使用。

在CocoaPods中,会存在以下几种档案:

podspec Pod的描述档案,一般来说表征你的专案地址,专案使用的平台和版本等资讯podfile 使用者编写的对于期望载入的pod以及对应Target资讯podfile.lock 记录了之前pod载入时的一些资讯,包括版本、依赖、CocoaPods版本等mainfest.lock 记录了本地pod的基本资讯,实际上是podfile.lock的拷贝 大部分开发者最熟悉的cocoaPods指令就是pod install,那具体在执行pod install时发生了什么呢?pod install 执行原理分析

当我们执行pod install时,会发生:

分析Dependency。 对比本地pod的version和podfile.lock中的pod version,如果不一致会提示存在风险对比podfile是否发生了变化。 如果存在问题,会生成两个列表,一个是需要Add的Pod(s),一个是需要Remove的Pod(s)。(如果存在remove的)删除需要Remove的Pods新增需要的Pod(s)。 此时,如果是常规的CocoaPods库(如果基于Git),会先去:Spec下查询对应的Pod资料夹找到对应的tag定位其Podspec档案git clone下来对应的档案(根据具体协议的不同,这里还可能存在以下几种方式的download:Bazaar、Mercurial、HTTP、SCP、SVN)copy到Pod资料夹中执行pre-Install hook生成Pod Project将该Pod中对应档案新增到工程中新增对应的framework、.a库、bundle等连结标头档案(link headers),生成Target执行 post-install hook生成podfile.lock,之后生成此档案的副本,将其放到Pod资料夹内,命名为manifest.lock (如果出现 The sandbox is not sync with the podfile.lock这种错误,则表示manifest.lock和podfile.lock档案不一致),此时一般需要重新执行pod install命令。配置原有的project档案(add build phase)添加了 Embed Pods Frameworks添加了 Copy Pod Resources其中,pre-install hook和post-install hook可以理解成回拨函式,是在podfile里对于install之前或者之后(生成工程但是还没写入磁盘)可以执行的逻辑,逻辑为:

pre_install do |installer|

# 做一些安装之前的hook

end

post_install do |installer|

# 做一些安装之后的hook

end

CocoaPods第三方库下载逻辑

CocoaPods的下载流程

首先,CocoaPods会根据Podfile中的描述进行依赖分析,最终得出一个扁平的依赖表。 这里,CocoaPods使用了一个叫做 Milinillo 的依赖关系解决算法。简单说就是使用了回溯法来整理出所有第三方库的一个依赖列表出来,据说是CoocaPods的开发工程师原创的算法,在解决问题上应该是够用,但是貌似如果第三方库复杂的时候会有效能问题。这里美团技术团队对此有专门的优化,详情请见 美团外卖iOS多端复用的推动、支撑与思考。针对列表中的每一项,回去Spec的Repo中检视其podSpec档案,找到其地址通过downloader进行对应库的下载。如果地址为git+tag,则此步骤为git clone xxxx.git 注意,此时必须要保证需要下载的pod版本号和git仓库的tag标签号一致。所有依赖库下载之后,便进入了和Xcode工程的融合步骤。

Xcode工程有什么变化

Xcode工程上有什么变化

在cocoaPods和Xcode工程进行整合的过程中,会有有以下流程

creat workspace 建立xcworkspace档案。其实xcworkspace档案本质上只是xcodeproject的集合,资料结构如下:

version = "1.0">

location = "group:Demo/Demo.xcodeproj">

location = "group:Pods/Pods.xcodeproj">

create group 在工程中建立group资料夹,逻辑上隔离一些档案create pod project & add pod library 建立pod.xcodeproject工程,并且将在podfile中定义的第三方库引入到这个工程之中。add embed frameworks script phase 添加了[CP] Embed Pods Frameworks,相应的,多了pods_xxx的group,下列xxx.framework.sh,来完成将内部第三方库打包成.a静态库档案(在Podfile中如果选择了!use_frameworks,则此步骤会打包成.framework)

[CP] Embed Pods Frameworks

remove embed frameworks script phase 如果本次podfile删除了部分第三方库,则此步骤会删除掉不需要的第三方库,将其的引用关系从Pod.xcodeproject工程中拿走。add copy resource script phase 如果第三方库存在资源bundle,则此步骤会将资原始档进行复制到集中的目录中,方便统一进行打包和封装。相应的,会新增[CP] Copy Pods Resources指令码。

[CP] Copy Pods Resources

add check manifest.lock script phase 前文提到过,manifest.lock其实是podfile.lock的副本。此步骤会进行diff,如果存在不一致,则会提示著名的那句The sandbox is not sync with the podfile.lock错误。add user script phase 此步骤是对原有project工程档案进行改造。在执行过pod install后,再次开启原有工程会发现无法编译通过,因为已经做了改动。首先,添加了对Pod工程的依赖,具体为引用中多了libPods_xxx.a档案。此步骤的.a档案(或者.framework档案)为上述步骤中xxx.framework.sh打包出来的档案,也就是说,cocoaPods会把所有第三方的元件封装为一个.a档案(或者.framework档案)!

静态档案引入建立了Pods的group,内含pods-xxx-debug.xconfig和pods-xxx.release.xconfig档案。这两个档案是对应工程的build phase的配置。相应的,主工程的Iinfo->Configurations的debug和release配置会对应上述两个配置档案。

Configurations上述两个配置都做了什么?包括: Header_search_path,指向了Pod/Headers/public/xxx,添加了Pods档案编译后的标头档案地址 Other_LDFLAGS,添加了-ObjC等等 一些Pods变了,例如Pods_BUILD_DIR等至此,原有xcode工程和新建的Pod工程完成了整合和融合。

好了,cocoaPods的好处和原理已经介绍的差不多了。大部分时间,我们通过引用github上的元件就够用了。但是有时候处于业务需要,我们需要来实现私有Pod库。所以接下来我们来介绍下如何在公司内网来实现一个私有库,实现一个私有元件。

利用CocoaPods实现私有元件

准备工作

安装好XCode配置好CocoaPods,并且可以pod update 以及 pod install 成功已经获得CocoaPods的Repo的地址,以及对应pod的Git地址(这里以git.xxx.com上申请的repo为例)涉及到的所有操作,请尽量在Terminal中进行,包括CocoaPods的相关操作(不要在CocoaPods官方客户端操作)本文涉及到的Demo,可以去https://git.xxx.com/XXX_SPA_XXX/HelloXXXPod去围观私有Spec Repo

所谓Spec Repo,就是Pods的索引。一旦在podfile中设定source为某个私有repo的git地址,在进行pod update的时候就会去这个repo中进行检索,如果检索到对应的pod,会读取该Pod的podspec从而进行安装。 一个Spec Repo的目录结构如下:

image.png

之后我们去git.xxx.com上新建一个相应的Repo地址,之后新增repo到本地,该repo地址是为了后面提交podspec使用。

# pod repo add [Private Repo Name] [GitHub HTTPS clone URL]

pod repo add XXXCocoaPodsRepo [email protected]:XXX_SPA_XXX/iOS_CocoaPods_Repo.git

成功后可以进入~/.cocoapods/repos目录下检视XXXCocoaPodsRepo这个目录了。

建立并Clone目标Pod地址

这里,我们以HelloXXXPod为例。 去git.xxx.com上去新建专案,之后获取地址,为:

[email protected]:XXX_SPA_XXX/HelloXXXPod.git

此时clone到本地,命令为:

git clone [email protected]:XXX_SPA_XXX/HelloXXXPod.git

建立Pod专案工程档案(源代码方式)

这里建议通过CocoPods的官方命令来进行Pod专案的建立,以测试专案HelloXXXPod为例,命令如下:

pod lib create HelloXXXPod

不出意外地话,会提问你六个问题(cocoaPods v1.5.3版本):

1.What platform do you want to use? [ iOS / macOS ]

2.What language do you want to use? [ Swift / ObjC ]

3.Would you like to include a demo application with your library? [ Yes / No ]

4.Which testing frameworks will you use? [ Specta / Kiwi / None ]

5.Would you like to do view based testing? [ Yes / No ]

6.What is your class prefix?

分别解释一下

What platform do you want to use?? [ iOS / macOS ] 问元件化应用在哪个平台上,一般我们选iOSWhat language do you want to use? [ Swift / ObjC ] 使用何种语言,可以根据专案是OC还是Swift自行选择Would you like to include a demo application with your library? [ Yes / No ] 问是否需要一个Demo工程,方便除错Pod。如果是第一次做元件化,建议选Yes,方便pod的除错Which testing frameworks will you use? [ Specta / Kiwi / None ] 问是否需要UT测试框架,可选择Specta和Kiwi,或者选择不要。Specta是OC的一个轻量级TDD/BDD框架,参考github/spectaKiwi是一个iOS的一个BDD框架,可以简单地部署和使用。github/kiwi UT测试框架如果要选择的话,建议选择Kiwi,可以参考我之前写的调研kiwi上手体验 本次的Demo,暂时选NoneWould you like to do view based testing? [ Yes / No ] 如果上一步选择了Specta ,这步会生成一部分有利于做自动化测试的逻辑和程式码What is your class prefix? 这里可以指定你的专案字首,这样在new一个类时会自动加上字首之后我们执行pod install,生成的档案目录树结构如下:

$ tree HelloXXXPod -L 2

HelloXXXPod

├── Example

│ ├── Build

│ ├── Podfile

│ ├── Podfile.lock

│ ├── Pods

│ ├── Tests

│ ├── helloXXXPod

│ ├── helloXXXPod.xcodeproj

│ └── helloXXXPod.xcworkspace

├── LICENSE

├── README.md

├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj

├── helloXXXPod

│ ├── Assets

│ └── Classes

└── helloXXXPod.podspec

开发

这时候可以在刚才生成的Example工程内做开发,这时候记得把新建的程式码放到Classes目录下。如果有图片资源,建议放到Assets下。

开发、除错完成之后,就可以去编辑podspec档案了。按以下方式来修改,不明白的字段请参考官方文件:

这里给出本次Demo的podspec供各位参考:

Pod::Spec.new do |s|

s.name = 'helloXXXPod'

s.version = '0.1.0'

s.summary = 'A short description of helloXXXPod.'

s.description = TODO: Add long description of the pod here.

DESC

s.homepage = 'https://git.xxx.com/XXX_SPA_XXX/HelloXXXPod'

s.license = { :type => 'MIT', :file => 'LICENSE' }

s.author = { 'nimomeng' => '[email protected]' }

s.source = { :git => '[email protected]:XXX_SPA_XXX/HelloXXXPod.git', :tag => s.version.to_s }

s.ios.deployment_target = '8.0'

s.source_files = 'helloXXXPod/Classes/**/*'

end

其中,注意修改这几个字段:

s.names.homepages.source (非常重要)s.source_files (如果不放在Classes下,记得在这里指定档案目录)本地除错

如果是通过pod lib create命令建立的Pod,会在Example中自动配置好该pod的本地除错指令码,如下:

use_frameworks!

platform :ios, '8.0'

target 'helloXXXPod_Example' do

pod 'helloXXXPod' :path => '../'

target 'helloXXXPod_Tests' do

inherit! :search_paths

end

end

其中,pod 'helloXXXPod' :path => '../'的含义是说,在上层目录来下载helloXXXPod这个pod。这是本地除错Pod的一种。 同样的,可以实现类似方式除错的方法,还有通过:podspec命令来指定,指定pod所在的podspec档案位置即可

其中,path语法精确到目录即可;podspec语法必须要精确到档案。

设定好podfile之后,在Example档案下执行pod install,则可以发现新的档案已经出现在专案工程的pods资料夹之下了。

image.png

注意,通过path语法进行更新后,Pod中程式码并不在Pod资料夹中,而是在一个叫 Development Pods中。

开发完成,需要本地验证podspec,确保其有效:

pod lib lint helloXXXPod.podspec

同步到Git上

之后要做的就是把库同步到Git上去了。这时候需要去git.xxx.com上建立一个对应的仓库,例如:

http://git.xxx.com/XXX_SPA_XXX/HelloXXXPod.git (替换为自己的实际git地址)

然后将程式码同步到此Git上。

git add .

git commit -m "Init"

git remote add origin http://git.xxx.com/XXX_SPA_XXX/HelloXXXPod.git(替换为自己的实际git地址)

git push --set-upstream origin master

podSpec档案需要版本控制资讯,所以我们要打一个Tag.

git tag -m "first demo" 0.1.0

git push --tags

向Spec Repo提交podspec

在执行本歩之前,确保最新程式码已经提交到了Git上,且已经打好了tag.

向Spec Repo提交podspec的命令:

pod repo push XXXCocoaPodsRepo HelloXXXPod.podspec --allow-warnings

在经过三轮的使用者校验之后,提交成功!这时候我们去~/.cocoapods/repos/XXXCocoaPodsRepo中检视,我们的的podspec已经在里面了!

此时通过pod search HelloXXXPod 已经可以查到了!

image.png

最后,为了保证本地的repo已经被更新,执行pod update来更新repo

如何在外部专案中使用

我们可以在想要使用的专案中的Podfile里加入如下程式码:

pod 'helloXXXPod'

即可。 当然,由于我们的是私有CocoaPods库,因此最好告诉系统这个库的source在哪里,因此在Podfile档案上部也请加上Spec Repo的git地址。同时,为了确保公共的cocoaPod可以被正常下载,请新增外部CocoaPod的库:

# For inner pods

source '[email protected]:XXX_SPA_XXX/iOS_CocoaPods_Repo.git'

# For public pods

source 'https://github.com/CocoaPods/Specs.git'

整个的Podfile档案看起来是这样的:

use_frameworks!

platform :ios, '8.0'

# source '[email protected]:XXX_SPA_XXX/iOS_CocoaPods_Repo.git'

# For public pods

source 'https://github.com/CocoaPods/Specs.git'

target 'helloXXXPod_Example' do

pod 'helloXXXPod'

target 'helloXXXPod_Tests' do

inherit! :search_paths

end

end

之后执行pod install 即可安装对应的Pods

验证

我们可以复用Example专案,只不过这次不再通过:path命令或者:podspec命令来做本地呼叫,而是完全使用安装外部pod的方式,即:

pod 'helloXXXPod'

注意:虽然pod已经推送到线上,但是本地一定要先更新pod的repo,不然还是无法找到最新的pod。确保先做pod update操作。

Example专案中,我们呼叫在Pod中写好的方法,检视是否输入对应的log即可验证:

image.png

至此,Pod建立完成。

常见问题

如果pod中用到framework,应该在哪里新增? 如果pod中用到framework,如AVFoundation,直接在podspec档案中新增s.frameworks = ‘AVFoundation’或者s.frameworks = [‘AVFoundation’,'MapKit'],而不应该新增在专案的Link Binary With Libraries下面。怎么取更新私有 pod? 更新私有pod的过程和建立pod的步骤一致,但是要记得在更改程式码后要记得一定重新run一下aggregate,更改podspec里的s.version(因为tag不能重复提交), 重新pod repo push如果出现这个错误怎么办:[!] An unexpected version directory `Assets` was encountered for the `/Users/nimo/.cocoapods/repos/xxxx` Pod in the `xxxx` repository.

这个错误,请检视:

podspec 是否未上传到服务器Podfile的source地址是否是Spec Repo的地址,而不是具体某一个Pod的地址。

参考文章

iOS模组化实践 -- 利用CocoaPods拆分专案使用Cocoapods建立私有podspec http://www.cnblogs.com/saytome/p/7230311.html https://www.jianshu.com/p/0bf291de94f7 https://zhuanlan.zhihu.com/p/22652365 https://objccn.io/issue-6-4/ https://www.objc.io/issues/6-build-tools/cocoapods-under-the-hood/ https://draveness.me/cocoapods
文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 报价宝 小米手机价格 华为手机价格 红米手机价格 耳机价格