Framework制作教程
- 详细步骤图
- 创建源码项目
- 创建对外发布的Framework项目
一、详细步骤图
FM制作流程.png二、创建源码项目
以MAWebKit为例, 源码项目统一都是放到GitLab源码Group下。
1. 创建Framework项目
Xcode -> File -> New -> Project 选择Cocoa Touch Framework模板,新建项目MAWebKit,勾选Include Unit Tests 为写单元测试和Sonar扫描做准备。
点击下一步选择合适的目录保存。
image.png
2. 新生成的项目结构如图
image.png3. 多个组件配置
如果一个组件库包含多个组件,类似Cocoapods的subspec。那需要新建其他组件的Framework。 例如:MAWebKitCore、MAWebKitNFC、MAWebKitGPS、MAWebKitFace
-
Project中选中项目文件,展示出Target列表,最下方选择 + 新建Target。
image.png -
选择Cocoa Touch Framework,填写必要项,该处可以不勾选UnitTests,点击完成创建。
image.png -
按照上述两步骤依次创建MAWebKitNFC、MAWebKitGPS、MAWebKitFace。创建完成后整体项 目结构如图。
image.png
4. 配置项目
- 配置所有Target的Deployment Target 为9.0
- 配置所有Target的版本号Version和Build
- 反选所有Target的Automatically Manage Signing(自动管理签名)
- 配置所有Target的Build Settings,Defines Module 改为 No (不生成module文件)
按照目前这种结构产生的文件目录,在项目目录下比较乱,可以把所有info.plist文件所在文件夹整 理到同一个文件夹Supports下,步骤如下:
- 项目中新建Group,命名为Supports
- 将所有info.plist所在文件夹的Group直接在Xcode中拖到Supports的Group下。
-
配置Build Settings中的info.plist File的值为: Supports/MAWebKit/info.plist (MAWebKit是 Target名字),由于info.plist文件的物理位置发生改变,原来的配置无法找到,所以需要重新进行配置
image.png
image.png
image.png
image.png
5. 配置Framework之间的依赖关系
Framework之间的依赖关系分为两种:
项目内的Framework依赖 和 跨项目的Framework依赖 。
(1) 项目内的Framework依赖: 直接配置
例如:MAWebKitNFC依赖于MAWebKitCore、 MAWebKitGPS依赖于MAWebKitCore
- 选中MAWebKitNFC的Target,点击General标签,
- Frameworks and Libraries 中选择加号,选择 MAWebKitCore ,同时选
择 Do Not Embed 。 - 注意点:如果MAWebKitCore中有Category,则需要在MAWebKitNFC的Build Settings中配置 other linker flag 增加 -ObjC 选项。
(2) 跨项目的Framework依赖:cocoapods管理
例如:MAWebKitGPS依赖⻨芽的 MAGPSKit ,MAWebKitPhoto依赖第三方
库 TZImagePickerController ,这样我们就通过cocoapods来进行管理
- 打开终端Terminal,cd到项目所在目录
- 运行命令: pod init , 这样会针对所有的Target生成一个Pofile文件
- 修改平台的版本号。
- 给target添加私有库或第三方库依赖的配置。
- 删除不需要配置依赖的target项。
- 开启 use_frameworks! 配置,为了打包的时候不将依赖的第三方库打到Framework中。
- 在终端中运行命令: pod install ,生成workspace文件、导入依赖的第三方库。
6. 源码目录结构规划
为了方便统一管理主framework和各个subspec的framework源码,将所有源码按照以下步骤,进行集中 管理。
-
创建源码总目录文件夹 MAWebKit ,用于存储资源文件和源码文件 为了方便源码调试,才设计这样的目录结构(参考了源码cocoapods私有库目录结构)
-
创建资源文件存储文件夹( Assets ),并创建bundle资源文件( MAWebKit.bundle ),如:MAWebKit/Assets/MAWebKit.bundle
image.png -
创建源码存储文件夹( Classes ),并在其下依次创建各个target的源码文件夹,如:
MAWebKit/Classes/MAWebKit , MAWebKit/Classes/MAWebKitCore , MAWebKit/Classes/MAWebKitGPS -
在各个target文件夹下依次创建公有文件夹( Public )和私有文件夹( Private ),如:MAWebKit/Classes/MAWebKit/Public , MAWebKit/Classes/MAWebKit/Private
image.png -
将Supports下的各个target的头文件,依次移动到各自的Public目录下
-
打开framework工程,依次删除Supports目录下相关头文件的引用
-
右键点击 Add Files To ”MAWebKit“ ,添加Assets到工程中
-
右键点击 Add Files To ”MAWebKit“ ,添加Classes到工程中
-
依次编辑各个target的Public中的头文件为对应的framework,且选择可⻅范围为public
-
依次编辑各个target的Public中的总头文件(如 MAWebKitCore.h ), 添加版本号备注信息, 方便集成方快速知道当前所用组件的版本号
image.png
image.png
image.png
image.png
7. 编写Framework源码
选中Public或者Private目录,右键,新建类文件,例如UIView+MAPrivate.h、 UIView+MAPrivate.m。选择对应的Framework(MAWebKitCore)的target。
image.png- 配置对外暴露的.h文件
选中.h文件,直接在最右侧的属性栏中选择可⻅范围(Private/Project/Public)。
在Build Phases的Headers中直接拖拽进行配置。
将所有设置为Public可⻅范围的.h文件引入到和Framework同名的.h文件中,统一使用尖括号 <>的方式引入。
以MAWebKitCore的Framework为例:
#import <MAWebKitCore/MAWebViewController.h>
// ... ...
- 源码编写规则
源码中引入其他类的规范
引入统一target内的类文件直接 #import "类名.h" 即可。
引入依赖的target或者第三方库的,使用 #import <第三方库(Framework)名/全量的头文件名.h>
源码的编写要符合sonar扫描的标准规则
所有能处理的警告信息都要处理掉
对外暴露的使用方法尽可能的简单
尽量没有初始化操作
8. 打包Framework操作
编写打包Framework脚本(最后有参考),命名为make-framework(可以其他名称),可以直接将 make-framework下载下来打包使用。
-
为了方便以后的打包,建议将 make-framework配置到系统的环境变量PATH中去,这样可以直接执行 make-framework xxxx 命令来进行打包操作。
-
make-framework脚本使用方法
image.png -
由于打包脚本要求必须有workspace文件,所以如果项目中没有workspace文件需要创建一个。
Xcode -> File -> New -> Workspace 新建workspace,保存目录xcodeproj放在同一目录下 即可。
打开新建的workspace,右键添加文件到workspace,选择对应的xcodeproj即可。 -
如果一个项目中有很多个Framework(类似我们举例的MAWebKit),如果每个Framework都手动 打包,这样会很麻烦,也很容易出错,这样我们可以写一个批量打包脚本。
# 批量打包脚本示例
maia-framework -w MAWebKit -s MAWebKit -p
maia-framework -w MAWebKit -s MAWebKitCore -p
maia-framework -w MAWebKit -s MAWebKitPhoto -p
maia-framework -w MAWebKit -s MAWebKitAudio -p
maia-framework -w MAWebKit -s MAWebKitNFC -p
maia-framework -w MAWebKit -s MAWebKitCodeScan -p
maia-framework -w MAWebKit -s MAWebKitGPS -p
maia-framework -w MAWebKit -s MAWebKitFace -o
9. 创建podspec文件
有时候为了方便定位问题,需要通过源码的方式进行调试,这就需要创建源码podspec。 我们可以通过命令行来创建cocoapods的配置podspec文件。
- 打开终端Terminal,进入项目目录
- 运行命令: pod spec create 库名(MAWebKit) ,这样会生成一个MAWebKit.podspec文件。
- 配置源码podspec文件,根据自己组件的具体情况,指定默认subspec,比如:组件MAWebKit,默认依赖核心组件MAWebKitCore,那么就指定默认subspec为Core 条码扫描MACodeScan,默认不依赖任何subspec,因为它的两个subspec是互斥的
三、创建对外发布的Framework项目
- 创建脚本maia-project(最后有参考),只要运行命令,按 照步骤一步一步的操作下去,会自动创建Assets、Frameworks、库名.podspec文件,同时会进行git的 关联操作。
- Assets:放置资源文件的位置
- Frameworks:放置所有framework的位置
- 库名.podspec:cocoapods的配置文件
脚本参考
mark-framework
#!/bin/sh
function usage() {
echo "************************************************"
echo "-s scheme的名称,必填"
echo "-w workspace名称,可选项,默认从项目中检测xcworkspace文件"
echo "-f 打包最终生成的framework的名字,可选参数,如果没有默认使用scheme的内容"
echo "-o 打包成功后是否自动打开framework所在的文件夹,默认不打开"
echo "-p 是否需要运行pod install,默认否"
echo "-d 是否以Debug的配置进行打包,默认使用Release"
echo "例: maia-framework -w MAWebKit -s MAWebKit -f MAWebKit -o -p -d"
echo "************************************************"
exit 1;
}
######################
# Options
######################
CONFIGURATION=Release
WORKSPACE_NAME=
SCHEME_NAME=
FRAMEWORK_NAME=
REVEAL_ARCHIVE_IN_FINDER=false
POD_INSTALL=false
######################
# Read Params
######################
while getopts "w:s:f:opd" arg
do
case $arg in
w )
WORKSPACE_NAME=$OPTARG
;;
s )
SCHEME_NAME=$OPTARG
;;
f )
FRAMEWORK_NAME=$OPTARG
;;
o )
REVEAL_ARCHIVE_IN_FINDER=true
;;
p )
POD_INSTALL=true
;;
d )
CONFIGURATION=Debug
;;
? )
usage
;;
esac
done
if [[ ${SCHEME_NAME} == "" ]]; then
echo "scheme必填, 使用-s来指定scheme。"
usage
fi
if [[ ${FRAMEWORK_NAME} == "" ]]; then
FRAMEWORK_NAME=${SCHEME_NAME}
fi
function printParams() {
echo "****************************"
echo "workspace: ${WORKSPACE_NAME}"
echo "scheme: ${SCHEME_NAME}"
echo "FRAMEWORK: ${FRAMEWORK_NAME}"
echo "Pod install: ${POD_INSTALL}"
echo "Reveal In Finder: ${REVEAL_ARCHIVE_IN_FINDER}"
echo "****************************"
}
printParams
ma_default_workspace=""
function check_default_workspace() {
echo "\n------ xcode workspace checking ..."
workspace_files=$(ls | grep .xcworkspace$ | wc -l)
if [ ${workspace_files} == 1 ] ; then
ma_default_workspace="`ls | grep .xcworkspace$ | sed -e 's/.xcworkspace//g'`"
echo "检测到workspace【 ${ma_default_workspace} 】"
else
echo "没有找到xcode的workspace或者项目下有多个workspace文件,请自行检查。"
exit 1
fi
}
check_default_workspace
if [[ ${WORKSPACE_NAME} == "" ]]; then
WORKSPACE_NAME="${ma_default_workspace}"
fi
if [[ ${POD_INSTALL} = true ]]; then
if [[ -f "Podfile" ]]; then
echo "--- Removing Pods cache"
rm -rf Podfile.lock
rm -rf Pods
pod install
else
echo "没有找到Podfile文件"
exit 1
fi
fi
######################
# Directories
######################
FRAMEWORK_TARGET_PATH="${HOME}/Documents/MaiaFrameworks/${FRAMEWORK_NAME}"
SIMULATOR_BUILD_DIR="${FRAMEWORK_TARGET_PATH}/${CONFIGURATION}-iphonesimulator"
DEVICE_BUILD_DIR="${FRAMEWORK_TARGET_PATH}/${CONFIGURATION}-iphoneos"
SIMULATOR_LIBRARY_PATH="${SIMULATOR_BUILD_DIR}/${FRAMEWORK_NAME}.framework"
DEVICE_LIBRARY_PATH="${DEVICE_BUILD_DIR}/${FRAMEWORK_NAME}.framework"
UNIVERSAL_LIBRARY_DIR="${FRAMEWORK_TARGET_PATH}/${CONFIGURATION}-iphoneuniversal"
UNIVERSAL_LIBRARY_PATH="${UNIVERSAL_LIBRARY_DIR}/${FRAMEWORK_NAME}.framework"
function printDirs() {
echo "----------------------------"
echo "BUILD_DIR : ${FRAMEWORK_TARGET_PATH}"
echo "SIMULATOR_LIBRARY_PATH : ${SIMULATOR_LIBRARY_PATH}"
echo "DEVICE_LIBRARY_PATH : ${DEVICE_LIBRARY_PATH}"
echo "UNIVERSAL_LIBRARY_DIR : ${UNIVERSAL_LIBRARY_DIR}"
echo "FRAMEWORK : ${UNIVERSAL_LIBRARY_PATH}"
echo "----------------------------"
}
printDirs
######################
# Create directory for universal
######################
rm -rf "${FRAMEWORK_TARGET_PATH}"
mkdir -p "${SIMULATOR_BUILD_DIR}"
mkdir -p "${DEVICE_BUILD_DIR}"
mkdir -p "${UNIVERSAL_LIBRARY_DIR}"
mkdir "${UNIVERSAL_LIBRARY_PATH}"
######################
# Build Frameworks
######################
BUILD_SETTINGS="ONLY_ACTIVE_ARCH=NO CLANG_ENABLE_CODE_COVERAGE=NO MACH_O_TYPE=staticlib DEFINES_MODULE=NO"
######################
# Build Simulator Frameworks
######################
SIM_BUILD_SHELL_PREFIX="xcodebuild -workspace ${WORKSPACE_NAME}.xcworkspace -scheme ${SCHEME_NAME} -sdk iphonesimulator -configuration ${CONFIGURATION}"
SIM_BUILD_SHELL="${SIM_BUILD_SHELL_PREFIX} ${BUILD_SETTINGS} EXCLUDED_ARCHS=arm64 clean build"
echo "------ Build Simulator Framework ------"
echo "${SIM_BUILD_SHELL}"
${SIM_BUILD_SHELL} 2>&1
SIM_FRAMEWORK_TARGET_DIR=`${SIM_BUILD_SHELL_PREFIX} -showBuildSettings | grep "\<BUILT_PRODUCTS_DIR" | sed -e 's/BUILT_PRODUCTS_DIR = //g'`
echo "****** Simulator framework path: ${SIM_FRAMEWORK_TARGET_DIR}"
SIM_COPY_SHELL="cp -r ${SIM_FRAMEWORK_TARGET_DIR}/${FRAMEWORK_NAME}.framework ${SIMULATOR_BUILD_DIR}"
echo "====== SIM COPY SHELL: ${SIM_COPY_SHELL}"
${SIM_COPY_SHELL}
######################
# Build Device Frameworks
######################
DEVICE_BUILD_SHELL_PREFIX="xcodebuild -workspace ${WORKSPACE_NAME}.xcworkspace -scheme ${SCHEME_NAME} -sdk iphoneos -configuration ${CONFIGURATION}"
DEVICE_BUILD_SHELL="${DEVICE_BUILD_SHELL_PREFIX} ${BUILD_SETTINGS} clean build"
echo "------ Build Device Framework ------"
echo "${DEVICE_BUILD_SHELL}"
${DEVICE_BUILD_SHELL} 2>&1
DEVICE_FRAMEWORK_TARGET_DIR=`${DEVICE_BUILD_SHELL_PREFIX} -showBuildSettings | grep "\<BUILT_PRODUCTS_DIR" | sed -e 's/BUILT_PRODUCTS_DIR = //g'`
echo "****** Device framework path: ${DEVICE_FRAMEWORK_TARGET_DIR}"
DEVICE_COPY_SHELL="cp -r ${DEVICE_FRAMEWORK_TARGET_DIR}/${FRAMEWORK_NAME}.framework ${DEVICE_BUILD_DIR}"
echo "====== DEVICE COPY SHELL: ${DEVICE_COPY_SHELL}"
${DEVICE_COPY_SHELL}
######################
# Copy files Framework
######################
cp -r "${DEVICE_LIBRARY_PATH}/." "${UNIVERSAL_LIBRARY_PATH}"
######################
# Make an universal binary
######################
lipo "${SIMULATOR_LIBRARY_PATH}/${FRAMEWORK_NAME}" "${DEVICE_LIBRARY_PATH}/${FRAMEWORK_NAME}" -create -output "${UNIVERSAL_LIBRARY_PATH}/${FRAMEWORK_NAME}" | echo
# For Swift framework, Swiftmodule needs to be copied in the universal framework
if [ -d "${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
cp -f ${SIMULATOR_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* "${UNIVERSAL_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
if [ -d "${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" ]; then
cp -f ${DEVICE_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/* "${UNIVERSAL_LIBRARY_PATH}/Modules/${FRAMEWORK_NAME}.swiftmodule/" | echo
fi
cp -r "${UNIVERSAL_LIBRARY_PATH}" "${FRAMEWORK_TARGET_PATH}"
######################
# Move Final Bundles
######################
FRAMEWORK_FINAL_PATH="${FRAMEWORK_TARGET_PATH}/${FRAMEWORK_NAME}.framework"
FRAMEWORK_BUNDLES="${FRAMEWORK_FINAL_PATH}/*.bundle"
mv ${FRAMEWORK_BUNDLES} "${FRAMEWORK_TARGET_PATH}"
######################
# Remove Bundles
######################
SIMULATOR_LIBRARY_BUNDLES="${SIMULATOR_LIBRARY_PATH}/*.bundle"
DEVICE_LIBRARY_BUNDLES="${DEVICE_LIBRARY_PATH}/*.bundle"
UNIVERSAL_LIBRARY_BUNDLES="${UNIVERSAL_LIBRARY_PATH}/*.bundle"
echo "---------- **** Remove Bundles **** ----------"
echo "Simulator Bundles: ${SIMULATOR_LIBRARY_BUNDLES}"
echo "Device Bundles: ${DEVICE_LIBRARY_BUNDLES}"
echo "Universal Bundles: ${UNIVERSAL_LIBRARY_BUNDLES}"
rm -rf ${SIMULATOR_LIBRARY_BUNDLES}
rm -rf ${DEVICE_LIBRARY_BUNDLES}
rm -rf ${UNIVERSAL_LIBRARY_BUNDLES}
FWK_PRIVATE_HEADERS_PATH="${FRAMEWORK_FINAL_PATH}/PrivateHeaders"
rm -rf ${FWK_PRIVATE_HEADERS_PATH}
echo -e "Framework is in \033[31m ${FRAMEWORK_TARGET_PATH} \033[0m"
if [ ${REVEAL_ARCHIVE_IN_FINDER} = true ]; then
open "${FRAMEWORK_TARGET_PATH}/"
fi
make-project
#!/bin/bash
function usage() {
echo
echo "Usage:"
echo
echo -e " \033[31m $ maia-project PROJECT-NAME \033[0m"
echo
echo " maia-project, 输出标准的Maia-iOS私有库项目工程,PROJECT-NAME为必须参数"
echo
}
if [[ $# -gt 0 ]]; then
projectName="$1"
if [[ -d $projectName ]]; then
echo -e "项目 [ \033[31m ${projectName} \033[0m ] 已存在。"
else
echo -e "待创建的项目为:\033[31m ${projectName} \033[0m"
echo "创建项目 ${projectName} ..."
mkdir $projectName
cd $projectName
echo "创建Frameworks文件夹 ..."
mkdir Frameworks
echo "创建Assets文件夹 ..."
mkdir Assets
touch Assets/.gitkeep
read -p "请输入组件库的版本号,直接回车默认为1.0.0 : " app_version
if [[ $app_version == "" ]]; then
app_version="1.0.0"
fi
read -p "请输入项目简介,直接回车默认为项目简介 : " project_info
if [[ $project_info == "" ]]; then
project_info="项目简介"
fi
read -p "请输入项目描述,直接回车默认为项目描述 : " project_desc
if [[ $project_desc == "" ]]; then
project_desc="项目描述"
fi
read -p "请输入最低支持的iOS系统版本,直接回车默认为8.0 : " miniVersion
if [[ $miniVersion == "" ]]; then
miniVersion="8.0"
fi
read -p "请输入作者姓名,直接回车默认为黄于 : " author
if [[ $author == "" ]]; then
author="黄于"
fi
read -p "请输入作者邮箱,直接回车默认为huangyu20@longfor.com : " author_email
if [[ $author_email == "" ]]; then
author_email="huangyi20@longfor.com"
fi
echo ""
echo "============================================================"
echo ""
echo "版本号 : ${app_version}"
echo "应用描述 : ${project_info}"
echo "应用简介 : ${project_desc}"
echo "最低支持的iOS系统版本 : ${miniVersion}"
echo "作者 : ${author}"
echo "作者邮箱 : ${author_email}"
echo ""
echo "============================================================"
echo ""
echo "生成Podspec文件 ..."
podFile=$projectName.podspec
echo "#" > $podFile
echo "# Be sure to run \`pod lib lint ${podFile}\` to ensure this is a" >> $podFile
echo "# valid spec before submitting." >> $podFile
echo "#" >> $podFile
echo "# Any lines starting with a # are optional, but their use is encouraged" >> $podFile
echo "# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html" >> $podFile
echo "#" >> $podFile
echo "" >> $podFile
echo "Pod::Spec.new do |s|" >> $podFile
echo "" >> $podFile
echo " s.name = '${projectName}'" >> $podFile
echo " s.version = '${app_version}'" >> $podFile
echo " s.summary = '${project_info}'" >> $podFile
echo " s.description = <<-DESC" >> $podFile
echo " ${project_desc}" >> $podFile
echo " DESC" >> $podFile
echo "" >> $podFile
echo " s.homepage = 'http://git.xxxxx.net/ios/${projectName}'" >> $podFile
echo " s.license = { :type => 'Copyright', :text => 'Copyright 2018 - 2020 longfor.com. All rights reserved.\n' }" >> $podFile
echo " s.authors = { '${author}' => '${author_email}' }" >> $podFile
echo "" >> $podFile
echo " s.ios.deployment_target = '${miniVersion}'" >> $podFile
echo "" >> $podFile
echo " s.source = { :git => 'http://git.xxxxx.net/ios/${projectName}.git', :tag => s.version.to_s }" >> $podFile
echo "" >> $podFile
echo " # s.resource = 'Assets/icon.png'" >> $podFile
echo " # s.resources = 'Assets/Resources/*.png'" >> $podFile
echo "" >> $podFile
echo " s.vendored_frameworks = 'Frameworks/*.framework'" >> $podFile
echo " # s.frameworks = 'CoreLocation', 'Foundation', 'UIKit', 'SystemConfiguration', 'AdSupport', 'Security', 'CoreTelephony'" >> $podFile
echo "" >> $podFile
echo " # s.library = 'sqlite3.0'" >> $podFile
echo " # s.libraries = 'sqlite3.0','c++'" >> $podFile
echo "" >> $podFile
echo " s.requires_arc = true" >> $podFile
echo "" >> $podFile
echo " # s.xcconfig = { 'HEADER_SEARCH_PATHS' => '\$(SDKROOT)/usr/include/libxml2' }" >> $podFile
echo " # s.dependency 'JSONKit', '~> 1.4'" >> $podFile
echo "" >> $podFile
echo "end" >> $podFile
echo "绑定Git ..."
git init
# 关联远程仓库
git remote add origin http://git.xxxxx.net/ios/$projectName.git
fi
else
usage
fi
网友评论