发布预编译的CocoaPods

gim*_*cpu 13 xcode cocoapods

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

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

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

Oli*_*son 18

您可以通过在podspec上Static Frameworkspec.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框架二进制文件,并构建bundle.当您计划分发.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>
Run Code Online (Sandbox Code Playgroud)

一旦创建了框架头文件,就需要将其设为"公共"头文件.公共标题是将被复制到.framework的标题,可以由使用您的框架的人导入.这与"项目"标题不同,后者不会随框架一起分发.这种区别使您可以拥有公共和私有API的概念.

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

Xcode 4.X: 单击Add Build Phase> Add Copy Headers.

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

您将看到公共,私有和项目标题的3个部分.要修改任何标题的范围,请在节之间拖放标题文件.或者,您可以打开Project Navigator并选择标题.接下来,展开File Inspector的Utilities窗格. (Cmd的+选项+ 0).

查看"目标成员资格"组,确保选中.h文件旁边的复选框.将标题的范围从"项目"更改为"公共".您可能必须取消选中并选中该框以获取下拉列表.这将确保标头被复制到复制标头阶段中的正确位置.

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

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

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

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

第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)
Run Code Online (Sandbox Code Playgroud)

步骤5:准备框架以用作依赖目标

为了使用静态库就好像它是一个框架,我们将在静态库目标中生成框架的基本框架.为此,我们将包含一个简单的构建后脚本.通过在Project Navigator中选择项目,选择目标,然后选择"Build Phases"选项卡,添加构建后脚本.

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

Xcode 5:选择Editor菜单> Add Build Phase> Add Run Script Build Phase

将以下脚本粘贴到运行脚本构建阶段的源部分.您可以通过单击阶段的标题重命名阶段(例如,我将其命名为"Prepare Framework").

prepare_framework.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"
Run Code Online (Sandbox Code Playgroud)

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

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

Serenity.framework/
  Headers/ -> Versions/Current/Headers
  Serenity -> Versions/Current/Serenity
  Versions/
    A/
      Headers/
        Serenity.h
        Widget.h
    Current -> A
Run Code Online (Sandbox Code Playgroud)

现在尝试构建项目并查看构建产品目录(通常 ~/Library/Developer/Xcode/DerivedData/<ProjectName>-<gibberish>/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}"
Run Code Online (Sandbox Code Playgroud) 重要的提示

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

SF_TARGET_NAME=${PROJECT_NAME}
Run Code Online (Sandbox Code Playgroud)

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

SF_TARGET_NAME=Serenity
Run Code Online (Sandbox Code Playgroud)

第4步:构建和验证

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

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

您现在可以将.framework拖到别处,压缩,上传,然后将其分发给第三方开发人员.


gim*_*cpu 5

我成功地以该 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
Run Code Online (Sandbox Code Playgroud)