前言:本篇主要是讲解从无到有的sdk开发,以及对sdk开发过程中遇到的问题记录,方便以后遇到相似问题可以轻松解决。如有不对的,期望大家多多提出...本文会一直持续更新
第一讲:还是先从最基础的开始,创建一个开发sdk的工程,最常用的还是framework。
说明下,本次创建是结合实际开发流程来的,创建demo调试工程,将主要的功能写在framework里面,最终交付framework给客户。
一:来个初级版本的,只有一个framework的情况
- 先在桌面创建一个空的文件夹,用来放demo和framework
- 创建一个普普通通的demo,demo存储位置放在刚刚创建的文件夹里面
- 最重要的,开始创建一个
静态framework
创建好后,位置和demo平级。这样就有一个开发调试demo,一个打包的sdk。我这里创建的framework名称是General。
点击打开General,我们去看看构造,本次sdk只支持ios系统,所以先把mac_os先删掉。products
文件目录下,放的就是我们想要的framework,我们要开发需要创建的类,就放在General
文件下。
再修改一下配置参数,请移步第二讲,`打包配置的参数`
-
接下来在General里面一个类,添加一个方法,测试下
要想外部能访问到这个类,还需要将person类的.h文件
设置为public
-
将
demo
和General
关联起来,方便调试Genera库
里面的方法
这个时候需要注意⚠️,我们创建的demo只是一个普通工程,是无法和其他库关联起来的,这个时候我们需要创建一个黑科技
,那就是workspace
。最简单的创建方式就是pods,是不是突然想到了😄,当我们使用cocopods的时候,工程pods后会出来一个带有workspace后缀的文件。那给项目添加cocopods,我就不说了,点击带有workspace
后缀的文件,重新打开工程
打开后,这个时候才能开始关联'General',在左边空白处,右键点击Add Files to ...
,进入后,添加General
的工程文件,一定要取消勾选copy item into groups folder
点击'add',添加后,重新回到工程,这个时候会看到General
已经来到了我们的工程项目中了。看一下工程目录,是不是一样的
进行到这里,我们只完成了一半,接下来是将General
的framework
,关联到demo里面。点击demo工程的target,在frameworks,libraies,and Embedded content
里面添加,点击General.framework
,添加吧
添加完成后,还需要最后一步设置,将embed设置为
Do Not Embed
第二讲 打包配置的参数
- 配置成静态库,在bulild setting 里面修改。
Mach-O Type , 设置为Static Library。这个是设置为静态库
- 配置Build Active Architecture。选择NO,表示不仅仅构建当前选择设备的架构
Build Active Architecture Only = NO
- 配置 Strip Debug Symbols During Copy
Strip Debug Symbols During Copy 设置为 NO,不然断点不会走
- 设置Excluded Architecture(排除架构)
General -> XXSDK -> Build Settings -> Excluded Architectures -> Release -> 添加选择Any iOS Simulator SDK -> 添加arm64
因为Xcode12以后,构建模拟器的库也默认支持arm64架构,这将会导致真机和模拟器的库合并时失败(fatal error: xx1/XLTestFrame and xx2/XLTestFrame have the same architectures (arm64) and can't be in the same fat output file),报错原因:真机库和模拟器库都包含arm64架构
第三讲:有时我们想知道当前的sdk包,支持在哪些手机上运行,这时就需要去查看。查看sdk 包 支持的设备版本
- 打开终端
cd 把桌面上的framework拖进来吧
2.1 使用 lipo -info 关键字查看framework,后面加上你framework名字
lipo -info frameworkName
2.2 使用 lipo -info 关键字查看.a的静态库,后面加上全称
lipo -info frameworkName.a
- 1 示例 opencv2.framework
cd /Users/zhangsan/Desktop/opencv2.framework
lipo -info opencv2
3.2 示例 LibFaceRecognition.a
cd /Users/zhangsan/Desktop/MSRecordAVSDK
lipo -info LibFaceRecognition.a
根据查询出来的指令集,查看对应支持的设备
iOS设备支持的指令集
armv6:
iPhone, iPhone 3G, iPod 1G/2G
armv7:
iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
armv7s:
iPhone 5, iPhone 5c, iPad 4
arm64:
iPhone X,iPhone 8(Plus),iPhone 7(Plus),iPhone 6(Plus),iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
arm64e:
iPhone XS\XR\XS Max
系统默认的指令集:
· $(SDKROOT)=iphoneos: armv7 arm64
· $(SDKROOT)=iphonesimulator: i386 x86_64
· $(SDKROOT)=macosx: x86_64
设置你的sdk支持的版本
如果你想设置你的sdk支持的版本为armv7 arm64
,你需要将你的系统版本设置为ios8
。
如果你想设置你的sdk支持的版本为arm64
,你需要将你的系统设置为ios11
。
第四讲:理解Build Active Architecture Only
往往我们在打包sdk的时候,会考虑其兼容性,提供给客户的版本,要兼容,不能报错。哪怕是功能不支持这个版本,但是至少编译你得过,然后再考虑怎么避开低系统版本的。
Build Active Architecture Only
一共有两个bool值,YES
、NO
。他们的含义分别是:
设置为yes,是只编译当前的architecture版本,是为了编译速度更快。
一般我们在debug环境下这样设置。
而设置为no时,系统会编译所有architecture下的版本
一般我们在release环境下这样设置。
所以我们得设置为NO,向下兼容你所有支持的版本。我在第二讲的时候,讲解了怎么设置支持
第五讲 开发中遇到的,觉得可以记录下的知识点
1. 配置代码选择性编译
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
// iOS9以后 执行这里
#else
//ios9以前 执行这里
#endif
2. 打包静态库时,xib不会被编译成nib,而如果你直接在主项目中使用xib,编译的时候就会把xib编程nib。因此需要我们手动把xib编程nib
使用命令行:ibtool --errors --warnings --output-format human-readable-text --compile /Users/**/Desktop/ViewController.nib /Users/**/Desktop/ViewController.xib
3. 查询一个库,是静态库还是动态库
-
首先在工程中找到当前库,鼠标右键
show in finder
,找到文件目录
打开终端,输入查询命令
cd 将当前的库拖进来
file 库名
示例: AFNetworking.framework
cd /Users/***/AFNetworking.framework
file AFNetworking
输出
current ar archive:说明是静态库
Mach-0 dynamically:说明是动态库
4. 查询动态库是否签名
cd 将当前的库拖进来
codesign -dv 库名
示例:ZRTC.framework
cd /Users/***/ZRTC.framework
codesign -dv ZRTC
输出:
Signature size=4895,Signed Time=Mar 2, 2022 at 10:14:14 AM :说明已签名
code object is not signed at all : 说明没有签名
5. 利用脚本自动忽略x86模拟器架构
背景:为了方便模拟器运行编辑,一般我们的framework会包含arm64和x86架构,但是archive打包上架时,会报错,不允许发布,需要将x86的架构给删除掉。这个时候就可以利用xcode自带的脚本工具
- 先创建脚本编辑器
Target -> build Phaes -> + -> New Run Script Phases
- 在编辑器里面输入脚本代码
#!/bin/sh
# Strip invalid architectures
strip_invalid_archs() {
binary="$1"
echo "current binary ${binary}"
# Get architectures for current file
archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
stripped=""
for arch in $archs; do
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
if [ -f "$binary" ]; then
# Strip non-valid architectures in-place
lipo -remove "$arch" -output "$binary" "$binary" || exit 1
stripped="$stripped $arch"
fi
fi
done
if [[ "$stripped" ]]; then
echo "Stripped $binary of architectures:$stripped"
fi
}
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
strip_invalid_archs "$FRAMEWORK_EXECUTABLE_PATH"
done
报错整理,开发中遇到的报错,持续更新....
1. 引入的库,设置的embed
bitcode_strip /Users/xx/Library/Developer/Xcode/DerivedData/xxx-
ageraaywrmdjawgxyquufnhwqqnf/Build/Products
/Debug-iphoneos/xxx.framework/xxx: /Applications/Xcode.app/Contents
/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/bitcode_strip exited with 1
解决方案,设置为 Do Not Embed
,主要是因为当前framework
是个静态库。如果是动态库,那么就需要设置为Embed & Sign
,目前appstore
是可以上线动态库,但是必须配置库的签名和app的签名一致
2. xcode15 运行ios12系统崩溃
dyld: Library not loaded: /System/Library/Frameworks/MetricKit.framework/MetricKit
Referenced from: /var/containers/Bundle/Application/B5F01DF6-99FE-44C9-A4FB-C4D781FCAD66/ZKJAIRecord.app/ZKJAIRecord
Reason: image not found
崩溃分析:由于MetricKit库是在Ios13系统才出现的,所以会崩溃。
解决办法:如果没有使用,就删除掉该库。如果要使用,那么就需要改最低支持版本。
3. framework开发时,崩溃到category
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[__NSCFConstantString zkj_toTest]: unrecognized selector sent to instance 0x10455c4b8'
崩溃分析:
苹果官方Q&A上有这么一段话:
The "selector not recognized" runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.
翻译过来,大概意思就是Objective-C的链接器并不会为每个方法建立符号表,而是仅仅为类建立了符号表。这样的话,如果静态库中定义了已存在的一个类的分类,链接器就会以为这个类已经存在,不会把分类和核心类的代码合起来。这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。
解决方法
解决方法,在Other Linker Flags里加上所需的参数,用到的参数一般有以下3个:
-ObjC
: 链接器会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中,虽然这样可能会因为加载了很多不必要的文件而导致可执行文件变大-all_load
: 会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。-force_load
: 所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。
找到Build settings->Linking->Other Linker Flags,将此属性修改成-ObjC
3. framework开发时,编译报错
Undefined symbols for architecture arm64:
"__swift_FORCE_LOAD_$_swiftCompatibility50", referenced from:
__swift_FORCE_LOAD_$_swiftCompatibility50_$_ZKJAIRTC in ZKJAIRTC[27](ZKJAIRTCErrorCodes.o)
__swift_FORCE_LOAD_$_swiftCompatibility50_$_ZKJAICore in ZKJAICore[280](ZKJInnerShadowView.o)
__swift_FORCE_LOAD_$_swiftCompatibility50_$_ZKJAICommon in ZKJAICommon[96](ZKJWindowUtil.o)
"__swift_FORCE_LOAD_$_swiftCompatibility51", referenced from:
__swift_FORCE_LOAD_$_swiftCompatibility51_$_ZKJAIRTC in ZKJAIRTC[27](ZKJAIRTCErrorCodes.o)
__swift_FORCE_LOAD_$_swiftCompatibility51_$_ZKJAICore in ZKJAICore[280](ZKJInnerShadowView.o)
__swift_FORCE_LOAD_$_swiftCompatibility51_$_ZKJAICommon in ZKJAICommon[96](ZKJWindowUtil.o)
"__swift_FORCE_LOAD_$_swiftCompatibility56", referenced from:
__swift_FORCE_LOAD_$_swiftCompatibility56_$_ZKJAIRTC in ZKJAIRTC[27](ZKJAIRTCErrorCodes.o)
__swift_FORCE_LOAD_$_swiftCompatibility56_$_ZKJAICore in ZKJAICore[280](ZKJInnerShadowView.o)
__swift_FORCE_LOAD_$_swiftCompatibility56_$_ZKJAICommon in ZKJAICommon[96](ZKJWindowUtil.o)
"__swift_FORCE_LOAD_$_swiftCompatibilityConcurrency", referenced from:
__swift_FORCE_LOAD_$_swiftCompatibilityConcurrency_$_ZKJAIRTC in ZKJAIRTC[27](ZKJAIRTCErrorCodes.o)
__swift_FORCE_LOAD_$_swiftCompatibilityConcurrency_$_ZKJAICore in ZKJAICore[280](ZKJInnerShadowView.o)
__swift_FORCE_LOAD_$_swiftCompatibilityConcurrency_$_ZKJAICommon in ZKJAICommon[96](ZKJWindowUtil.o)
"__swift_FORCE_LOAD_$_swiftCompatibilityDynamicReplacements", referenced from:
__swift_FORCE_LOAD_$_swiftCompatibilityDynamicReplacements_$_ZKJAIRTC in ZKJAIRTC[27](ZKJAIRTCErrorCodes.o)
__swift_FORCE_LOAD_$_swiftCompatibilityDynamicReplacements_$_ZKJAICore in ZKJAICore[280](ZKJInnerShadowView.o)
__swift_FORCE_LOAD_$_swiftCompatibilityDynamicReplacements_$_ZKJAICommon in ZKJAICommon[96](ZKJWindowUtil.o)
ld: symbol(s) not found for architecture arm64
Linker command failed with exit code 1 (use -v to see invocation)
问题排查分析,是因为在开发framework时,主体语言是OC,但是嵌入了swift语言类。在demo层也是OC语言框架,导致的。
解决办法:在App工程里面,新建一个swift类,空文件即可,不需要任何代码。再次编辑完美解决。
PS
: 一般出现这个问题,还有就是在Framework引用了第三方类,但是在外部App未引用该类
4. Xcode升级15
报错:Sandbox: rsync.samba(38011) deny(1) file-write-create
使用 Xcode15 新建项目后,pod 引入部分第三方会报上面的错误
解决办法:Build Settings 搜索 sandbox,把 Build Options
中的 User Script Sandboxing
改为 NO
5. Pod导入三方库后,使用时崩溃
在开发framework时,用到了三方masonry
,App工程中引用该framework,pod里添加'masonry',结果运行时,崩溃在了masonry的方法上。
解决办法:Build Settings 搜索 linker,在 Build setting
中的 Other Linker Flags
,添加 $(inherited)
通过CocoaPods集成的项目,
$(inherited)
将会包含Pods.xcodeproj中的配置。inherited 是继承的意思。
6. 隐私政策,三方SDK以二进制的形式加载
这里列举一下相关命令行
# 1.搜索指定目录下是否包含_CodeSignature签名目录
find -name -type d "_CodeSignature"
# 2.列出本地与代码签名相关的证书
security find-identity -v -p codesigning
# 3.对SDK进行签名,苹果视频https://developer.apple.com/videos/play/wwdc2023/10061
codesign --timestamp -v --sign "Your Certificate Name" </path/to/SDK.framework>
# 4.验证签名(可以看到相关签名信息)
codesign -dvvv </path/to/SDK.framework>
7. 使用CocoaPods管理三方库时,Podfile文件中关于use_frameworks!的使用有以下特点
说明:s.static_framework = true\false用于在库的podspec文件中声明是否要生成静态库。
Podfile选项 \ Podspec | s.static_framework = true | s.static_framework = false |
---|---|---|
use_frameworks! | 静态库 | 动态库 |
不使用use_frameworks! | 静态库,swift项目导入库时报错,OC不会 | 静态库,swift项目导入库报错,OC不会 |
use_frameworks! :linkage => :static | 静态库 | 静态库 |
use_frameworks! :linkage => :dynamic | 静态库 | 动态库 |
8. iOS12及以下系统Release模式下崩溃
dyld: Library not loaded: /System/Library/Frameworks/SwiftUI.framework/SwiftUI
Referenced from: /Users/handsome/Library/Developer/CoreSimulator/Devices/B7DD7057-0DC7-47B0-B783-D2BA487CE81B/data/Containers/Bundle/Application/3C31E11E-3716-4176-9C0F-6B3521637D07/SDKDemo.app/SDKDemo
Reason: image not found
dyld: launch, loading dependent libraries
关键信息为 第一行
dyld: Library not loaded: /System/Library/Frameworks/SwiftUI.framework/SwiftUI
看样子是SwiftUI的问题,Google一波之后,确认SwiftUI是iOS13开始使用,iOS13以下是没有的。
如果使用了SwiftUI,那么不进行特别处理的话,在iOS12上会崩溃,哪怕你使用了#available进行处理。
因为使用了SwiftUI之后Xcode会默认导入SwiftUI.framework库。而且默认导入的framework都是Required类型。
解决方案:
-
方案一
Build Phases选项卡中的 Link Binary With Libraries 里面添加 SwiftUI.framework,然后将Status改为Optional。
-
方案二
Build Settings 选项卡中的 Other Linker Flags 设置里面添加 -weak_framework SwiftUI