前言
之前的时候做过iOS 的sdk开发,上次做sdk已经过去两年了,但是最近在一个微信群里有人问,我就顺便帮他解决了一些问题 总的来说sdk的开发不难,就是要懂得一些配置,先简单记录下自己搭建的过程,希望后面有用
一、关于SDK
有关iOS SDK的相关理论知识请参考如下文章
二、如何制作SDK(framework)
1、创建SDK工程
通过如上方式创建SDK工程,这里我创建名为
GZAnyWhereSDK
的SDK工程,如下图所示:生成的工程只有
GZAnyWhereSDK.h
这一个头文件,一般我们将需要暴露的头文件都放在这个头文件中,以供外部调用。
2、开发SDK功能
如上图,我创建了一个名为
GZAnyWhereLocation
的类文件,并且将它设置为public
,这样就可以把这个文件导入GZAnyWhereSDK.h
头文件,以供外部调用
在
GZAnyWhereLocation
的类文件中实现SDK的相关功能,这里只实现一个类方法作为参考你可以根据自己的业务需求开发更多的功能
4、SDK打包
(1)创建打包工程Aggregate
这里我创建了名为
GZAnyWhereSDK-Script
的打包工程
(2)添加依赖的target
(3)创建Run script
添加如下脚本,脚本是通用的,可以直接拿去用
sh
代码解读
复制代码
#要build的target名
#!/bin/sh
#要build的target名
TARGET_NAME=${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}/"
#创建输出目录,并删除之前的framework文件
mkdir -p "${UNIVERSAL_OUTPUT_FOLDER}"
rm -rf "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework"
#分别编译模拟器和真机的Framework
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
#拷贝framework到univer目录
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_FOLDER}"
lipo "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" -remove arm64 -output "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}"
#合并framework,输出最终的framework到build目录
lipo -create -output "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"
#删除编译之后生成的无关的配置文件
dir_path="${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]]
then
rm -f "${dir_path}/${file}"
fi
done
#判断build文件夹是否存在,存在则删除
if [ -d "${SRCROOT}/build" ]
then
rm -rf "${SRCROOT}/build"
fi
rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"
#打开合并后的文件夹
open "${UNIVERSAL_OUTPUT_FOLDER}"
修改在release环境下进行构建
(4)SDK工程配置
1、设置SDK支持的最低版本
2、设置SDK为静(动)态库
3、设置SDK支持的设备类型
4、设置SDK的
Other Linker Flags
添加-ObjC
Other Linker Flags
添加 -ObjC
的说明:
- 1、确保链接器正确的处理目标文件中的OC类和类别,以避免在某些情况下静态库或框架中的类别或对象被编译器优化掉,导致报符号找不到的错误;
- 2、在链接静态库或框架时,链接器通常会剔除掉未直接引用的符号(包括分类的方法),添加
-ObjC
后,链接器就会把所有OC类和类别进行加载链接,避免在运行时报找不到符号的错误;
5、设置
Build Active Architecture Only
- 1.Debug时设置为YES,可以在调试时只编译当前所调试设备的架构类型的SDK包,如当你iPhone真机调试时,只编译支持iPhone运行的arm64的SDK架构包;
- 2.Release时设置为NO,使得在发布SDK打包时,编译支持所有架构的SDK包,如支持iPhone、iPad、iWatch等;
6、查看并替加对外暴露的头文件
可以根据需要将Private和Object中的文件拖动到Public中,以暴露给外部调用。
7、设置
Perform Single-Object Prelink
为YES作用是xcode会在链接前
将所有目标文件合并成一个中间文件
,这样可以减少链接时的重复动作,提高构建性能;特别是在大型项目中可以明显减少构建时间。
(5)构建SDK
点击红色箭头指向的按钮构建SDK
运行报错
这个错误信息表明,
rm
命令在尝试读取文件/Users/xiongjinhui/Desktop/SDK/GZAnyWhereSDK/GZAnyWhereSDK/GZAnyWhereSDK/GZAnyWhereSDK.framework
时被沙盒机制拒绝了。沙盒机制限制了应用程序对文件系统的访问,以提高安全性。
解决编译报错
![]()
User Script Sandboxing
是xcode的一种安全机制,通过限制脚本对系统资源的访问,将脚本执行限制在受控的环境中,防止潜在的恶意代码执行和未经授权的资源访问。
重新执行得到SDK包
通过以上步骤就完成了SDK的开发与构建了,但是这种只能先开发完,再将SDK放到Demo中进行调试,对开发者来说特别不友好,那么我们该如何一边开发一边调试呢?
5、边开发边调试SDK
首先将SDK放在一个文件目录中,如下图
在SDK所在目录下创建xcworkspace和一个demo工程
将SDK工程、demo工程和.xcworkspace文件放在同一目录下,打开.xcworkspace,依次将SDK工程和demo工程的project拖入.xcworkspace的同级目录下,如上图。
将SDK和demo工程进行关联,在demo工程中导入SDK
在demo工程中导入SDK头文件并调用了SDK的方法
这样我们就可以一边开发一边调试SDK了
6、如何让SDK和demo工程依赖第三方库
在对工程进行pod 管理时,一般我们是在
.xcodeproj
所在的目录执行pod init
从而创建podfile
文件,再在podfile
文件中引入第三方库后,执行pod install
,之后会自动创建一个.xcworkspace
文件来管理主工程和pod工程;但是如上图所示,我们已经自己创建了pod工程,并且将SDK工程和demo工程导入xcworkspace进行关联,这种情况下再使用以前pod的创建方式就不行了。
创建
podfile
在SDK的根目录上执行
pod init
创建podfile文件,并将其移动到xcworkspace所有的目录
打开
podfile
文件并设置ruby脚本如下:
ruby
代码解读
复制代码
# Uncomment the next line to define a global platform for your project
workspace 'GZAnyWhereSDKWorkspace.xcworkspace'
platform :ios, '15.0'
target 'GZAnyWhereSDK' do
# Comment the next line if you don't want to use dynamic frameworks
project './GZAnyWhereSDK/GZAnyWhereSDK.xcodeproj'
use_frameworks!
# Pods for GZAnyWhereSDK
pod 'FMDB'
end
target 'GZAnyWhereSDKDemo' do
project './GZAnyWhereSDKDemo/GZAnyWhereSDKDemo.xcodeproj'
use_frameworks!
# Pods for GZAnyWhereSDK
pod 'Masonry'
end
- 1、
workspace 'GZAnyWhereSDK.xcworkspace'
意思是创建名为GZAnyWhereSDK的xcworkspace文件,你也可以使用其他名称- 2、
project './GZAnyWhereSDK/GZAnyWhereSDK.xcodeproj'
解释:因为podfile文件所在目录没有project工程,所以需要为'GZAnyWhereSDK'这个target指定一个project,同样的需要为'GZAnyWhereSDKDemo'target指定一个project
删除
之前创建的xcworkspace
文件,因为pod install
后会自动创建
并管理SDK工程、demo工程和pod工程的xcworkspace
执行
pod install
后的目录文件如下图所示
打开xcworkspace并选中demo工程执行
执行后可能会报第三方库的错误,我这里是因为导入的Masonry设置的支持的iOS版本太低了导致的。我们将pod下的所有第三方库支持的最低iOS版本设置成iOS15.0,然后在执行就不会报上面的错误了
在SDK工程和demo工程中引入第三方库头文件,并使用第三方库的功能
至此我们就完成了给SDK工程和Demo工程同时引入pod管理第三方库的功能
三、自动化脚本编译SDK
下面我们看一下如何使用shell脚本自动化打包SDK
在xcworkspace所在目录下创建shell脚本
编写脚本
sh
代码解读
复制代码
#!/bin/bash
# 停止脚本遇到任何错误时
set -e
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 项目配置
WORKSPACE_PATH="$SCRIPT_DIR/GZAnyoneSDK.xcworkspace"
SCHEME="GZAnyoneSDK"
CONFIGURATION="Release"
DERIVED_DATA_DIR="$SCRIPT_DIR/../DerivedData"
FRAMEWORK_NAME="GZAnyoneSDK"
# 创建输出目录
mkdir -p ${DERIVED_DATA_DIR}/universal
# 编译真机架构
xcodebuild -configuration $CONFIGURATION -derivedDataPath ${DERIVED_DATA_DIR}/iOS -workspace $WORKSPACE_PATH -scheme $SCHEME -sdk iphoneos clean build
# 编译模拟器架构
xcodebuild -configuration $CONFIGURATION -derivedDataPath ${DERIVED_DATA_DIR}/simulator -workspace $WORKSPACE_PATH -scheme $SCHEME -sdk iphonesimulator clean build
# 创建XCFramework
xcodebuild -create-xcframework \
-framework ${DERIVED_DATA_DIR}/iOS/Build/Products/Release-iphoneos/${FRAMEWORK_NAME}.framework \
-framework ${DERIVED_DATA_DIR}/simulator/Build/Products/Release-iphonesimulator/${FRAMEWORK_NAME}.framework \
-output ${DERIVED_DATA_DIR}/universal/${FRAMEWORK_NAME}.xcframework
echo "Universal XCFramework is created at: ${DERIVED_DATA_DIR}/universal/${FRAMEWORK_NAME}.xcframework"
echo "-------------编译完成---------------------------"
执行以上脚本,最终会得到一个xcframework后缀的SDK,
xcframework
是一种打包方式,可以将多种平台
(iPhone、iPad、Mac、Simulator或arm64、i386、x86_64)的库文件打包在一起
上面的脚本之所以需要打包成xcframework
主要是因为,当真机
打包的SDK是arm64架构
的,模拟器
打包的是x86_64和arm64
的,使用xcframework可以将他们合并在一起
。
如果只想打包单一架构的SDK,可以使用如下脚本:
sh
代码解读
复制代码
echo "------------------Start-----------------------"
#获取脚本所在目录
project_path=$(cd `dirname $0`; pwd)
#设置framework名称
framework_name="GZAnyoneSDK"
#设置workspace路径
work_space="${project_path}/GZAnyoneSDK.xcworkspace"
#设置framework产物路径
work_dir="${project_path}/BuildDeriver/Build/Products"
#设置工作路径
deriver_data_dir="${project_path}/../BuildDeriver"
#生成的SDK所在的路径
#真机
device_dir=${work_dir}/'Release-iphoneos'/${framework_name}'.framework'
echo "device_dir is: ${device_dir}"
#模拟器
#simulator_dir=${work_dir}/'Release-iphoneosimulator'/${framework_name}'.framework'
#echo "simulator_dir is:${simulator_dir}"
echo "------------------clean begin----------------------"
#删除上一次的文件
sudo rm -r ${deriver_data_dir}
echo "------------------clean end------------------------"
#xcode编译
echo "------------------build device begin----------------------"
#编译真机包
xcodebuild -configuration 'Release' -derivedDataPath ${deriver_data_dir} -workspace ${work_space} -scheme ${framework_name} ONLY_ACTIVE_ARCH=NO -sdk iphoneos clean build
echo "------------------build device completed------------------"
#echo "------------------build simulator begin----------------------"
#编译模拟器包
#xcodebuild -configuration 'Release' -derivedDataPath ${deriver_data_dir} -workspace ${work_space} -scheme ${framework_name} ONLY_ACTIVE_ARCH=NO -sdk iphonesimulator clean build
#echo "------------------build simulator completed------------------"
执行脚本
发现报错了,脚本执行被拒绝了,这是因为没有给脚本设置权限导致的
设置脚本的执行权限
使用
sudo chmod +wrx build.sh
的意思是给build.sh脚本设置可读可写可执行权限,这样我们再执行./build.sh
进就可以正常编译了,最终我们得到的产物如下图所示
四、SDK开发组件化
1、问题描述
为了能将项目中的代码和SDK进行共用,我们可以选择组件化实现。当使用swift进行开发时,如果在swift中引用了某个组件,这时需要导入这个组件:
arduino
代码解读
复制代码
import DolphinFramework
由于SDK最终需要提供给其他项目使用,一般是以framework的方式提供,swift语言暴露接口的方式是使用public
或open
进行修饰。但由于依赖了组件,此时在接入SDK的项目中,就需要提供对应组件的framework,否则,就算使用静态库的方式也不行。我遇到的问题如下:
在
network-extension
小组件中使用到了SDK中的类DacsVPNExtension.swift,DacsVPNExtension.swift导致入了DCWireGuard,但在demo工程中,只提供了MobileDACSSDK,所以这里才会报错。
2、解决方案
我想了几种方案尝试解决,如:
- 1.想办法实现swift 私有化import DCWireGuard,
可惜失败(望大神指导)
;- 2.改用OC实现,提供.h文件给外部调用,
可以解决
。