iOSFramework制作与发布流程的踩坑记录(一)

前面说了

这是公司iOS Framework制作和发布过程的记录。

主要要求和情况有:

1.Swift 项目

2.不依赖其他第三方库

3.没有xib、storyboard等资源文件

4.包为框架

5.发布到 CocoaPods

工具,使用的语言版本

Xcode:10.2.1

斯威夫特:5.0.1

CocoaPods:1.7.4

一、框架创建0.新的Cocoa Touch框架1.修改配置

YES:只选择目标设备对应的指令集进行编译链接。

否:编译和链接将涵盖所有指令集。如果需要,选择执行相应的指令集。

Debug一般设置为YES,执行效率高。

Release 通常是 NO 以支持所有可能的架构。

模拟器:4s ~ 5: i386; 5s 后:x86_64。

真机:

armv6:iPhone1、2、3G; iPod 触摸1、2.

armv7 设备:iPhone 3GS、4、4S; iPad1、2、iPod Touch 3G、4.

armv7s 设备:iPhone 5、5C、iPad 4.

arm64 设备:iPhone 5S 及更新机型、iPad Air 及更新机型

指令集向后兼容armv7s >> armv7 >> armv6(iPhone5可以运行armv7架构的指令集,但可能无法充分利用特性)

2. 编写 SDK

OC SDK:注意需要提供的头文件在Build Phases-Headers的Public下添加,其中不要在.h @interface中写不想暴露的方法。

Swift SDK:为了支持OC项目的使用,可以对外调用的类、方法、属性等的可见性必须至少是public的,并且必须添加@objc来支持OC调用(classes也必须继承自 NSObject)

3.编译框架并合并框架

在xcode中分别选择模拟器和真机,构建,生成两个.framework,分别支持模拟设备的架构和真机的架构。具体位置是~/Library/Developer/Xcode/DerivedData/project name-一串字符串/Build/Products,或者在Xcode中的Products目录下右键-在finder中显示。为了方便用户调试,需要合并两个框架。

打开一个MyFramework.framework(其实是一个文件夹),可以看到它的结构:

图片[1]-iOSFramework制作与发布流程的踩坑记录(一)-老王博客

3.@ >1 手动合并(不推荐)

(3.7@>复制一个真机.framework作为合并目标.framework

(3.8@> 合并框架二进制文件。在终端输入:

lipo -创建模拟器Framework路径真机Framework路径-输出合并target.framework/MyFramework

(3.9@ > 合并MyFramework.framework/Modules/MyFramework.swiftmodule下的文件

纯OC写的框架第二步就可以完成了,Swift库也需要合并这个目录。

在第一步中,我们以框架的真机版本为基准,所以在这一步中,将模拟器版本下同一路径下的所有文件复制过来。

(4) 合并 MyFramework.framework/Headers/MyFramework-Swift.h

这是Swift5带来的变化(xcode10.3.8@>,如果不合并文件,使用真机/模拟器版本在两个环境下都无法编译同一时间。

Xcode更新日志中提到了编译相关的问题

如果您正在构建一个包含 Swift 代码的框架并使用 lipo 创建一个同时支持设备和模拟器平台的二进制文件,您还必须为每个平台组合生成的 Framework-Swift.h 头文件以创建一个同时支持两者的头文件设备和模拟器平台。 (48635615)@ >

(如果你的Framework使用了mixins(包括Swift代码),然后使用lipo工具生成一个同时支持真机和模拟器平台的二进制库。你需要拼接Header文件的内容(YourFramework- Swift.h) 由两个不同的环境共同生成的一个新的头文件同时支持这两个平台)

将两个文件的内容拼接起来,打开目标MyFramework-Swift.h(上一步真机的内容),具体修改如下:

#if TARGET_OS_SIMULATOR
   // 你编译生成模拟器环境下的 Framework 中的头文件中的内容(整篇复制进来)
   
   #else
   // 你编译生成真机环境下的 Framework 中的头文件中的内容(整篇复制进来)
   
   #endif

生成的文件内容大致如下:

#if TARGET_OS_SIMULATOR
   /************** 模拟器环境下的 Framework 中的头文件中的内容 *********/
   #if 0
   #elif defined(__x86_64__) && __x86_64__
   // Generated by Apple Swift version 5.0.1 effective-4.2 (swiftlang-1001.0.82.4 clang-1001.0.46.5)
   ...
   # pragma clang attribute pop
   
   #endif
   /******************************************************************/
   #else
   /************** 真机环境下的 Framework 中的头文件中的内容 *********/
   #elif defined(__arm64__) && __arm64__
   // Generated by Apple Swift version 5.0.1 effective-4.2 (swiftlang-1001.0.82.4 clang-1001.0.46.5)
   ...
   #endif
   #pragma clang diagnostic pop
   
   #endif
   /******************************************************************/
   #endif

3.2 使用脚本(推荐)

图片[2]-iOSFramework制作与发布流程的踩坑记录(一)-老王博客

手动步骤非常繁琐,不利于后续更新和维护,所以我们使用Xcode的脚本阶段编写合并脚本,在编译时自动完成上述工作。

在Xcode的framework项目中,点击左边的项目名,选择TARGETS-Build Phases-plus-New Run Script Phase,粘贴如下shell脚本,选择真机和模拟器各编译一次 合并自动完成,合并成功后会在finder中打开目标框架位置。

# 脚本功能:合并对应模拟器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

二、将框架发布到 CocoaPods 公共库

将开源代码或框架发布到 pod 的方式基本相同。

你可以参考这篇文章

0.注意

测试完全跑通后,正式打包发送到pod进行介绍。不能调用库方法,但是可以直接在本地导入框架。重试几次后发现:

将 pods 库的名称更改为与 .framework orz 的名称不同

例如,如果框架名为 MyFramework,则 pod 将更改为 MyFrameworkPod。

(这是我爬了很久找到的解决方法,如果你有其他合适的方法,可以指出)

1.创建 pod 库

使用pod lib create ‘MyFrameworkPod’命令创建pod共享库,根据提示输入选择你需要的配置

What platform do you want to use?? [ iOS / macOS ]
 > iOS
 
What language do you want to use?? [ Swift / ObjC ]
 > Swift
Would you like to include a demo application with your library? [ Yes / No ]
 > No
Which testing frameworks will you use? [ Specta / Kiwi / None ]
 > None
Would you like to do view based testing? [ Yes / No ]
 > No
What is your class prefix?
 > MF

将准备好的MyFramewrok.framework文件复制到MyFrameworkPod/MyFrameworkPod/,

修改 MyFrameworkPod.podspec 文件。

Pod::Spec.new do |s|
  s.name             = 'MyFrameworkPod'
  s.version          = '0.1.0'
  s.summary          = 'MyFrameworkPod'
  s.description      = <<-DESC
My Framework
                       DESC
  s.homepage         = 'https://github.com/wmadao/MyFrameworkPod'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'wmadao11' => 'wmadao11@gmail.com' }
  s.source           = { :git => "https://github.com/wmadao/MyFrameworkPod.git", :tag => "#{s.version}" }
  s.ios.deployment_target = '8.0'
  s.platform     = :ios, '8.0'
  s.requires_arc = true
  # swift版本
  s.swift_versions = "5.0"
  # 静态库framework位置
  s.vendored_frameworks = 'MyFrameworkPod/*.{framework}'
  s.source_files = 'MyFrameworkPod/Classes/**/*'
  
  # s.frameworks = 'Foundation'
  # s.resource_bundles = {
  #   'MyFrameworkPod' => ['MyFrameworkPod/Assets/*.png']
  # }
  # s.public_header_files = 'Pod/Classes/**/*.h'
  # s.dependency 'AFNetworking', '~> 2.3'
end

重点是:

2.将整个 pod lib 推送到 GitHub

创建一个标签,将其推送到您自己的 github 存储库,然后发布一个版本。注意podspec中的source路径必须和repository地址一致

git tag -a 0.1.0 -m "first release"
git push origin --tags

3. 将 MyFrameworkPod.podspec 推送到 CocoaPods

如果不是需要先注册pod trunk账号,描述部分可以省略。注册需要邮箱验证

pod trunk register [email] [username] –description=[description]

pod trunk me 可以查看我当前的信息和库

p>

验证代码和podspec文件是否错误

pod lib lint MyFrameworkPod.spec

有警告不通过,根据提示修改消除所有警告,或者添加–allow-warnings忽略警告

上传 podspec

pod trunk push MyFrameworkPod.podspec

同样,如果有警告,则不会通过,–allow -warnings 忽略警告

上传成功,pod搜索MyFrameworkPod可以查询到某个库的首页、来源、当前版本等信息

或者使用 pod trunk info MyFrameworkPod 查询某个库的所有版本和开发者

三、项目使用

准备工作:由于是Swift库,如果项目是纯oc,没有和Swift混合,是不会编译的。

解决方法:在项目中新建一个Swift文件,Xcode会提示是否需要创建Bridging Header,选择Create。

本地导入

将 .framework 拖到项目中。导入头文件使用(无需配置Embeded Binaries)

远程Pod导入安装CocoaPods,在终端输入sudo gem install cocoapods创建一个podfile,如果项目还没有使用过Pod,在终端进入项目根目录执行pod init会生成一个podfile文件pod导入找不到头文件,编辑文件,导入 SDKpod ‘MyFrameworkPod’pod导入找不到头文件,默认使用最新版本。或者使用 pod ‘MyFrameworkPod’, ‘0.1.1’ 指定执行 pod install 的版本

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'MyProject' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  
  pod 'MyFrameworkPod'
end

需要用到的地方引入头文件,

注意这里是MyFramework而不是MyFrameworkPod,这是修改pod名称的原因,否则同名导入时,总是会导入Pod而不是framework文件,导致SDK不被调用.

一些暂时没有遇到的点引用

CocoaPods公共库、远程私有库、本地私有库使用与整理

CocoaPods 发布 SDK

混合静态库(Static Framework)在XCode升级到1后无法在模拟器(或真机)上编译0.2

pod lib lint 对于仅限 Swift 的供应商框架失败

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论