前言
SDK开发系列文章一
SDK开发系列文章二
之前两个文章介绍了Cocoapods的原理,Xcode环境变量以及动态库和静态库的介绍,这些基本知识就单独抽出来了,方便以后翻阅。看了一遍网上的一些静态库制作,很多都是告诉你打开Xcode,然后选择静态库,然后你随便写点东西,然后你编译就看到一个.a,简单的看下架构,简单的合并下,把头文件导出来,就结束了。
但是实际你遇到的东西远远不止这些。比如SDK有可能依赖第三方库
,也有可能SDK里面用到了其他SDK
,比如你SDK里面用到了微信支付SDK,或者你用到自己写的.a库,需不需要一起打进你自己做的SDK
,这些你就可能在项目中遇到,下面就针对上面的几个场景写一个SDKDemo。
Cocoapods制作静态库
项目配置
一般我们都是用Cocoapods的模板先创建一个工程,这也是你制作第三方库的第一步
FVFXGM44HV29:Desktop mikejing191$ pod lib create MKJDemoSDKCloning `/CocoaPods/pod-template.git` into `MKJDemoSDK`.Configuring MKJDemoSDK template.------------------------------To get you started we need to ask a few questions, this should only take a minute.If this is your first time we recommend running through with the guide: - /making/using-pod-lib-create.html( hold cmd and double click links to open in a browser. )What platform do you want to use?? [ iOS / macOS ]> iOSWhat language do you want to use?? [ Swift / ObjC ]> ObjcWould you like to include a demo application with your library? [ Yes / No ]> YesWhich testing frameworks will you use? [ Specta / Kiwi / None ]> NoneWould you like to do view based testing? [ Yes / No ]> NoWhat is your class prefix?> MKJRunning pod install on your new library.Analyzing dependenciesFetching podspec for `MKJDemoSDK` from `../`Downloading dependenciesInstalling MKJDemoSDK (0.1.0)Generating Pods projectIntegrating client project[!] Please close any current Xcode sessions and use `MKJDemoSDK.xcworkspace` for this project from now on.Sending statsPod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.Ace! you're ready to go!We will start you off by opening your project in Xcodeopen 'MKJDemoSDK/Example/MKJDemoSDK.xcworkspace'To learn more about the template see `/CocoaPods/pod-template.git`.To learn more about creating a new pod, see `/making/making-a-cocoapod`.
项目结构如下
podspec文件描述
.podspec
该文件就是用来描述pod库的所有信息的,比如源码地址,怎么构建,依赖,版本,作者,介绍等信息。
Pod::Spec.new do |s|s.name = 'MKJDemoSDK' #项目名称s.version= '0.1.0' # 项目版本s.summary= 'A short description of MKJDemoSDK.' # 简述s.description= <<-DESC # 详述TODO: Add long description of the pod here.DESCs.homepage = '/714831204@/MKJDemoSDK' # 主页# s.screenshots= '/screenshots_1', '/screenshots_2's.license= {:type => 'MIT', :file => 'LICENSE' } # 协议s.author = {'714831204@' => 'MIKEJING191@' } # 作者s.platform = :ios, '8.0' # 支持的平台s.ios.deployment_target = '8.0's.requires_arc = true # arc和mrc选项piler_flags = '-DOS_OBJECT_USE_OBJC=0', '-Wno-format' # A list of flags which should be passed to the compiler.s.libraries = 'xml2', 'z' , 'sqlite3' # 表示依赖的系统静态库,如libz.dylibs.frameworks = 'QuartzCore', 'CoreData' # 表示依赖系统的动态库s.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.framework' # 依赖非系统的frameworks.vendored_libraries = 'xxx/xxx/libProj4.a', 'libJavaScriptCore.a' # 依赖非系统的静态库,依赖的第三方的或者自己的静态库文件必须以lib为前缀进行命名,否则会出现找不到的情况s.preserve_paths = 'Frameworks/*.framework', 'IMPORTANT.txt' # By default, CocoaPods removes all files that are not matched by any of the other file pattern.s.source = {:git => '/714831204@/MKJDemoSDK.git', :tag => s.version.to_s }# 如果不是本地开发,会从改路径下的Tag下下载# s.social_media_url = '/<TWITTER_USERNAME>'s.source_files = 'MKJDemoSDK/Classes/**/*' # 源码地址目录就是项目根目录,也可以自己写路径# 资源文件,为了避免冲突,cocoapods建议用resource_bundles,而不再是用resourcess.resource_bundles = {'MKJDemoSDK' => ['MKJDemoSDK/Assets/*.png']'MapBox' => ['MapView/Map/Resources/*.png'],'MapBoxOtherResources' => ['MapView/Map/OtherResources/*.png']}# 对外公开的头文件s.public_header_files = 'Pod/Classes/**/*.h'# 项目依赖# s.dependency 'AFNetworking', '~> 2.3'end
上面是把常用的介绍下,具体看cocoapods官方Spec文档
Development Pod
Development Pods are different from normal CocoaPods in that they are symlinked files, so making edits to them will change the original files, so you can work on your library from inside Xcode. Your demo & tests will need to include references to headers using the #import <MyLib/XYZ.h> format.[!] Note: Due to a Development Pods implementation detail, when you add new/existing files to Pod/Classes or Pod/Assets or update your podspec, you should run pod install or pod update.
Development Pods 是符号链接文件,所以对它的编辑也会改变原始文件。这样就能在xcode内开发你的pod库。当你向 Pod/Classes 或 Pod/Assets 添加新的/已经存在的文件,或者更新你的 .podspec 时,需要再次运行 pod install 或者 pod update。
配置完Spec文件之后,需要对它验证
pod lib lint ***.podspec //仅本地验证pod spec lint [NAME.podspec|DIRECTORY|http://PATH/NAME.podspec ...] //使用网络验证远端
在实际项目中,我们会用到我们的私有库,git官方仓库,而且有可能有依赖静态库,就需要按如下写法编写
pod lib lint *****.podspec --sources='git@私有库, git@公有库' --verbose --allow-warnings --use-libraries --no-clean --platforms=ios
--use-libraries
表示依赖静态库
--allow-warnings
忽略警告
--verbose
继承来的字段 用来打印Debug信息 默认--silent
不打印
--no-clean
保留构建目录做检查
其他详细的可以翻阅官方文档
上述表达的所有参数描述需要的都可以翻阅官方文档的解释,而且你pod lint
的时候是根据本地tag或者远端tag进行验证的,如果你打了tag没有提交就会报错,所以提交完,打上tag,pod lint验证完成之后,我们就可以进行静态库打包了。
项目打包静态库
我们需要一个插件Cocoapods Packer 安装命令sudo gem install cocoapods-packager
pod package --help
查看所有的用法
打包如下
pod package xxx.podspec --library --force --no-mangle --spec-sources=私有库,公有库# --library 表示打包成.a文件。--force 表示强制覆盖之前存在的文件 mangle字段,默认是给类名加了前缀,其他不会加,比如Categorypod package xxx.podspec --force --no-mangle --spec-sources=私有库,公有库# 没有--library,则打包成.framework文件
--force // 强制覆盖--no-mangle // 不加前缀 默认是mangle前缀添加--embedded // 静态库.framework --library // 静态.a --dynamic // 生成动态.framework --bundle-identifier //动态.framework是需要签名的,所以只有生成动态库的时候需要这个 --exclude-deps // 不包含依赖的pod库的符号表/依赖的pod库不打包进去。生成动态库的时候不能使用这个命令,动态库一定需要包含依赖的符号表。--configuration // 表示生成的库是debug还是release,默认是release 。--configuration=Debug,ONLY_ACTIVE_ARCH=NO --subspecs# 如果你的pod库有subspec,那么加上这个命名表示只给某个或几个subspec生成二进制库,# --subspecs=subspec1,subspec2。生成的库的名字就是你podspec的名字,# 如果你想生成的库的名字跟subspec的名字一样,那么就需要修改podspec的名字。 # 这个脚本就是批量生成subspec的二进制库,每一个subspec的库名就是podspecName+subspecName --spec-sources=private,/CocoaPods/Specs.git# 一些依赖的source,如果你有依赖是来自于私有库的,# 那就需要加上那个私有库的source,默认是cocoapods的Specs仓库。
注意 mangle选项是默认的,会给类名添加前缀,但是其他是不会加的比如Category,但是如果你--Library,而且你项目有vendored_libraries,是会报错podspec has binary-only depedencies,mangling not possible.,因此需要改成--no-mangle不添加前缀
把代码打上tag,提交到上面Spec文件的git仓库,pod lint通过后进行打包,package会根据spec文件中的tag和s.version匹配之后从git仓库拉下最新的代码,然后pod会安装对应的依赖,编译源文件,如果有mangle参数就进行类名添加,最后打包成.a。
存在问题
虽然有个字段是
--exclude-deps
会把第三方依赖不打进去,但是如果我们依赖了.a文件,package还是会把.a给打进去,而且还不能用mangle
字段,如果我们依赖的.a是很常用的,你打进去,然后你把包给宿主,如果宿主也用了这个.a,那就会报错冲突,这就很尴尬的存在了,个人觉得Package是方便功能单一的打包用的,如果复杂的需求SDK,还是用Xcode自带的打包吧。
Xcode制作静态库
现在我们的Demo的场景是这样的
1.SDK有一个主页面,可以进行微信支付和关闭SDK
2.页面用到第三方Masonry
3.SDK依赖微信支付SDK
项目Demo地址
新建项目
项目结构如上图,Podfile包含了Masonry,我们编写一个MKJPay的Demo模拟打包,MKJPay就是要暴露给外部的文件,API如下
#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface MKJPay : NSObject+ (void)registerWXApp:(NSString *)appKey; // 宿主App启动注册+ (void)openSDKFromController:(UIViewController *)viewController; // 业务代码点击触发// 微信回调触发+ (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;+ (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;@endNS_ASSUME_NONNULL_END
这个时候要把微信的SDK拖进来,然后工程的配置比如Link Binary
的配置,Scheme
的配置,白名单的配置这些就不介绍了,根据微信的文档配置即可。根据我们前两篇文章的介绍,我们不直接拖进来,可以看到我们项目目录中么有任何微信的东西,我们放在实际目录下,我们通过Header Search Path
和Library Search Path
这两个参数来配置。
微信所在目录
如下图:
Header Search Path
如下图
这里的$(PROJECT_DIR)
这些环境变量前两个文章有介绍,我们配置好对应的路径搜索,然后选择recursive
与否无非就是递归与否,看自己的需求配置
Library Search Path
配置如下图
Link Binary With Libraries
如下图
Other Link Flags
如下图
该字段我的理解是和上面的Link Binary是差不多的,个人感觉Other Link包含Link Binary,一个是命令,一个是图形化,命令可以配置的参数更多。而且我们这里的第三方库比如Masonry和AF,都是在Other Link那里通过Cocoapods的脚本进行添加
到这里,我们的配置就可以了,根据API编写好业务代码,然后就可以编译过了,都不用把微信拖入项目中,直接放在外部就可以了。
静态库制作
上面的业务代码已经配置好了,我们也能正常跑起来,而且也能进行微信支付,现在把它打包成静态库。
方式一
另起一个项目,把Target选择成静态库制作那个,然后把之前写的东西和配置一模一样配置好,然后你Command
+B
,就会在生成对应架构的.a文件,这个不多做介绍了,和之前项目是一样的
方式二
在当前项目下,新建一个Target,然后把MKJSDK
文件夹先删除引用,然后再拖进来,记得把两个Target都给选上。然后在Cocoapods的Podfile文件做修改,新建一个Target如下:
target 'MKJPay' do# Uncomment the next line if you're using Swift or would like to use dynamic frameworksuse_frameworks!pod 'Masonry'end
同样设置该Target下对应的Search Path
和Other Link Flags
(Objc
和$(inherited)
)
配置好静态库Target后,现在可以单独编译MKJPay
跑起来,就会出现静态库。或者我们在SDKDemo
那里设置下依赖,这样两个Target下的产物都会一起出现了
下面来看下对应编译出来的架构参数
Build Active Architecture Only
对应的是是否只编译当前设备的那个架构,如果No的话就会把当前设备的其他架构编译出来,比如模拟器,你编译的时候只会有x86_64
,如果是NO,那么就会有i386
,但是现在貌似只要支持x86_64
就好了,真机的话同理。前提是Valid Achitectures
两者是交集。
详细的文章介绍这些参数
现在编译的静态库是没有把微信的静态库和第三方Framework给打包进去的,如果把我们的静态库给宿主,宿主需要自行导入依赖的第三方库,这样的好处是不会包进去,如果宿主也用,就存在一份即可,不会引来冲突,不好的地方就是宿主处理依赖会稍微多一点步骤。
按这种方式提供的静态库,宿主需要把我们的静态库,头文件,微信静态库拖入项目,或者直接配置对应Header Search Path
和Library Search Path
,之后在Link Binary
那里把需要的依赖库给添加上,最后我们还依赖Masonry
,所以我们一样引入Cocoapods,导入对应的三方库,就可以使用我们的静态库了。
那么还有个要注意的,如果我们需要把我们自己依赖的静态库也打包进我们的libxxxx.a文件中需要做什么?也很容易,我们再静态库Target的Build Phase中,Link Binary中加上我们依赖的第三方静态库,Xcode就会把对应的静态库也打进我们的项目。这样做的前提是保证宿主没有用到同样的库,这些库最好都是私有库,公有库谁都不能保证会不会冲突。
lipo 和 ar 操作介绍
lipo 是创建和操作“通用文件”(多种CPU架构混合的二进制文件)的命令。它仅生成一个输出文件,并且不改变原始输入文件。
通俗的讲,lipo 的作用是,将多架构类型的文件拆分成单独某个架构类型的文件,或者将多个不同架构类型的文件合成为一个 fat 的通用文件(多架构类型文件)。所以 lipo 进行各种操作的本质是处理 “架构类型”,而不是操作文件内容。
列出通用文件所支持的全部架构类型从多个/单个输入文件创建一个通用架构的fat文件从一个通用fat文件,thin 分离出一个指定架构的文件从输入文件抽取、替换、移除某个架构类型,从而创建出一个单独的新的通用输出文件。
除了 -arch, -arch_blank, -output, 以及 -segalign 这四个 option 可以与其他 option 结合使用以外,剩下的 option 都仅能使用一个。参数 input_file (输入文件) 是必选的,并且仅仅 -create 这个 option 允许有多个输入文件。除了-info
和-detailed_info-output
之外,-output
是必选的。
-info # lipo File.a -info-detailed_info# lipo File.a -detailed_info-output [output_file]-create [input_file] [input_file] -output [output_file]# lipo -create InputFileOne.a InputFileTwo.a -output File.a# 注: 输入文件不能包含同样的架构类型-thin [arch_type] # lipo InputFile.a -thin armv7 -output File_armv7.a
ar
# ar -t File.a //显示所有.o文件清单# ar -s //更新库的符号索引表 __.SYMDEF# ar -x //从库中提取一个成员。如果不指定要提取的模块,则提取库中所有的模块。 # ar -r -c -s libFileName.a *.o //将当前文件夹中的所有 .o 文件归档为 libFileName.a 文件,并且更新符号表实际应用:将 libFile1.a (arm64) 和 libFile2.a (arm64) 合并为一个 libFile.a (arm64) 文件。假定 libFile1.a 和 libFile2.a 位于 /Users/xxx/localLib 路径下mkdir ./temp/libFilecd ./temp/libFilear -x /Users/xxx/localLib/libFile1.aar -x /Users/xxx/localLib/libFile2.aar -r -c -s libFile.a *.o
简单打包脚本合并
其实打包脚本的编写你首先得要掌握一门脚本语言Python也好,shell也好,总之这是前提,然后我们就是主要了解xcodebuild
这个命令的作用即可。
我找了三个文章简单看下就能自己操作一下
iOS脚本打包
xcodebuild官方介绍翻译
xcodebuild API
还有这个哥们介绍的环境变量
打开你的vim或者编辑器,开始写一个简单的打包静态库,然后进行多种结构合并成一个的脚本
FVFXGM44HV29:CocoapodsDemo mikejing191$ xcodebuild -usageUsage: xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings [-json]] [<buildsetting>=<value>]... [<buildaction>]...xcodebuild [-project <projectname>] -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings [-json]] [-showdestinations] [<buildsetting>=<value>]... [<buildaction>]...xcodebuild -workspace <workspacename> -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [-showdestinations] [<buildsetting>=<value>]... [<buildaction>]...xcodebuild -version [-sdk [<sdkfullpath>|<sdkname>] [-json] [<infoitem>] ]xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]xcodebuild -showsdks [-json]xcodebuild -exportArchive -archivePath <xcarchivepath> [-exportPath <destinationpath>] -exportOptionsPlist <plistpath>xcodebuild -exportNotarizedApp -archivePath <xcarchivepath> -exportPath <destinationpath>xcodebuild -exportLocalizations -localizationPath <path> -project <projectname> [-exportLanguage <targetlanguage>...]xcodebuild -importLocalizations -localizationPath <path> -project <projectname>xcodebuild -resolvePackageDependencies [-project <projectname>|-workspace <workspacename>] -clonedSourcePackagesDirPath <path>
这里命令告诉我了我们几个最常用的,我们这里是xcworkspace
根据选择我们选择对应的那个写,具体的参数这里就不介绍了,主要介绍下最后几个[<buildsetting>=<value>]
这里代表我们可以把xcode build setting那里面的基本参数进行重新赋值修改,这里我们就把默认编译路径修改了,例如以下两个,或者其他环境参数的修改
TARGET_BUILD_DIR=${arm_64_dir} 或者 CONFIGURATION_BUILD_DIR=${arm_64_dir}
具体脚本如下
#!/bin/bashread -p "请输入版本号码: " versionworkspaceName="SDKDemo"targetName="MKJPay"name="${targetName}-v${version}"tagertDir=~/Desktop/${name}if [[ -d ${tagertDir} ]]; thenecho "已存在--->${tagertDir}"elseecho "不存在,创建文件夹--->${tagertDir}"mkdir -p ${tagertDir}fix86_64_dir=${tagertDir}/x86_64arm_64_dir=${tagertDir}/arm64arm_v7_dir=${tagertDir}/armv7#echo "${x86_64_dir}---${arm_64_dir}--${arm_v7_dir}"xcodebuild clean build -workspace ${workspaceName}.xcworkspace -scheme ${workspaceName} -configuration Debug -arch x86_64 -sdk iphonesimulator12.1 TARGET_BUILD_DIR=${x86_64_dir}xcodebuild clean build -workspace ${workspaceName}.xcworkspace -scheme ${workspaceName} -configuration Debug -arch arm64 -sdk iphoneos12.1 TARGET_BUILD_DIR=${arm_64_dir}xcodebuild clean build -workspace ${workspaceName}.xcworkspace -scheme ${workspaceName} -configuration Debug -arch armv7 -sdk iphoneos12.1 TARGET_BUILD_DIR=${arm_v7_dir}lipo -create "${x86_64_dir}/lib${targetName}.a" "${arm_64_dir}/lib${targetName}.a" "${arm_v7_dir}/lib${targetName}.a" -o "${tagertDir}/MKJPay.a"open ${tagertDir}
这里的脚本会在桌面生成一个文件夹,里面存放x86_64
,arm64
,armv7
三个架构,然后会合成一个MKJPay.a
的包含所有的最终架构
然后把整个.a文件拖入Demo工程测试即可,不过也可以用脚本复制过去,这里就不写了。
对了,如果脚本不能./
出来,记得chmod 777 xxx.sh
一下
Demo地址
参考文章:
Cocoapods Blog
Cocoapods
Cocoapods 二进制
组件化
Cocoapods插件打包