xcode 发布预编译的 CocoaPods

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23117574/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 04:52:59  来源:igfitidea点击:

Publishing a precompiled CocoaPods

xcodecocoapods

提问by gimpycpu

We are currently building an SDK for a customer using CocoaPods.

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

The main problem we have is that our boss would like the SDK to be a black box. He wants us to precompile the code in order to protect our source.

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

Is there anything we can do within the Podspec in order to protect our code?

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

采纳答案by gimpycpu

I succeeded using that Podspec as an example :

我成功地使用该 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" => "[email protected]" }
  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

回答by Oliver Atkinson

You can do exactly that by creating a Static Frameworkand including it in the spec.vendored_frameworksproperty on your podspec.

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

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

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

Follow the tutorial below for how to create your own static framework.

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

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

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

How to Create a Static Framework for iOS

如何为 iOS 创建静态框架

There are a few constraints that we want to satisfy when building a .framework:

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

  • Fast iterative builds when developing the framework. We may have a simple application that has the .framework as a dependency and we want to quickly iterate on development of the .framework.
  • Infrequent distribution builds of the .framework.
  • Resource distribution should be intuitive and not bloat the application.
  • Setup for third-party developers using the .framework should be easy.
  • 开发框架时快速迭代构建。我们可能有一个简单的应用程序,它以 .framework 作为依赖项,我们希望快速迭代 .framework 的开发。
  • .framework 的不频繁分发版本。
  • 资源分配应该是直观的,而不是使应用程序膨胀。
  • 使用 .framework 为第三方开发人员设置应该很容易

I believe that the solution I will outline below satisfies each of these constraints. I will outline how to build a .framework project from scratch so that you can apply these steps to an existing project if you so desire. I will also include project templates for easily creating a .framework.

我相信我将在下面概述的解决方案满足这些约束中的每一个。我将概述如何从头开始构建 .framework 项目,以便您可以根据需要将这些步骤应用于现有项目。我还将包括用于轻松创建 .framework 的项目模板。

Overview

概述

View a sample project that shows the result of following these steps in the sample/Serenitydirectory.

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

Within the project we are going to have three targets: a static library, a bundle, and an aggregate.

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

The static library target will build the source into a static library (.a) and specify which headers will be "public", meaning they will be accessible from the .framework when we distribute it.

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

The bundle target will contain all of our resources and will be loadable from the framework.

bundle target 将包含我们所有的资源,并且可以从框架加载。

The aggregate target will build the static library for i386/armv6/armv7/armv7s, generate the fat framework binary, and also build the bundle. You will run this target when you plan to distribute the .framework.

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

When you are working on the framework you will likely have an internal application that links to the framework. This application will link to the static library target as you normally would and copy the .bundle in the copy resources phase. This has the benefit of only building the framework code for the platform you're actively working on, significantly improving your build times. We'll do a little bit of work in the framework project to ensure that you can use your framework in your app the same way a third party developer would (i.e. importing should work as expected). Jump to the dependent project walkthrough.

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

Create the Static Library Target

创建静态库目标

Step 1: Create a New "Cocoa Touch Static Library" Project

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

The product name will be the name of your framework. For example, Serenitywill generate Serenity.frameworkonce we've set up the project.

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

Step 2: Create the Primary Framework Header

步骤 2:创建主要框架标题

Developers expect to be able to import your framework by importing the <Serenity/Serenity.h>header. Ensure that your project has such a header (if you created a new static library then there should already be a Serenity.h and Serenity.m file; you can delete the .m).

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

Within this header you are going to import all of the public headers for your framework. For example, let's assume that we have some Widgetwith a .h and .m. Our Serenity.h file would look like this:

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

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

Once you've created your framework header file, you need to make it a "public" header. Public headers are headers that will be copied to the .framework and can be imported by those using your framework. This differs from "project" headers which will notbe distributed with the framework. This distinction is what allows you to have a concept of public and private APIs.

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

To change a file's [target membership visibility in XCode 4.4+] (Can't change target membership visibility in Xcode 4.5), you'll need to select the Static Library target you created (Serenity), open the Build Phases tab:

要更改文件的 [XCode 4.4+ 中的目标成员可见性](无法更改 Xcode 4.5 中的目标成员可见性),您需要选择您创建的静态库目标(Serenity),打开构建阶段选项卡:

Xcode 4.X:Click on Add Build Phase > Add Copy Headers.

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

Xcode 5:Add Build Phases from the menu. Click on Editor > Add Build Phase -> Add Copy Headers Build Phase. Note: If the menu options are grayed out, you'll need to click on the whitespace below the Build Phases to regain focus and retry.

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

You'll see 3 sections for Public, Private, and Project headers. To modify the scope of any header, drag and drop the header files between the sections. Alternatively you can open the Project Navigator and select the header. Next expand the Utilities pane for the File Inspector. (Cmd+Option+0).

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

Look at the "Target Membership" group and ensure that the checkbox next to the .h file is checked. Change the scope of the header from "Project" to "Public". You might have to uncheck and check the box to get the dropdown list. This will ensure that the header gets copied to the correct location in the copy headers phase.

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

Step 3: Update the Public Headers Location

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

By default the static library project will copy private and public headers to the same folder: /usr/local/include. To avoid mistakenly copying private headers to our framework we want to ensure that our public headers are copied to a separate directory, e.g. $(PROJECT_NAME)Headers. To change this setting, select the project in the Project Navigator and then click the "Build Settings" tab. Search for "public headers" and then set the "Public Headers Folder Path" to "$(PROJECT_NAME)Headers" for all configurations. If you are working with multiple Frameworks make sure that this folder is unique.

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

Ongoing Step: Adding New Sources to the Framework

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

Whenever you add new source to the framework you must decide whether to expose the .h publicly or not. To modify a header's scope you will follow the same process as Step 2. By default a header's scope will be "Project", meaning it will not be copied to the framework's public headers.

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

Step 4: Disable Code Stripping

步骤 4:禁用代码剥离

We do not want to strip any code from the library; we leave this up to the application that is linking to the framework. To disable code stripping we must modify the following configuration settings:

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

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

Step 5: Prepare the Framework for use as a Dependent Target

第 5 步:准备框架以用作相关目标

In order to use the static library as though it were a framework we're going to generate the basic skeleton of the framework in the static library target. To do this we'll include a simple post-build script. Add a post-build script by selecting your project in the Project Navigator, selecting the target, and then the "Build Phases" tab.

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

Xcode 4.X:Click Add Build Phase > Add Run Script

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

Xcode 5:Select Editor menu > Add Build Phase > Add Run Script Build Phase

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

Paste the following script in the source portion of the run script build phase. You can rename the phase by clicking the title of the phase (I've named it "Prepare Framework", for example).

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

准备框架.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"

This will generate the following folder structure:

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

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

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

Try building your project now and look at the build products directory (usually ~/Library/Developer/Xcode/DerivedData/<ProjectName>-<gibberish>/Build/Products/...). You should see a libSerenity.astatic library, a Headersfolder, and a Serenity.frameworkfolder that contains the basic skeleton of your framework.

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

Create the Framework Distribution Target

创建框架分发目标

When actively developing the framework we only care to build the platform that we're testing on. For example, if we're testing on the iPhone simulator then we only need to build the i386 platform.

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

This changes when we want to distribute the framework to third party developers. The third-party developers don't have the option of rebuilding the framework for each platform, so we must provide what is called a "fat binary" version of the static library that is comprised of the possible platforms. These platforms include: i386, armv6, armv7, and armv7s.

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

To generate this fat binary we're going to build the static library target for each platform.

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

Step 1: Create an Aggregate Target

步骤 1:创建聚合目标

Click File > New Target > iOS > Other and create a new Aggregate target. Title it something like "Framework".

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

Step 2: Add the Static Library as a Dependent Target

步骤 2:将静态库添加为从属目标

Add the static library target to the "Target Dependencies".

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

Step 3: Build the Other Platform

第 3 步:构建其他平台

To build the other platform we're going to use a "Run Script" phase to execute some basic commands. Add a new "Run Script" build phase to your aggregate target and paste the following code into it.

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

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}"
重要的提示

The above script assumes that your library name matches your project name in the following line:

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

SF_TARGET_NAME=${PROJECT_NAME}

If this is not the case (e.g. your xcode project is named SerenityFramework and the target name is Serenity) then you need to explicitly set the target name on that line. For example:

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

SF_TARGET_NAME=Serenity

Step 4: Build and Verify

第 4 步:构建和验证

You now have everything set up to build a distributable .framework to third-party developers. Try building the aggregate target. Once it's done, expand the Products folder in Xcode, right click the static library and click "Show in Finder". If this doesn't open Finder to where the static library exists then try opening ~/Library/Developer/Xcode/DerivedData/<project name>/Build/Products/Debug-iphonesimulator/.

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

Within this folder you will see your .framework folder.

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

You can now drag the .framework elsewhere, zip it up, upload it, and distribute it to your third-party developers.

您现在可以将 .framework 拖到其他地方,将其压缩、上传并分发给您的第三方开发人员。

回答by Samer Murad

Static Librariesare not supported in Swift, so for anyone coming here looking for the solution for a Swift SDK, here is a nice articleexplaining how it should be done.

Swift 不支持静态库,因此对于来这里寻找Swift SDK解决方案的任何人来说,这里有一篇很好的文章解释了它应该如何完成。

Update

更新

Swift 4 now natively supports static swift libraries.

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

Side note:

For Anyone still interested in creating a swift dynamic library, this is still a nice, very helpful article article

边注:

对于仍然对创建 swift 动态库感兴趣的任何人,这仍然是一篇很好的非常有用的文章