iOS组件化实践之:新建一个Pod组件库
0.背景
在IOS组件化的实践过程中,
- 规划期间需要对组件进行定义、功能划分、接口设计等
- 实践期间需要对组件进行创建,迁移主工程能力等
这过程,就看你需要较为频繁地创建Pod组件库,因此对于了解一下,Pod组件的创建,及其模板的使用与修改,变成较为重要
1. 通过常规模板创建
CocoaPods提供的创建指令:
1
pod lib create xx
通过输出可以看到:
1
Cloning `https://github.com/CocoaPods/pod-template.git` into `xx`.
此命令默认从 github Pod-template,下载默认模板进行创建,依次回答几个问题之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
What platform do you want to use?? [ iOS / macOS ]
>
ios
What language do you want to use?? [ Swift / ObjC ]
>
swift
Would you like to include a demo application with your library? [ Yes / No ]
>
yes
Which testing frameworks will you use? [ Quick / None ]
>
quick
Would you like to do view based testing? [ Yes / No ]
>
yes
当前路径下就生成了一个Pod仓库,Spec文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Pod::Spec.new do |s|
s.name = 'xx'
s.version = '0.1.0'
s.summary = 'A short description of xx.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/chenyp/xx'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'chenyp' => '165685965@qq.com' }
s.source = { :git => 'https://github.com/chenyp/xx.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '10.0'
s.source_files = 'xx/Classes/**/*'
# s.resource_bundles = {
# 'xx' => ['xx/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
end
2. 通过自定义模板创建
一般而言,不建议通过上述方式进行创建,默认以git全局配置的name、email字段,加上github主页进行填充,因为默认模板所创建的诸多字段是必要修改的(不改无法通过私有组件部署):
1
2
3
4
s.homepage = 'https://github.com/chenyp/xx'
s.author = { 'chenyp' => '165685965@qq.com' }
s.source = { :git => 'https://github.com/chenyp/xx.git', :tag => s.version.to_s }
……
由于组件化的过程是需要频繁创建多个组件仓库,如果每次都改上述字段,有可能错改漏改,导致效率低下,基于懒惰是程序员的美德这一不成文的追求(其实应该是尽量能把重复工作流程化),着手对上述模板进行修改。
自定义模板创建:
1
git clone git@github.com:CocoaPods/pod-template.git
通过修改仓库中的“NAME.podspec”文件中的相关字段
1
2
3
4
s.homepage = 'https://内网githost/name/xx'
s.author = { '内网git提交commit姓名' => '内网git提交commit email' }
s.source = { :git => 'https://内网githost/name/xx.git', :tag => s.version.to_s } #clone 组件库地址
……
修改后将上述模板放到git仓库上,执行:
1
pod lib create xx --template-url=ssh://内网githost/ios/Pods/Tmplate/XXX_Pod_Tmeplate.git
3. 通过高度自定义模板创建
一般而言,到上述即可完成基础的模板自定义,减少创建私有库后所需的重复修改字段的工作量,但是对于下列选项的一致性,如果能省略冗余及提高统一性,自然更好了:
-
平台选择的冗余(iOS)
-
代码风格的统一(类名前缀)
-
语言统一(iOS/Swift)
-
Demo工程
-
测试依赖
这就需要对pod-template的ruby代码进行修改已达到不需要重复设定模板交互的目的:
模板配置:TemplateConfigurator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def run
@message_bank.welcome_message
platform = self.ask_with_answers("What platform do you want to use?", ["iOS", "macOS"]).to_sym
case platform
when :macos
ConfigureMacOSSwift.perform(configurator: self)
when :ios
framework = self.ask_with_answers("What language do you want to use?", ["Swift", "ObjC"]).to_sym
case framework
when :swift
ConfigureSwift.perform(configurator: self)
when :objc
ConfigureIOS.perform(configurator: self)
end
end
#……此处省略后面的代码……#
end
改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def run
@message_bank.welcome_message
#自定义模板优化:这里可以绕过了一些问题(免回答iOS、Objc),当然如果想要设置成其他的,也可以调用其他方法,或者恢复询问式配置
ConfigureIOS.perform(configurator: self)
# platform = self.ask_with_answers("What platform do you want to use?", ["iOS", "macOS"]).to_sym
#
# case platform
# when :macos
# ConfigureMacOSSwift.perform(configurator: self)
# when :ios
# framework = self.ask_with_answers("What language do you want to use?", ["Swift", "ObjC"]).to_sym
# case framework
# when :swift
# ConfigureSwift.perform(configurator: self)
#
# when :objc
# ConfigureIOS.perform(configurator: self)
# end
# end
#……此处省略后面的代码……#
end
选择了相关语言,就在相关语言的 Configure 内配置后续的设置,例如:选择了iOS,就在ConfigureIOS.rb配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def perform
keep_demo = configurator.ask_with_answers("Would you like to include a demo application with your library", ["Yes", "No"]).to_sym
framework = configurator.ask_with_answers("Which testing frameworks will you use", ["Specta", "Kiwi", "None"]).to_sym
case framework
when :specta
configurator.add_pod_to_podfile "Specta"
configurator.add_pod_to_podfile "Expecta"
configurator.add_line_to_pch "@import Specta;"
configurator.add_line_to_pch "@import Expecta;"
configurator.set_test_framework("specta", "m", "ios")
when :kiwi
configurator.add_pod_to_podfile "Kiwi"
configurator.add_line_to_pch "@import Kiwi;"
configurator.set_test_framework("kiwi", "m", "ios")
when :none
configurator.set_test_framework("xctest", "m", "ios")
end
snapshots = configurator.ask_with_answers("Would you like to do view based testing", ["Yes", "No"]).to_sym
case snapshots
when :yes
configurator.add_pod_to_podfile "FBSnapshotTestCase"
configurator.add_line_to_pch "@import FBSnapshotTestCase;"
if keep_demo == :no
puts " Putting demo application back in, you cannot do view tests without a host application."
keep_demo = :yes
end
if framework == :specta
configurator.add_pod_to_podfile "Expecta+Snapshots"
configurator.add_line_to_pch "@import Expecta_Snapshots;"
end
end
prefix = nil
loop do
prefix = configurator.ask("What is your class prefix").upcase
if prefix.include?(' ')
puts 'Your class prefix cannot contain spaces.'.red
else
break
end
end
#……此处省略后面的代码……#
end
找到相关的问答区:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def perform
#自定义模板优化: 这里默认需要demo,当然如果想要设置成其他的,也可以调用其他方法,或者恢复询问式配置
keep_demo = "yes"#configurator.ask_with_answers("Would you like to include a demo application with your library", ["Yes", "No"]).to_sym
#自定义模板优化:: 这里默认不需要testing frameworks,当然如果想要设置成其他的,也可以调用其他方法,或者恢复询问式配置
framework = "none" #configurator.ask_with_answers("Which testing frameworks will you use", ["Specta", "Kiwi", "None"]).to_sym
case framework
when :specta
configurator.add_pod_to_podfile "Specta"
configurator.add_pod_to_podfile "Expecta"
configurator.add_line_to_pch "@import Specta;"
configurator.add_line_to_pch "@import Expecta;"
configurator.set_test_framework("specta", "m", "ios")
when :kiwi
configurator.add_pod_to_podfile "Kiwi"
configurator.add_line_to_pch "@import Kiwi;"
configurator.set_test_framework("kiwi", "m", "ios")
when :none
configurator.set_test_framework("xctest", "m", "ios")
end
#自定义模板优化:这里默认不需要view based testing,当然如果想要设置成其他的,也可以调用其他方法,或者恢复询问式配置
snapshots = "no"#configurator.ask_with_answers("Would you like to do view based testing", ["Yes", "No"]).to_sym
case snapshots
when :yes
configurator.add_pod_to_podfile "FBSnapshotTestCase"
configurator.add_line_to_pch "@import FBSnapshotTestCase;"
if keep_demo == :no
puts " Putting demo application back in, you cannot do view tests without a host application."
keep_demo = :yes
end
if framework == :specta
configurator.add_pod_to_podfile "Expecta+Snapshots"
configurator.add_line_to_pch "@import Expecta_Snapshots;"
end
end
prefix = nil
loop do
#自定义模板优化:这里默认prefix为PSC,当然如果想要设置成其他的,也可以调用其他方法,或者恢复询问式配置
prefix = "PSC"#configurator.ask("What is your class prefix").upcase
if prefix.include?(' ')
puts 'Your class prefix cannot contain spaces.'.red
else
break
end
end
#……此处省略后面的代码……#
end
至此,优化的自定义模板已经完成,再创建另一个(如果你需要保留上面那种简易自定义)git仓库存储,一键创建更符合需求的pod私有库模板:let‘s try
1
pod lib create xx --template-url=ssh://内网githost/ios/Pods/Tmplate/PSC_iOS_Objc_Demo_NoneTest.git
4. 如果你还是使用Gem管理Pod插件的话
一般而言,在多人团队中,需要用到Gem来管理Pod插件版本,避免由于Pod版本不一致,导致的集成与编译问题,如果你还有Gemfile需要配置的话,由于模板配置的代码最后的是:
1
2
3
4
5
6
7
8
# There has to be a single file in the Classes dir
# or a framework won't be created, which is now default
`touch Pod/Classes/ReplaceMe.m`
`mv ./templates/ios/* ./`
# remove podspec for osx
`rm ./NAME-osx.podspec`
在Podfile同级目录下也存放一个自己在用的Gemfile即可:
1
2
3
4
5
6
7
8
9
10
# frozen_string_literal: true
source "https://rubygems.org"
gem 'cocoapods', '1.11.2'
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# gem "rails"
5 最后的忽略
模板创建之后,会执行Pod install,此时与常规工程一样,在Example下会新增Pod文件夹存在Pod依赖库,提交时需忽略,本着能少干重复劳动就不多干的原则,我们再把.gitignore修改一下:
在根目录中找到.gitignore文件,由于是.开头,默认是隐藏了,在下面代码中添加你需要忽略的文件夹
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# macOS
.DS_Store
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
# Bundler
.bundle
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
# Note: if you ignore the Pods directory, make sure to uncomment
# `pod install` in .travis.yml
#
# Pods/ #打开此处注释可以忽略 以下Example/Pods/
#以下可以继续添加忽略文件及文件夹
6.扩展:Podfile依赖
一般而言,创建的Pod私有库中的Podfile,会包含简单的模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
use_frameworks!
platform :ios, '10.0'
target '${POD_NAME}_Example' do
pod '${POD_NAME}', :path => '../'
target '${POD_NAME}_Tests' do
inherit! :search_paths
${INCLUDED_PODS}
end
end
对于单个Pod组件库,也许够用了,但是我们如果是在进行组件化改造的进程中,需要与现有的私有库进行交互与依赖:比如业务库依赖基础库等,
为了方便起见,不至于对每次创建私有库进行Podfile配置(还是懒😭),因此对Podfile进行了改造:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
source 'ssh://私有库git地址/Specs.git'
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '12.0' #写这篇文章时使用的podfile默认是10.0,可以改成你需要的
post_install do |installer|
installer.generated_projects.each do |project|
project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
config.build_settings["DEVELOPMENT_TEAM"] = "xxxxx" #这里指定签名,避免编译出错还要手动指定
end
end
end
end
use_frameworks!
target '${POD_NAME}_Example' do
#新建pod库创建模板的时候把私有库拉取一下
def debug_pod(pod_name)
this_pod_name = "${POD_NAME}"
puts "debug_pod :pod_name = #{pod_name} and this_pod_name is #{this_pod_name}"
if this_pod_name == pod_name
puts "#{this_pod_name} is equal args #{pod_name} do not pod"
elsif
#为了方便管理,这里的所有私有库建议放在同一个目录下
pod "#{pod_name}", :path => "../../#{pod_name}/"
end
end
#为了避免重复定义 这里统一按下面方式进行处理吧,由于下文已经匹配了当前pod,这里不用注释掉
pod '${POD_NAME}', :path => '../'
#这里定义需要被引入的所有私有库的库名
private_pods = ["基础组件1", "基础组件2", "基础组件3", "能力组件1", "能力组件2", "能力组件3", "业务组件1", "业务组件2"]
#这里将所有私有库名与路径匹配起来进行pod
private_pods.each do |private_pod|
debug_pod(private_pod)
end
#如果上述方法用不来,或者不好用也可以改用下面比较传统的方式进行:
#pod '基础组件1', :path => '../../基础组件1/'
#pod '基础组件2', :path => '../../基础组件2/'
#pod '基础组件3', :path => '../../基础组件3/'
#pod '能力组件1', :path => '../../能力组件1/'
#pod '能力组件2', :path => '../../能力组件2/'
#pod '能力组件3', :path => '../../能力组件3/'
#pod '业务组件1', :path => '../../业务组件1/'
#pod '业务组件2', :path => '../../业务组件2/'
target '${POD_NAME}_Tests' do
inherit! :search_paths
${INCLUDED_PODS}
end
end
值得一提的是,需要把所有Dev Pod放在同一个目录下,避免路径管理麻烦
至此,生产的Pod组件库模板可以满足组件化进程需求了。
##