iOS封装framework小结

1.封装SDK

    1. Flies -> New -> Project -> iOS -> Framework & Library -> Cocoa Touch Framework


    1. 将需要封装的类放入工程中
      我这里将MBProgress和AFN封装到了一起


    1. 修改参数
      TARGET -> Build Setting -> Linking
      Mach-O Type 修改成 Static Library,很多文章提到需要把Dead Code Stripping 设置为 No,Link With Standard Library 设置为 No,Xcode7以后已不再需要设置


    1. 设置SDK支持的最低iOS版本号
    1. 根据需求添加 armv7s
      Xcode6之后,默认不支持armv7s,如果需要添加FrameWork的工程是支持armv7s,那将会有冲突
      Build Setting -> Architectures


    1. 设置Build Active Architecture Only
      这个属性设置为yes,是为了debug的时候编译速度更快,它只编译当前的architecture版本。
      而设置为no时,会编译所有的版本。
      编译出的版本是向下兼容的,比如你设置此值为yes,用iphone4编译出来的是armv7版本的,iphone5也可以运行,但是armv6的设备就不能运行。
      所以,一般debug的时候可以选择设置为yes,release的时候要改为no,以适应不同设备。


    1. 引入头文件
      Build Phases -> Headers
      将需要暴露出来的.h文件放到Public中(这样framework的header文件夹会将这些头文件暴露出来)
      在SDKFramework.h(每个人项目名不一样,注意切换)文件中引入Public中的.h(这样引入SDKFramework就可以使用这里应用的所有类,可以不做)



    1. 生成framework
      分为手动生成和自动生成两个方法,手动生成基本思想是分别编译模拟器和真机,将生成的包合成,具体可以参考我下面的链接。
      这里主要介绍脚本自动生成
      Editor -> Add Target -> Cross Platform -> Other -> Aggregate



      添加脚本



      复制脚本
# Type a script or drag a script file from your workspace to insert its path.#!/bin/sh
#要build的target名(若一个工程有多个target,最好手动指定需要打包的目标,如TARGET_NAME="framework名")
TARGET_NAME=${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/Products/"

#创建输出目录,并删除之前的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}"

#合并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}"

切换到 CreateFramework -> Generic iOS Device



我是切换到release来打包的,debug是否可以没有尝试,编译工程,会自动生存打开Framework所在文件夹,这就是我们需要的Framework了

2.封装项目

    1. 封装一个完整的项目,资源文件需要放到bundle中去,新建一个文件夹,改名为source.bundle,讲项目的资源文件丢进去,然后项目引用资源文件的时候用 source.bundle/xxx.png的形式引用。如果用到xib的话,也需要讲xib和nib文件丢到bundle里,具体参考 iOS项目打包 。
    1. 根据上面封装SDK的步骤,讲完整项目的类都丢进将要封装的Framework里面。bundle丢不丢似乎没什么影响。SDK的Framework包也要引入,之前看到的文章 iOS项目打包 提到SDK的Framework不可以add target,但是实际操作起来,不添加的会报错。因为封装的是项目,会有AppDelegate文件,会和新项目冲突,需要在 Build Phases 中去掉,分别在 Headers 和 Compile Sources 中去掉 AppDelegate.h 和AppDelegate.m。然后根据上面封装SDK的步骤正常打包出Framework就可以了。
    1. 新项目引入封装项目的Framework,需要将被封装项目用到的SDK的Framework重新引入新项目,被封装项目的bundle也需要引入。截止到这里,就完成了。
    1. 封装项目里可能存在引入了 AppDelegate.h 或者是其他新项目的头文件,这个时候,可以考虑在被封装项目里,讲引入位置从.h改成.m,如果没办法修改,可以在新项目的 Allow Non-modular Includes In Framework Modules 改成 Yes。

3.踩坑

    1. 如果是OC工程,其中混入了Swift,打出来的包无法使用,建议将swift文件单独打包,然后导入swift包,更新OC工程,再单独对OC的部分打包。
    1. 封装swift工程时,需要对生成的XXX-Swift.h文件进行合并,封装无法同时下真机和模拟器运行,这里依旧推荐使用脚本进行。选择Framework本身的target,不是上文1-8的target,然后添加脚本,真机编译一遍,模拟器编译一遍,就会弹出合并好的Framework。
# 脚本功能:合并对应模拟器cpu架构和真机架构的不同framework
# 使用方法:在framework工程中,TARGETS-Build Phases-加号-New Run Script Phase,
#          粘贴脚本,分别选择真机和模拟器各编译一次即可,成功合并后会在finder中打开。
#          (很多教程写新建一个Aggregate Target再添加脚本,但是
#          由于默认已经有一个与项目名相同的TARGET,不能重名,如果又要保持framework名称一致
#          又需要修改,不如这样方便。)
# 用到的xcode环境变量: 参考https://www.jianshu.com/p/b5c85dcd6b04
# ${SRCROOT} 项目根目录
# ${PROJECT_NAME} 项目名
# ${BUILD_ROOT} 编译输出根目录,通常为~/Library/Developer⁩/Xcode⁩/DerivedData⁩/项目名-乱七八糟的字符串/Build/Products
# ${CONFIGURATION} release或debug
# ${ACTION} 编译时为build

# 真机编译时生成的framework位置  
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
# 模拟器编译时生成的framework位置
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
# 定义合并后framework的存放位置 这里放在项目根目录
INSTALL_DIR=${SRCROOT}/${PROJECT_NAME}.framework

# build时执行,且两类cpu架构均已编译成功生成framework
if [ "${ACTION}" = "build" ] && [ -d "${DEVICE_DIR}" ] && [ -d "${SIMULATOR_DIR}" ]
then

# 删除原有的合并文件(.framework其实是个文件夹)
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

# 新建合并文件
mkdir -p "${INSTALL_DIR}"

# 将真机framework拷贝至合并文件(因为后面的lipo -create只合并输出.framework下的"项目名"二进制文件,
# 还需要剩余的其他文件才能被使用,本脚本以真机framework的为基准,
# 这一步合并了Modules/xxx.swiftmodule文件夹,以及下面提到的Headers/xxx-Swift.h
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"

# 利用lipo合并两个.framework里的二进制文件,结果保存在合并后目录
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"

# ***如果是swift工程,还需要拷贝.swiftmodule下的文件
SIMULATOR_SWIFT_MODULES_DIR=${SIMULATOR_DIR}/Modules/${PROJECT_NAME}.swiftmodule/.
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]
then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${INSTALL_DIR}/Modules/${PROJECT_NAME}.swiftmodule"
fi

# *** xcode10.2以后,如果包含Swift文件,
# 还需要合并处理xx.framework/Headers/PROJECT_NAME-Swift.h里的内容
SIMULATOR_SWIFT_HEADER_FILE=${SIMULATOR_DIR}/Headers/${PROJECT_NAME}-Swift.h
DEVICE_SWIFT_HEADER_FILE=${DEVICE_DIR}/Headers/${PROJECT_NAME}-Swift.h
INSTALL_SWIFT_HEADER_FILE=${INSTALL_DIR}/Headers/${PROJECT_NAME}-Swift.h

if [ -e "${SIMULATOR_SWIFT_HEADER_FILE}" ] && [ -e "${DEVICE_SWIFT_HEADER_FILE}" ]
then
# 合并-Swift.h
# 写入.h文件
echo "#if TARGET_OS_SIMULATOR" > "${INSTALL_SWIFT_HEADER_FILE}"
# 模拟器
cat "${SIMULATOR_SWIFT_HEADER_FILE}" >> "${INSTALL_SWIFT_HEADER_FILE}"
echo "#else" >> ${INSTALL_SWIFT_HEADER_FILE}
# 真机
cat "${DEVICE_SWIFT_HEADER_FILE}" >> "${INSTALL_SWIFT_HEADER_FILE}"
echo "#endif" >> "${INSTALL_SWIFT_HEADER_FILE}"
fi
# 合并-Swift.h结束

# 打开项目目录,得到合并后的.framework
open "${SRCROOT}"
fi

    1. 当打包包含pods的工程时,在打包的工程里也引入pods,编译成功后,将依赖里由pods提供的.a依赖去掉,再进行打包。当pod库更新的时候,.a文件会重新产生,需要再次删除。

参考文章:
iOS项目打包
iOSFramework封装
iOS新版本Framework封装细节
iOS脚本封装Framework

你可能感兴趣的:(iOS封装framework小结)