【问题标题】:Publishing a precompiled CocoaPods发布预编译的 CocoaPods
【发布时间】:2014-04-16 18:38:48
【问题描述】:

我们目前正在为使用 CocoaPods 的客户构建 SDK。

我们遇到的主要问题是我们的老板希望 SDK 成为一个黑匣子。他希望我们预编译代码以保护我们的源代码。

我们可以在 Podspec 中做些什么来保护我们的代码吗?

【问题讨论】:

  • 我写了一篇关于如何发布预编译的通用 CocoaPod 的详尽指南:eladnava.com/…
  • @EladNava 您能否添加一篇文章如何通过 cocoapods 分发带有 .bundle 资源的封闭框架?
  • @MihailSalari 我建议检查如何将“.bundle”资源与您的 pod 捆绑在一起,可能会在 Podspec 中提及它们。不过,我自己对此没有任何经验,所以无能为力。
  • @EladNava 好的,谢谢。

标签: xcode cocoapods


【解决方案1】:

您可以通过创建 Static Framework 并将其包含在您的 podspec 的 spec.vendored_frameworks 属性中来完全做到这一点。

http://guides.cocoapods.org/syntax/podspec.html#vendored_frameworks

按照以下教程了解如何创建自己的静态框架。

https://github.com/jverkoey/iOS-Framework#walkthrough

如何为 iOS 创建静态框架

在构建 .framework 时,我们希望满足一些约束:

  • 开发框架时快速迭代构建。我们可能有一个简单的应用程序,它具有 .framework 作为依赖项,我们希望快速迭代 .framework 的开发。
  • .framework 的不经常分发版本。
  • 资源分配应该是直观的,而不是让应用程序膨胀。
  • 使用 .framework 的第三方开发人员的设置应该简单

我相信我将在下面概述的解决方案可以满足这些限制。我将概述 如何从头开始构建 .framework 项目,以便您可以将这些步骤应用于现有的 如果您愿意,可以进行项目。我还将包含项目模板,以便轻松创建 .framework.

概述

查看一个示例项目,该项目显示在sample/Serenity 中执行这些步骤的结果 目录。

在项目中,我们将拥有三个目标:静态库、捆绑包和聚合。

静态库目标将源构建成静态库 (.a) 并指定哪些标头 将是“公开的”,这意味着当我们分发它时,它们将可以从 .framework 访问。

捆绑目标将包含我们所有的资源,并且可以从框架中加载。

聚合目标将为i386/armv6/armv7/armv7s构建静态库,生成fat框架 二进制文件,并构建捆绑包。当您计划分发 .framework.

当您在开发框架时,您可能会有一个内部应用程序链接到 框架。此应用程序将像往常一样链接到静态库目标并复制 复制资源阶段的 .bundle。这样做的好处是只构建框架代码 对于您正在积极开发的平台,显着缩短您的构建时间。我们会做一个 框架项目中的一些工作,以确保您可以在您的应用程序中使用您的框架 与第三方开发人员相同的方式(即导入应该可以工作 正如预期的那样)。跳转到依赖项目演练。

创建静态库目标

第 1 步:新建“Cocoa Touch 静态库”项目

产品名称将是您的框架的名称。例如,Serenity 将生成 Serenity.framework 一旦我们设置了项目。

第 2 步:创建主框架标头

开发人员希望能够通过导入 <Serenity/Serenity.h> 来导入您的框架 标题。确保您的项目有这样的标题(如果您创建了一个新的静态库,那么那里 应该已经是 Serenity.h 和 Serenity.m 文件;您可以删除 .m)。

在此标头中,您将导入框架的所有公共标头。为了 例如,假设我们有一些 Widget 带有 .h 和 .m。我们的 Serenity.h 文件看起来 像这样:

#import <Foundation/Foundation.h>
#import <Serenity/Widget.h>

创建框架头文件后,您需要将其设为“公共”头文件。上市 headers 是将复制到 .framework 的标头,并且可以由使用您的 框架。这与将与框架一起分发的“项目”标头不同。 这种区别使您可以拥有公共和私有 API 的概念。

更改文件的 [XCode 4.4+ 中的目标成员可见性] (Can't change target membership visibility in Xcode 4.5), 您需要选择您创建的静态库目标 (Serenity),打开 Build Phases 选项卡:

Xcode 4.X: 单击“添加构建阶段”>“添加复制标头”。

Xcode 5: 从菜单中添加构建阶段。单击编辑器 > 添加构建阶段 -> 添加复制标题构建阶段。注意:如果菜单选项显示为灰色,您需要单击 Build Phases 下方的空白处以重新获得焦点并重试。

您将看到公共、私有和项目标头的 3 个部分。要修改任何标题的范围,请在部分之间拖放标题文件。或者,您可以打开项目导航器并选择标题。接下来展开文件检查器的实用程序窗格。 (Cmd+Option+0)。

查看“Target Membership”组并确保选中 .h 文件旁边的复选框。 将标题的范围从“项目”更改为“公共”。您可能必须取消选中并选中该框以获取下拉列表。这将确保标题得到 在复制标头阶段复制到正确的位置。

第 3 步:更新公共标头位置

默认情况下,静态库项目会将私有和公共头文件复制到同一文件夹: /usr/local/include。为了避免错误地将私有标头复制到我们的框架中,我们要确保 我们的公共标头被复制到一个单独的目录,例如$(PROJECT_NAME)Headers。要更改此设置, 在 Project Navigator 中选择项目,然后单击“Build Settings”选项卡。搜索“公共 headers”,然后将所有配置的“Public Headers 文件夹路径”设置为“$(PROJECT_NAME)Headers”。 如果您使用多个框架,请确保此文件夹是唯一的。

正在进行的步骤:向框架添加新源

每当您向框架添加新源时,您必须决定是公开公开 .h 还是 不是。要修改标头的范围,您将遵循与步骤 2 相同的过程。默认情况下,标头的 范围将是“项目”,这意味着它不会被复制到框架的公共标头中。

第 4 步:禁用代码剥离

我们不想从库中剥离任何代码;我们把它留给应用程序 链接到框架。要禁用代码剥离,我们必须修改以下配置 设置:

"Dead Code Stripping" => No (for all settings)
"Strip Debug Symbols During Copy" => No (for all settings)
"Strip Style" => Non-Global Symbols (for all settings)

第 5 步:准备框架以用作从属目标

为了像使用框架一样使用静态库,我们将生成基本的 静态库目标中的框架骨架。为此,我们将包含一个简单的后期构建 脚本。通过在 Project Navigator 中选择您的项目,选择目标,然后添加构建后脚本 “构建阶段”选项卡。

Xcode 4.X:点击添加构建阶段>添加运行脚本

Xcode 5:选择编辑器菜单>添加构建阶段>添加运行脚本构建阶段

将以下脚本粘贴到运行脚本构建阶段的源代码部分。您可以通过单击重命名阶段 阶段的标题(例如,我将其命名为“准备框架”)。

准备框架.sh
set -e

mkdir -p "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"

# Link the "Current" version to "A"
/bin/ln -sfh A "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/Current"
/bin/ln -sfh Versions/Current/Headers "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Headers"
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}"

# The -a ensures that the headers maintain the source modification date so that we don't constantly
# cause propagating rebuilds of files that import these headers.
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"

这将生成以下文件夹结构:

-- Note: "->" denotes a symbolic link --

Serenity.framework/
  Headers/ -> Versions/Current/Headers
  Serenity -> Versions/Current/Serenity
  Versions/
    A/
      Headers/
        Serenity.h
        Widget.h
    Current -> A

现在尝试构建您的项目并查看构建产品目录(通常 ~/Library/Developer/Xcode/DerivedData/&lt;ProjectName&gt;-&lt;gibberish&gt;/Build/Products/...)。你应该 查看 libSerenity.a 静态库、Headers 文件夹和 Serenity.framework 文件夹 包含框架的基本骨架。

创建框架分发目标

在积极开发框架时,我们只关心构建我们正在测试的平台。为了 例如,如果我们在 iPhone 模拟器上进行测试,那么我们只需要构建 i386 平台即可。

当我们想要将框架分发给第三方开发人员时,这种情况会发生变化。第三方 开发者没有为每个平台重新构建框架的选项,所以我们必须提供 所谓的静态库的“胖二进制”版本,由可能的 平台。这些平台包括:i386、armv6、armv7 和 armv7s。

为了生成这个胖二进制文件,我们将为每个平台构建静态库目标。

第 1 步:创建聚合目标

单击文件 > 新目标 > iOS > 其他并创建一个新的聚合目标。将其命名为“框架”。

第 2 步:将静态库添加为依赖目标

将静态库目标添加到“目标依赖项”。

第 3 步:构建其他平台

为了构建另一个平台,我们将使用“运行脚本”阶段来执行一些基本命令。 将新的“运行脚本”构建阶段添加到您的聚合目标并将以下代码粘贴到其中。

build_framework.sh
set -e
set +u
# Avoid recursively calling this script.
if [[ $SF_MASTER_SCRIPT_RUNNING ]]
then
    exit 0
fi
set -u
export SF_MASTER_SCRIPT_RUNNING=1

SF_TARGET_NAME=${PROJECT_NAME}
SF_EXECUTABLE_PATH="lib${SF_TARGET_NAME}.a"
SF_WRAPPER_NAME="${SF_TARGET_NAME}.framework"

# The following conditionals come from
# https://github.com/kstenerud/iOS-Universal-Framework

if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
then
    SF_SDK_PLATFORM=${BASH_REMATCH[1]}
else
    echo "Could not find platform name from SDK_NAME: $SDK_NAME"
    exit 1
fi

if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]
then
    SF_SDK_VERSION=${BASH_REMATCH[1]}
else
    echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
    exit 1
fi

if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
then
    SF_OTHER_PLATFORM=iphonesimulator
else
    SF_OTHER_PLATFORM=iphoneos
fi

if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$SF_SDK_PLATFORM$ ]]
then
    SF_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}"
else
    echo "Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR"
    exit 1
fi

# Build the other platform.
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk ${SF_OTHER_PLATFORM}${SF_SDK_VERSION} BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}" $ACTION

# Smash the two static libraries into one fat binary and store it in the .framework
xcrun lipo -create "${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" -output "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"

# Copy the binary to the other architecture folder to have a complete framework in both.
cp -a "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"
重要的提示

以上脚本假定您的库名称与以下行中的项目名称匹配:

SF_TARGET_NAME=${PROJECT_NAME}

如果不是这种情况(例如,您的 xcode 项目名为 SerenityFramework,目标名称为 Serenity)那么您需要在该行上明确设置目标名称。例如:

SF_TARGET_NAME=Serenity

第 4 步:构建和验证

您现在已经完成了为第三方开发人员构建可分发的 .framework 的所有设置。尝试 构建聚合目标。完成后,在 Xcode 中展开 Products 文件夹,右键单击 静态库,然后单击“在 Finder 中显示”。如果这没有打开 Finder 到静态库的位置 存在然后尝试打开 ~/Library/Developer/Xcode/DerivedData/&lt;project name&gt;/Build/Products/Debug-iphonesimulator/.

在此文件夹中,您将看到您的 .framework 文件夹。

您现在可以将 .framework 拖到其他地方,将其压缩、上传,然后分发到您的 第三方开发者。

【讨论】:

  • 我一开始确实尝试过这个解决方案,但我从来没有成功在我们的库中使用 Cocoapods。无法在静态上运行 pod install。我们还有一个 xcdatamodel 包含在 SDK 中,所以如果不使用嵌入式框架,就没有真正的方法
  • 我发现这里的 Pod 做了类似的事情,我会调查一下。 github.com/Estimote/iOS-SDK/blob/master/EstimoteSDK.podspec
  • 如果您使用上述方法,您可以将您的静态库编译成 .framework(看起来更专业)按照教程并使用 podspec 上的 vendored_frameworks 属性。见https://github.com/CocoaPods/Specs/blob/master/Appsee/2.0.5/Appsee.podspec
  • 嗨 Oliver,很遗憾,就像我在第一条评论中所说的那样,SDK 依赖于 CoreData,我们有一个 xcdatamodel,它不能包含在标准的 .framework 中。 (除非我弄错了)
  • 您不能再在 CocoaPods 中使用静态框架。
【解决方案2】:

我以该 Podspec 为例成功了:

Pod::Spec.new do |s|
  s.name         = "EstimoteSDK"
  s.version      = "1.3.0"
  s.summary      = "iOS library for Estimote iBeacon devices"
  s.homepage     = "http://estimote.com"
  s.author       = { "Estimote, Inc" => "contact@estimote.com" }
  s.platform     = :ios 
  s.source       = { :git => "https://github.com/Estimote/iOS-SDK.git", :tag => "  {s.version}" }
  s.source_files =  'EstimoteSDK/Headers/*.h'
  s.preserve_paths = 'EstimoteSDK/libEstimoteSDK.a'
  s.vendored_libraries = 'EstimoteSDK/libEstimoteSDK.a'
  s.ios.deployment_target = '7.0'
  s.frameworks = 'UIKit', 'Foundation', 'SystemConfiguration', 'MobileCoreServices', 'CoreLocation'
  s.requires_arc = true
  s.xcconfig  =  { 'LIBRARY_SEARCH_PATHS' => '"$(PODS_ROOT)/EstimoteSDK"',
               'HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/Headers/EstimoteSDK"' }
  s.license      = {
    :type => 'Copyright',
    :text => <<-LICENSE
      Copyright 2013 Estimote, Inc. All rights reserved.
      LICENSE
  }
end

【讨论】:

    【解决方案3】:

    Static Libraries 在 Swift 中不受支持,因此对于任何来这里寻找 Swift SDK 解决方案的人来说,这里有一个很好的 article 解释应该怎么做。

    更新

    Swift 4 现在原生支持静态 swift 库。

    边注:

    对于仍然对创建 swift 动态库感兴趣的任何人,这仍然是一篇很好、很有帮助的文章article

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-04-09
      • 1970-01-01
      • 2016-11-25
      • 2015-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-27
      相关资源
      最近更新 更多