关于ios:在Swift框架中导入CommonCrypto

Importing CommonCrypto in a Swift framework

如何在iOS的swift框架中导入CommonCrypto

我了解如何在Swift应用程序中使用CommonCrypto:将#import 添加到桥接头。但是,swift框架不支持桥接头文件。文件上说:

You can import external frameworks that have a pure Objective-C codebase, a pure Swift codebase, or a mixed-language codebase. The
process for importing an external framework is the same whether the
framework is written in a single language or contains files from both
languages. When you import an external framework, make sure the
Defines Module build setting for the framework you’re importing is set
to Yes.

You can import a framework into any Swift file within a different
target using the following syntax:

1
import FrameworkName

不幸的是,导入CommonCrypto不起作用。也没有将#import 添加到伞头。


更简单、更健壮的方法是创建一个名为"commonCryptoModuleMap"的聚合目标,在运行脚本阶段自动生成模块映射,并使用正确的xcode/sdk路径:

enter image description hereenter image description here

运行脚本阶段应包含此bash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This if-statement means we'll only run the main script if the CommonCryptoModuleMap directory doesn't exist
# Because otherwise the rest of the script causes a full recompile for anything where CommonCrypto is a dependency
# Do a"Clean Build Folder" to remove this directory and trigger the rest of the script to run
if [ -d"${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap" ]; then
    echo"${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap directory already exists, so skipping the rest of the script."
    exit 0
fi

mkdir -p"${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap"
cat <<EOF >"${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap/module.modulemap"
module CommonCrypto [system] {
    header"${SDKROOT}/usr/include/CommonCrypto/CommonCrypto.h"
    export *
}
EOF

使用shell代码和${SDKROOT}意味着您不必对Xcode.app路径进行硬编码,因为Xcode.app路径会随系统的不同而变化,特别是当您使用xcode-select切换到beta版本,或者在安装了多个版本的非标准位置的CI服务器上构建时。您也不需要对sdk进行硬编码,这样就可以在iOS、MacOS等环境中使用。您也不需要在项目的源目录中包含任何内容。

创建此目标后,使用目标依赖项使库/框架依赖于它:

enter image description here

这将确保在构建框架之前生成模块映射。

MacOS注:如果您也支持macOS,那么您需要将macosx添加到您刚刚创建的新聚合目标的Supported Platforms构建设置中,否则它不会将模块映射与其他框架产品放在正确的Debug派生的数据文件夹中。

enter image description here

接下来,将模块映射的父目录${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap添加到swift部分(SWIFT_INCLUDE_PATHS下的"导入路径"构建设置中:

enter image description here

如果在项目或xcconfig级别定义了搜索路径,请记住添加一个$(inherited)行。

就是这样,你现在应该能够

更新xcode 10

Xcode10现在附带了一个公共加密模块映射,使得这种解决方案变得不必要。如果您希望同时支持Xcode9和10,那么可以在运行脚本阶段进行检查,以查看模块映射是否存在,例如。

1
2
3
4
5
6
7
COMMON_CRYPTO_DIR="${SDKROOT}/usr/include/CommonCrypto"
if [ -f"${COMMON_CRYPTO_DIR}/module.modulemap" ]
then
   echo"CommonCrypto already exists, skipping"
else
    # generate the module map, using the original code above
fi


实际上,您可以构建一个"只起作用"的解决方案(不需要像这里的其他解决方案所要求的那样,将module.modulemap和EDOCX1[3]设置复制到项目中),但它确实要求您创建一个虚拟的框架/模块,您将正确地导入到您的框架中。我们还可以确保它在不考虑平台的情况下工作(iphoneosiphonesimulatormacosx)。

  • 向项目中添加一个新的框架目标,并将其命名为系统库之后的名称,例如"CommonCrypto"。(您可以删除伞头commoncrypto.h。)

  • 添加新的配置设置文件并命名,例如"commoncrypto.xcconfig"。(不要检查任何目标是否包含在内。)用以下内容填充它:

    1
    2
    3
    4
    5
    6
    MODULEMAP_FILE[sdk=iphoneos*]        = \
        $(SRCROOT)/CommonCrypto/iphoneos.modulemap
    MODULEMAP_FILE[sdk=iphonesimulator*] = \
        $(SRCROOT)/CommonCrypto/iphonesimulator.modulemap
    MODULEMAP_FILE[sdk=macosx*]          = \
        $(SRCROOT)/CommonCrypto/macosx.modulemap
  • 创建上面三个引用的模块映射文件,并用以下内容填充它们:

    • iphoneos.modulemap(iphoneos.modulemap)

      1
      2
      3
      4
      module CommonCrypto [system] {
          header"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCrypto.h"
          export *
      }
    • iphoneSimulator.modulemap(iphoneSimulator.modulemap)

      1
      2
      3
      4
      module CommonCrypto [system] {
          header"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
          export *
      }
    • macosx.modulemap模块

      1
      2
      3
      4
      module CommonCrypto [system] {
          header"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/CommonCrypto/CommonCrypto.h"
          export *
      }

    (如果您运行的是测试版,请将"xcode.app"替换为"xcode beta.app"。如果不运行el capitan,则用当前的OS SDK替换10.11

  • 在项目设置的"信息"选项卡上,在"配置"下,将CommonCrypto的调试和发布配置设置为CommonCrypto(引用CommonCrypto.xcconfig)。

  • 在框架目标的构建阶段选项卡上,将CommonCryptoFramework添加到目标依赖项。另外,将libcommoncrypto.dylib添加到带有库构建阶段的链接二进制文件中。

  • 在产品中选择commoncrypto.framework,并确保其包装的目标成员资格设置为可选。

  • 您现在应该能够在包装框架中构建、运行和import CommonCrypto

    例如,请参见sqlite.swift如何使用虚拟sqlite3.framework。


    我发现了一个Github项目,它在swift框架中成功地使用了CommonCrypto:sha256 swift。此外,本文关于sqlite3的相同问题也很有用。

    基于以上,步骤如下:

    1)在项目目录中创建一个CommonCrypto目录。在中,创建一个module.map文件。模块映射将允许我们使用CommonCryptoLibrary作为swift中的模块。其内容包括:

    1
    2
    3
    4
    5
    module CommonCrypto [system] {
        header"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/usr/include/CommonCrypto/CommonCrypto.h"
        link"CommonCrypto"
        export *
    }

    2)在构建设置中,在swift编译器-搜索路径中,将CommonCrypto目录添加到导入路径(SWIFT_INCLUDE_PATHS目录)。

    Build Settings

    3)最后,将CommonCrypto和其他模块一样导入到swift文件中。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import CommonCrypto

    extension String {

        func hnk_MD5String() -> String {
            if let data = self.dataUsingEncoding(NSUTF8StringEncoding)
            {
                let result = NSMutableData(length: Int(CC_MD5_DIGEST_LENGTH))
                let resultBytes = UnsafeMutablePointer<CUnsignedChar>(result.mutableBytes)
                CC_MD5(data.bytes, CC_LONG(data.length), resultBytes)
                let resultEnumerator = UnsafeBufferPointer<CUnsignedChar>(start: resultBytes, length: result.length)
                let MD5 = NSMutableString()
                for c in resultEnumerator {
                    MD5.appendFormat("%02x", c)
                }
                return MD5
            }
            return""
        }
    }

    局限性

    在另一个项目中使用自定义框架在编译时失败,错误为missing required module 'CommonCrypto'。这是因为CommonCrypto模块似乎不包含在自定义框架中。解决方法是在使用框架的项目中重复步骤2(设置Import Paths)。

    模块映射不是平台独立的(它当前指向特定的平台,即iOS 8模拟器)。我不知道如何使标题路径相对于当前平台。

    iOS 8的更新<=我们应该删除行链接"commoncrypto",以获得成功的编译。

    更新/编辑

    我一直收到以下生成错误:

    ld: library not found for -lCommonCrypto for architecture x86_64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

    除非我从我创建的module.map文件中删除了行link"CommonCrypto"。一旦我拆下这条线,它就没问题了。


    这个答案讨论了如何使它在一个框架内工作,以及如何与可可和迦太基一起工作。

    ??模映射方法

    我用modulemap来包装CommonCrypto,https://github.com/onmyway133/arcane,https://github.com/onmyway133/reideer

    对于获得header not found的用户,请查看https://github.com/onmyway133/arcane/issues/4或运行xcode-select --install

    • 制作包含module.modulemapCCommonCrypto文件夹

      1
      2
      3
      4
        module CCommonCrypto {
          header"/usr/include/CommonCrypto/CommonCrypto.h"
          export *
        }
    • 转到"构建设置"->"导入路径"

      1
        ${SRCROOT}/Sources/CCommonCrypto

    ??采用模映射方法的椰子

    • 以下是podspec https://github.com/onmyway133/arcane/blob/master/arcane.podspec

      1
      2
      3
      4
        s.source_files = 'Sources/**/*.swift'
        s.xcconfig = { 'SWIFT_INCLUDE_PATHS' =>
        '$(PODS_ROOT)/CommonCryptoSwift/Sources/CCommonCrypto' }
        s.preserve_paths = 'Sources/CCommonCrypto/module.modulemap'
    • 使用module_map不起作用,见https://github.com/cocoapods/cocoapods/issues/5271

    • path上使用本地开发包不起作用,请参阅https://github.com/cocoapods/cocoapods/issues/809

    • 这就是为什么您看到我的示例podfile https://github.com/onmyway133/commoncrypto.swift/blob/master/example/commoncryptoswiftdemo/podfile指向git repo的原因。

      1
      2
      3
        target 'CommonCryptoSwiftDemo' do
          pod 'CommonCryptoSwift', :git => 'https://github.com/onmyway133/CommonCrypto.swift'
        end

    ??公共标题方法

    • ji是libxml2的包装器,它使用公共头方法

    • 它有一个头文件https://github.com/honghooz/ji/blob/master/source/ji.h,其中Target Membership设置为Public

    • 它有libxml2的头文件列表https://github.com/honghoaz/ji/tree/master/source/ji-libxml

    • 它有构建设置->头搜索路径

      1
        $(SDKROOT)/usr/include/libxml2
    • 它有构建设置->其他链接器标志

      1
        -lxml2

    ??采用公共标题方法的椰子

    • 看看podspec https://github.com/honghoaz/ji/blob/master/ji.podspec

      1
      2
        s.libraries        ="xml2"
        s.xcconfig         = { 'HEADER_SEARCH_PATHS' =>           '$(SDKROOT)/usr/include/libxml2', 'OTHER_LDFLAGS' => '-lxml2' }

    ??有趣的相关帖子

    • 如何从SWIFT呼叫C?
    • https://spin.atomicobject.com/2015/02/23/c-libraries-swift/


    好消息!Swift 4.2(Xcode 10)最终提供了通用加密!

    只需在您的swift文件中添加import CommonCrypto


    警告:iTunesConnect可能会拒绝使用此方法的应用程序。

    我的团队中的新成员意外地破坏了一个顶级答案给出的解决方案,所以我决定将其整合到一个名为CommonCryptoModule的小型包装项目中。您可以手动或通过cocoapods安装:

    1
    pod 'CommonCryptoModule', '~> 1.0.2'

    然后,您所要做的就是在需要CommonCrypto的地方导入模块,如下所示:

    1
    import CommonCryptoModule

    希望其他人能发现这一点。


    我想我对迈克·韦勒出色的工作有了改进。

    在包含此bash的Compile Sources阶段之前添加一个运行脚本阶段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # This if-statement means we'll only run the main script if the
    # CommonCrypto.framework directory doesn't exist because otherwise
    # the rest of the script causes a full recompile for anything
    # where CommonCrypto is a dependency
    # Do a"Clean Build Folder" to remove this directory and trigger
    # the rest of the script to run

    FRAMEWORK_DIR="${BUILT_PRODUCTS_DIR}/CommonCrypto.framework"

    if [ -d"${FRAMEWORK_DIR}" ]; then
    echo"${FRAMEWORK_DIR} already exists, so skipping the rest of the script."
    exit 0
    fi

    mkdir -p"${FRAMEWORK_DIR}/Modules"
    cat <<EOF >"${FRAMEWORK_DIR}/Modules/module.modulemap"
    module CommonCrypto [system] {
        header"${SDKROOT}/usr/include/CommonCrypto/CommonCrypto.h"
        export *
    }
    EOF

    ln -sf"${SDKROOT}/usr/include/CommonCrypto""${FRAMEWORK_DIR}/Headers"

    这个脚本构建了一个简单的框架,module.map位于正确的位置,然后依赖xcode对BUILT_PRODUCTS_DIR的自动搜索来查找框架。

    我将原来的CommonCryptoInclude文件夹链接为框架的headers文件夹,因此结果也应该适用于目标C项目。


    对于使用Swift 4.2和Xcode 10的任何人:

    CommonCrypto模块现在由系统提供,因此您可以像其他任何系统框架一样直接导入它。

    1
    import CommonCrypto


    @Mogstad已经很好地将@stephencelis解决方案包装在可可中:

    pod"libcommoncrypto"

    其他可用的豆荚对我不起作用。


    modulemap解决方案可能很好,并且对sdk的更改很健壮,但是我发现它们在实践中难以使用,而且在将事情分发给他人时不如我希望的那样可靠。为了让它更简单,我采取了不同的方式:

    只需复制标题。

    我知道,脆弱。但苹果几乎从未对CommonCrypto做过重大的改动,我一直梦想着,如果不将CommonCrypto最终变成一个模块化的报头,它们将不会以任何重要的方式改变它。

    "复制标题"的意思是"像预处理器那样,将所有需要的标题剪切粘贴到项目中的一个大标题中。"作为可以复制或改编的示例,请参阅rncryptor.h。

    请注意,所有这些文件都是根据APSL2.0获得许可的,并且此方法有意维护版权和许可声明。我的连接步骤是在MIT下获得许可的,这只适用于下一个许可通知)。

    我并不是说这是一个很好的解决方案,但到目前为止,它似乎是一个对实现和支持都非常简单的解决方案。


    我在jjrscott的答案中添加了一些cocoapods魔法,以防您需要在cocoapods库中使用commoncrypto。

    1)将这一行添加到您的podspec中:

    1
    s.script_phase = { :name => 'CommonCrypto', :script => 'sh $PROJECT_DIR/../../install_common_crypto.sh', :execution_position => :before_compile }

    2)将它保存在您的库文件夹中或您喜欢的任何位置(但是不要忘记相应地更改脚本阶段…)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # This if-statement means we'll only run the main script if the
    # CommonCrypto.framework directory doesn't exist because otherwise
    # the rest of the script causes a full recompile for anything
    # where CommonCrypto is a dependency
    # Do a"Clean Build Folder" to remove this directory and trigger
    # the rest of the script to run
    FRAMEWORK_DIR="${BUILT_PRODUCTS_DIR}/CommonCrypto.framework"

    if [ -d"${FRAMEWORK_DIR}" ]; then
    echo"${FRAMEWORK_DIR} already exists, so skipping the rest of the script."
    exit 0
    fi

    mkdir -p"${FRAMEWORK_DIR}/Modules"
    echo"module CommonCrypto [system] {
        header"${SDKROOT}/usr/include/CommonCrypto/CommonCrypto.h"
        export *
    }">>"${FRAMEWORK_DIR}/Modules/module.modulemap"

    ln -sf"${SDKROOT}/usr/include/CommonCrypto""${FRAMEWORK_DIR}/Headers"

    很有魅力:)


    我知道这是个老问题。但我找到了一种在Swift项目中使用库的替代方法,这可能对那些不想导入这些答案中引入的框架的人有所帮助。

    在swift项目中,创建一个objective-c桥接头,在objective-c中创建nsdata类别(或使用库的自定义类)。唯一的缺点是必须在objective-c中编写所有实现代码。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #import"NSData+NSDataEncryptionExtension.h"
    #import <CommonCrypto/CommonCryptor.h>

    @implementation NSData (NSDataEncryptionExtension)
    - (NSData *)AES256EncryptWithKey:(NSString *)key {
        //do something
    }

    - (NSData *)AES256DecryptWithKey:(NSString *)key {
    //do something
    }

    然后在您的Objective-C桥接头中,添加这个

    1
    #import"NSData+NSDataEncryptionExtension.h"

    然后在swift类中做类似的事情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public extension String {
    func encryp(withKey key:String) -> String? {
        if let data = self.data(using: .utf8), let encrypedData = NSData(data: data).aes256Encrypt(withKey: key) {
            return encrypedData.base64EncodedString()
        }
        return nil
    }
    func decryp(withKey key:String) -> String? {
        if let data = NSData(base64Encoded: self, options: []), let decrypedData = data.aes256Decrypt(withKey: key) {
            return decrypedData.UTF8String
        }
        return nil
    }
    }

    它按预期工作。


    如果您有以下问题:

    ld:找不到用于-lapple_加密的库clang:错误:链接器命令失败,退出代码为1(使用-v查看调用)

    在Xcode 10中,Swift 4.0。CommonCrypto是框架的一部分。

    添加

    • 导入公共加密

    去除

    • 来自链接二进制文件的commoncrpto lib文件和来自内部版本的库阶段
    • 从桥接头导入CommonCrypto

    这对我有用!


    我不确定Xcode9.2是否发生了变化,但现在实现这一点要简单得多。我只需要在我的框架项目目录中创建一个名为"commoncrypto"的文件夹,并在其中创建两个文件,一个名为"cc.h",如下所示:

    1
    2
    #include <CommonCrypto/CommonCrypto.h>
    #include <CommonCrypto/CommonRandom.h>

    另一个叫做module.modulemap:

    1
    2
    3
    4
    module CommonCrypto {
        export *
        header"cc.h"
    }

    (我不知道为什么在模块映射文件中不能直接引用sdkroot区域中的头文件,但我无法使其工作)

    第三件事是找到"导入路径"设置并设置为$(srcroot)。实际上,如果您不想在根级别设置CommonCrypto文件夹,可以将其设置为希望其位于的任何文件夹。

    在这之后,你应该可以使用

    1
    import CommonCrypto

    在任何swift文件和所有类型/功能等中都可用。

    不过,警告一句——如果你的应用程序使用libcommoncrypto(或libcorecrocrypto),那么对于一个不太成熟的黑客来说,在你的应用程序上附加一个调试器并找出传递给这些函数的密钥是非常容易的。


    很简单。添加

    1
    #import <CommonCrypto/CommonCrypto.h>

    到.h文件(项目的桥接头文件)。作为惯例,您可以将其称为项目名称桥接头.h。

    然后转到您的项目构建设置并查找Swift编译器代码生成。在它下面,将桥接头的名称添加到条目"objetive-c桥接头"。

    你完了。在您的swift代码中不需要导入。此桥接头文件中列出的任何公共Objective-C头文件将对Swift可见。