目前手游 iOS SDK 普遍使用手动集成方案。但随着近几年出海热,SDK 需要集成大量第三方库,如 AppsFlyer、Firebase、Admob、Facebook、Twitter、AppLovin 等等。其中一些三方库不支持 CocoaPods 集成,所以我也都使用手动集成。
因为不同的游戏需要接入的第三方库不一样,当用不到的时候,SDK 里就需要剔除相关代码,不然编译也会报错。我的解决方案是通过宏来进行开关隔离,比如:
arduino
#define AppsFlyer 1
#if AppsFlyer
// 调用相关接口
#endif
像这样通过设置 AppsFlyer 为 1 或者 0 来启用和关闭相关代码,关闭时编译器会过滤掉这些代码。此外,当不同的游戏接入的 SDK 出现一些问题需要测试时,就需要频繁改参数、改宏开关。这些操作相当繁琐,所以我都是每个包复制一份 SDK 代码进行设置,用空间换方便。
复制 SDK 时三方库也会复制,三方库可占空间了。以苹果那金子般的内存,过不了多久电脑存储空间就不够用了,必须定期清理。
随着 SDK 趋近稳定,三方库也需要保持更新,我计划将 SDK 改用 CocoaPods 集成。这样不用每次都生成 SDK 副本,也方便研发接入。
最开始是计划把 SDK 里的三方库相关代码拆分成单独的库,让它们单独去依赖对应的三方库,提供接口给 SDK 调用。这样不同的游戏用到哪个三方库就导入对应的库,不再用 SDK 内部宏定义开关。
因为之前从未用 Pod 来管理自己的库,我以为这个事应该很快弄完,结果意外的折腾了好长时间......
我遇到的问题主要归为两类:
- 依赖导致的链接和配置错误;
- 重复引用警告(即把依赖的三方库代码也编译到我的拆分库里,而游戏工程通过 Pod 也会引入一份)。
以下是我将封装层从静态库改成动态库再改成源码依赖的过程和遇到的问题。
1. 静态库阶段
我首先把 SDK 里的三方库相关代码拆分成了一个个静态库,相当于把需要用到的三方库接口封装了一层。为什么不用动态库?因为我不想要 ipa 包里的 Framework 里有太多自定义库。如果是静态库,那这些代码会编译到主二进制里。
在静态库开发工程里测试编译都正常,但在验证 podspec 时报各种错误。原因是依赖各种类型的三方库导致的复杂的链接和配置错误,还要掺杂不同编程语言。折腾了两三天最终放弃了,改为动态库。
注 :
podspec文件就是库的配置和导入说明书,需要在里面设置库的名称、版本、支持系统版本、库文件位置、系统库和三方库依赖,以及一些工程配置如Other Link Flag的设置等。把这个文件成功发布到 Pod 后,大家就可以通过 Pod 来引入你的库了。
2. 动态库阶段
把拆分库改成动态库后,就少了很多链接和配置错误。有一部分三方库并未提供 Pod 引入,只提供了下载链接,这种就无法自动保持更新了,只能我手动去替换三方库。
理论上可以在 podspec 里直接把三方库的下载链接设置为它们官网的下载链接,前提是他们链接保持不变并且里面的文件路径不改。但是这样我感觉不太稳,所以还是手动替换更新。
此外,我的库都是用 OC 写的,有一部分三方库是用 Swift 写的或者混合了 Swift,这种情况需要在 build setting 里调整一些设置。还好有 AI 可以问,不然有得折腾了。
SDK 内部之前会调用很多三方库代码,但是我希望这些三方封装层独立于 SDK,即 SDK 不要依赖这些库。于是原本在 SDK 内部调用的接口就放到外部由研发来调用,后面发现外部接入变得复杂了好多。而且很多接口跟研发也没什么关系,这样做增加了他们的接入难度,和旧版 SDK 接入变化很大。
我把这个情况问了下 AI,AI 给的方案是通过代理实现:即把 SDK 用到的三方库封装层方法定义成一个协议,让封装层自行选择实现协议里的方法。在 SDK 内部调用实现了相关协议方法的代理,代理指定由研发在外部初始化封装层后设置。
当然这个操作后面也省略了,改成在 SDK 内部通过特定方法在判断三方封装层类存在时创建封装层实例并设置为代理。经过这些操作,SDK 接口对比旧版变化就比较小了。
objectivec
ini
// 通过运行时设置代理
SEL sharef = NSSelectorFromString(@"shared");
// AppsFlyer
if (NSClassFromString(SDK_APPSFLYER)) {
self.appsFlyerDelegate = [NSClassFromString(SDK_APPSFLYER) performSelector:sharef];
}
从前面到这里,我的库的开发工程都是用 CocoaPod 来引入的三方库。CocoaPod 会在我编译拆分库的 Target 生成 framework 文件时,把依赖的三方库代码和文件都打包进来。这样一来,当我发布到 Pod 后用 SDK Demo 工程测试时会报警告,内容是三方库在我的拆分库里也有实现代码。在试了 AI 给的很多解决方法都没能解决这个问题后,我只能在库开发工程里移除了 Pods,将三方库改为手动导入,以此避免三方库打进我的封装库里。
这里面有个意外就是 VKID 库(俄罗斯微信)仍然得用 Pods 来引入,因为它是以纯 Swift 源码导入的。我在手动导入 Swift 源码时会报缺失某个代码文件,用 Pod 就正常,按住 command 能找到这个文件代码,但我看不到它在哪------根本没有路径......
此时我也发现在 OC 动态库里依赖的三方 Swift 库会被打包进来,设置为 Do not embed 也没有用。AI 确认了这一点并建议我改回静态库......
无奈我最终只能把封装库改成用 Pod 源代码引入,而且要改就把所有三方封装库都改成源码引入算了。
3. 源代码阶段
当我把其他封装库都成功发布到 Pod 官方后,最后轮到 VKID 时又报错了。Swift 写的 VKID 并未提供 OC 调用的接口,我在封装层用 @objc 做了下桥接,然后用 OC 写的一个 Manager 类来调用转接口,很合理吧?这样就不行,podspec 验证不通过。
折腾来折腾去,最终 AI 给出的方案是把封装层的 Swift 拆分出来单独成库,不要跟 OC 混合在一起。果然拆开后就验证通过了,成功发布到 Pod 官方。
关于库签名和隐私文件
之前是动态库的时候,需要把单独编译成的真机和模拟器 framework 合并成 xcframework,然后再对这些库进行签名。动态库签名是苹果近两年的要求,不签名提包会被打回。改成源码导入后就省去这些麻烦了。
此外,这种源码库的隐私文件要不要加暂时不确定。理论上不用,我暂未提包测试过,但 AI 建议加上,看了下一些三方库也是加了的。
关于 AI
我用到的 AI 主要是 DeepSeek 和 Gemini,都是免费的版本。之前用 AI 养成习惯开新对话,这次咨询的问题比较复杂,我会在一个对话里继续来回问很多次,发现 AI 变强好多,问到后面还会记得前面的推理。
说句题外话,AI编程都出来这么久了,Xcode仍然没有官方支持AI,真是低人一等。随便用一下其他AI编程工具,感觉都不是一个时代的东西。苹果公司的AI部门貌似内斗严重,至今拿不出像样的东西。