iOS SDK(framework)开发

727 阅读8分钟

前言

之前的时候做过iOS 的sdk开发,上次做sdk已经过去两年了,但是最近在一个微信群里有人问,我就顺便帮他解决了一些问题 总的来说sdk的开发不难,就是要懂得一些配置,先简单记录下自己搭建的过程,希望后面有用

一、关于SDK

有关iOS SDK的相关理论知识请参考如下文章

二、如何制作SDK(framework)

1、创建SDK工程

image.png

通过如上方式创建SDK工程,这里我创建名为GZAnyWhereSDK的SDK工程,如下图所示: image.png 生成的工程只有GZAnyWhereSDK.h这一个头文件,一般我们将需要暴露的头文件都放在这个头文件中,以供外部调用。

2、开发SDK功能

image.png

如上图,我创建了一个名为GZAnyWhereLocation的类文件,并且将它设置为public,这样就可以把这个文件导入GZAnyWhereSDK.h头文件,以供外部调用

image.png

GZAnyWhereLocation的类文件中实现SDK的相关功能,这里只实现一个类方法作为参考 image.png 你可以根据自己的业务需求开发更多的功能

4、SDK打包

(1)创建打包工程Aggregate

image.png

这里我创建了名为GZAnyWhereSDK-Script的打包工程 image.png

(2)添加依赖的target

image.png

(3)创建Run script

image.png

添加如下脚本,脚本是通用的,可以直接拿去用

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}"

image.png

修改在release环境下进行构建 image.png

(4)SDK工程配置

1、设置SDK支持的最低版本 image.png

2、设置SDK为静(动)态库 image.png

3、设置SDK支持的设备类型 image.png

4、设置SDK的Other Linker Flags添加 -ObjC image.png

Other Linker Flags添加 -ObjC的说明:

  • 1、确保链接器正确的处理目标文件中的OC类和类别,以避免在某些情况下静态库或框架中的类别或对象被编译器优化掉,导致报符号找不到的错误;
  • 2、在链接静态库或框架时,链接器通常会剔除掉未直接引用的符号(包括分类的方法),添加-ObjC后,链接器就会把所有OC类和类别进行加载链接,避免在运行时报找不到符号的错误;

5、设置Build Active Architecture Only image.png

  • 1.Debug时设置为YES,可以在调试时只编译当前所调试设备的架构类型的SDK包,如当你iPhone真机调试时,只编译支持iPhone运行的arm64的SDK架构包;
  • 2.Release时设置为NO,使得在发布SDK打包时,编译支持所有架构的SDK包,如支持iPhone、iPad、iWatch等;

6、查看并替加对外暴露的头文件 image.png 可以根据需要将Private和Object中的文件拖动到Public中,以暴露给外部调用。

7、设置Perform Single-Object Prelink为YES image.png 作用是xcode会在链接前将所有目标文件合并成一个中间文件,这样可以减少链接时的重复动作,提高构建性能;特别是在大型项目中可以明显减少构建时间。

(5)构建SDK

点击红色箭头指向的按钮构建SDK image.png

运行报错 image.png 这个错误信息表明,rm 命令在尝试读取文件 /Users/xiongjinhui/Desktop/SDK/GZAnyWhereSDK/GZAnyWhereSDK/GZAnyWhereSDK/GZAnyWhereSDK.framework 时被沙盒机制拒绝了。沙盒机制限制了应用程序对文件系统的访问,以提高安全性。

解决编译报错 image.png User Script Sandboxing是xcode的一种安全机制,通过限制脚本对系统资源的访问,将脚本执行限制在受控的环境中,防止潜在的恶意代码执行和未经授权的资源访问。

重新执行得到SDK包 image.png

通过以上步骤就完成了SDK的开发与构建了,但是这种只能先开发完,再将SDK放到Demo中进行调试,对开发者来说特别不友好,那么我们该如何一边开发一边调试呢?

5、边开发边调试SDK

首先将SDK放在一个文件目录中,如下图 image.png

在SDK所在目录下创建xcworkspace和一个demo工程 image.png 将SDK工程、demo工程和.xcworkspace文件放在同一目录下,打开.xcworkspace,依次将SDK工程和demo工程的project拖入.xcworkspace的同级目录下,如上图。

将SDK和demo工程进行关联,在demo工程中导入SDK image.png

在demo工程中导入SDK头文件并调用了SDK的方法 image.png

这样我们就可以一边开发一边调试SDK了

6、如何让SDK和demo工程依赖第三方库

image.png

在对工程进行pod 管理时,一般我们是在.xcodeproj所在的目录执行pod init从而创建podfile文件,再在podfile文件中引入第三方库后,执行pod install,之后会自动创建一个.xcworkspace文件来管理主工程和pod工程;但是如上图所示,我们已经自己创建了pod工程,并且将SDK工程和demo工程导入xcworkspace进行关联,这种情况下再使用以前pod的创建方式就不行了。

创建podfile image.png 在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 image.png

执行pod install后的目录文件如下图所示 image.png

打开xcworkspace并选中demo工程执行 image.png 执行后可能会报第三方库的错误,我这里是因为导入的Masonry设置的支持的iOS版本太低了导致的。我们将pod下的所有第三方库支持的最低iOS版本设置成iOS15.0,然后在执行就不会报上面的错误了 image.png

在SDK工程和demo工程中引入第三方库头文件,并使用第三方库的功能 image.png

至此我们就完成了给SDK工程和Demo工程同时引入pod管理第三方库的功能

三、自动化脚本编译SDK

下面我们看一下如何使用shell脚本自动化打包SDK

在xcworkspace所在目录下创建shell脚本 image.png

编写脚本

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------------------"

执行脚本

image.png 发现报错了,脚本执行被拒绝了,这是因为没有给脚本设置权限导致的

设置脚本的执行权限 image.png 使用sudo chmod +wrx build.sh的意思是给build.sh脚本设置可读可写可执行权限,这样我们再执行./build.sh进就可以正常编译了,最终我们得到的产物如下图所示

image.png

四、SDK开发组件化

1、问题描述

为了能将项目中的代码和SDK进行共用,我们可以选择组件化实现。当使用swift进行开发时,如果在swift中引用了某个组件,这时需要导入这个组件:

arduino
 代码解读
复制代码
import DolphinFramework

由于SDK最终需要提供给其他项目使用,一般是以framework的方式提供,swift语言暴露接口的方式是使用publicopen进行修饰。但由于依赖了组件,此时在接入SDK的项目中,就需要提供对应组件的framework,否则,就算使用静态库的方式也不行。我遇到的问题如下:

image.png

network-extension小组件中使用到了SDK中的类DacsVPNExtension.swift,DacsVPNExtension.swift导致入了DCWireGuard,但在demo工程中,只提供了MobileDACSSDK,所以这里才会报错。

2、解决方案

我想了几种方案尝试解决,如:

  • 1.想办法实现swift 私有化import DCWireGuard,可惜失败(望大神指导)
  • 2.改用OC实现,提供.h文件给外部调用,可以解决

五、参考链接

www.jianshu.com/p/cc2d3cd54…;

www.jianshu.com/p/ed65aed59…;

www.jianshu.com/p/398d015b1…;

www.jianshu.com/p/49f82a59b….