在一个Swift框架中导入CommonCrypto

190

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

我知道如何在Swift应用程序中使用CommonCrypto:将#import <CommonCrypto/CommonCrypto.h>添加到桥接头文件中。 然而,Swift框架不支持桥接头文件。 文档中写道:

您可以导入具有纯Objective-C代码库、纯Swift代码库或混合语言代码库的外部框架。导入外部框架的过程与框架是单一语言编写还是包含两种语言文件无关。导入外部框架时,请确保您正在导入的框架的“定义模块”构建设置已设置为“Yes”。

您可以使用以下语法将一个框架导入到不同目标中的任何Swift文件中:

import FrameworkName

很遗憾,导入CommonCrypto失败了。在总头文件中添加#import <CommonCrypto/CommonCrypto.h>也不行。


2
@rmaddy Objective-C是C语言的超集。您是在说我们不能从Swift使用CommonCrypto吗? - hpique
4
@rmaddy,我刚刚成功地使用模块映射使CommonCrypto工作起来了。我将完善这个解决方案,并在今天晚些时候发布它。 - hpique
如果您觉得方便,并且您正在寻找的功能已经实现,可以尝试使用CryptoSwift - Marcin
1
苹果刚刚开源了CommonCrypto。如果我们有源代码,或许可以让它运行起来。 - eyeballz
有没有一个真正解决方案的时间表,而不是这些临时措施?这和ABI兼容性让我觉得Apple没明白或者只是不在乎。我认真考虑将我的框架迁回Objective C。我不喜欢这种语言,但至少我不必在每个版本中重新学习String类。 - Roy Falk
显示剩余2条评论
16个回答

141
创建一个名为“CommonCryptoModuleMap”的聚合目标,使用运行脚本阶段自动生成模块映射,并使用正确的Xcode/SDK路径,这样更简单和更可靠。

enter image description here enter image description here

Run Script阶段应包含以下bash代码:

# 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-select 切换到 beta 版本,或者在 CI 服务器上构建时安装了多个版本并放置在非标准位置。您也不需要硬编码 SDK,因此这应该适用于 iOS、macOS 等。您也不需要在项目源目录中放置任何内容。
创建此目标后,使用“目标依赖项”使您的库/框架依赖于它:

enter image description here

这将确保在构建框架之前生成模块映射。 macOS提示:如果您还支持macOS,则需要将macosx添加到刚创建的新聚合目标的Supported Platforms构建设置中,否则它将无法将模块映射放置在正确的Debug派生数据文件夹中与其余的框架产品一起。

enter image description here

接下来,在Swift部分的“导入路径”构建设置下,添加模块映射的父目录${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap:

enter image description here

请记得在项目或xcconfig级别定义搜索路径时添加$(inherited)行。
这样,您现在应该能够导入CommonCrypto了。
Xcode 10现在附带有一个CommonCrypto模块映射,因此不需要使用此解决方法。如果您想同时支持Xcode 9和10,可以在Run Script阶段检查模块映射是否存在。
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

8
这个答案应该排在最上面。简洁而优美。 - Abdullah Saeed
1
虽然有点晚了,但这应该是最佳答案。它很简单,其他开发人员在同一项目上工作时更容易看到需求。 - fatuous.logic
1
如果我在我的.framework中这样做,那么我在包含此框架的项目中是否也需要这样做? - Ravi Kiran
1
@MikeWeller 你好,我按照上面描述的步骤进行了操作,但是出现了“No such module 'CommonCrypto'”的错误。我现在使用的是xCode 9。是否需要做些什么才能使它工作?还是我遗漏了什么。 - Vanya
2
@IanDundas 我已经更新了上面的代码,修复了重新编译的问题,并解决了在macOS上使用的问题。 - iwasrobbed
显示剩余17条评论

95

您实际上可以构建一个“只需工作”的解决方案(无需像其他解决方案一样复制module.modulemapSWIFT_INCLUDE_PATHS设置到您的项目中),但需要您创建一个虚拟的框架/模块,将其导入到您的实际框架中。我们还可以确保它适用于不同的平台(iphoneosiphonesimulatormacosx)。

  1. 向您的项目添加一个新的框架目标,并将其命名为系统库,例如“CommonCrypto”。(您可以删除伞头文件CommonCrypto.h。)

  2. 添加一个新的配置设置文件,并将其命名为“CommonCrypto.xcconfig”,并填充以下内容。(不要选择任何目标以包含。)

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

    • iphoneos.modulemap

      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
      module CommonCrypto [system] {
          header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/CommonCrypto/CommonCrypto.h"
          export *
      }
      
    • macosx.modulemap

      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,请将 10.11 替换为您当前的操作系统SDK。)

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

  6. 在框架目标的构建阶段选项卡中,将CommonCrypto框架添加到目标依赖项。此外,在链接二进制文件构建阶段中还要添加libcommonCrypto.dylib

  7. 产品中选择CommonCrypto.framework,并确保其目标成员资格可选

现在,您应该能够在包装器框架中构建,运行和导入 CommonCrypto 了。

例如,请参见SQLite.swift如何使用虚拟的sqlite3.framework


4
没有执行步骤(5)对我来说可行。如果执行了步骤(5),会出现以下构建错误:ld: cannot link directly with /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.2.sdk/usr/lib/system/libcommonCrypto.dylib. Link against the umbrella framework 'System.framework' instead. for architecture x86_64。建议使用系统框架“System.framework”进行链接,而不要直接链接到“/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.2.sdk/usr/lib/system/libcommonCrypto.dylib”。 - stannie
4
太棒了!我用它来制作 https://github.com/soffes/Crypto,但不必链接 System.framework。值得注意的是,如果你的框架跨平台,你必须为 Mac 和 iOS 分别制作一个独立的封装框架。 - Sam Soffes
34
人们如何或从哪里了解这些信息呢? - hola
5
请注意,在第一步中需要选择Objective-C作为语言。这容易被忽视。此外,可能由于我没有一个.dylib文件,在第5步中我需要添加.framework文件。 - Teo Sartori
7
太可怕了。我的Xcode集合被破坏了,每一个都有不同的问题,头文件的绝对路径到处都是,真是太令人作呕了。在Cupertino,或至少负责这个modulemap混乱的人出了大问题。 - Anton Tropashko
显示剩余20条评论

84

我发现了一个GitHub项目,成功地在Swift框架中使用了CommonCrypto:SHA256-Swift。此外,这篇文章关于使用sqlite3遇到的同样问题也很有用。

基于上述内容,步骤如下:

1)在项目目录中创建一个名为CommonCrypto的文件夹,在其中创建一个module.map文件,这个文件将允许我们在Swift中将CommonCrypto库作为模块使用。它的内容如下:

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) 在“Build Settings”中的Swift Compiler - Search Paths中,将CommonCrypto目录添加到Import PathsSWIFT_INCLUDE_PATHS)。

Build Settings

3) 最后,在您的Swift文件中像导入其他模块一样导入CommonCrypto。例如:

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(设置导入路径)。

模块映射不具备平台独立性(它当前指向特定平台,即iOS 8模拟器)。我不知道如何使头文件路径相对于当前平台。

iOS 8及以下版本的更新:我们应该删除行link "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"这一行,否则会一直出现此错误。一旦我删除了这一行,就成功构建了。


31
天啊,苹果真想让事情变得困难。如果Swifties能让我们在不必经历所有这些麻烦的情况下导入文件/框架,那会死他们吗? - zaph
4
这真让人恼火,因为$SDKROOT变量应该允许您使用独立于平台的路径,但我不知道如何在Swift中实现。 - danimal
2
直到我从module.map文件中删除了“link "CommonCrypto"”才使它对我起作用。 - Teo Sartori
1
有人能确认这在Xcode 7.3上可行吗?这个解决方案在更新后对我不起作用了。 - Nikita Kukushkin
1
苹果的XCode和Swift有太多问题了,我都不知道从哪里开始说起。你是认真的吗?在项目设置文件中使用绝对路径,我怎么能让团队一起合作呢?他们每次开始工作前都必须更改路径吗?真的吗?/Applications/Xcode6-Beta5.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator8.0.sdk/usr/include/CommonCrypto/CommonCrypto.h" - Siddharth
显示剩余12条评论

52

本答案讨论如何在框架中使用以及使用 Cocoapods 和 Carthage 时的解决方案

模块映射方法

我在我对 CommonCrypto 的包装器 https://github.com/onmyway133/arcane, https://github.com/onmyway133/Reindeer 中使用了 modulemap

对于那些遇到 头文件未找到 的人,请查看 https://github.com/onmyway133/Arcane/issues/4 或运行 xcode-select --install

  • 创建一个名为 CCommonCrypto 的文件夹,其中包含 module.modulemap

  module CCommonCrypto {
    header "/usr/include/CommonCrypto/CommonCrypto.h"
    export *
  }
  • 前往“Build Settings” -> “导入路径”

  •   ${SRCROOT}/Sources/CCommonCrypto
    

    使用modulemap的Cocoapods

      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 的本地开发 Pod 无法正常工作,查看https://github.com/CocoaPods/CocoaPods/issues/809

  • 这就是为什么您可以在我的示例 Podfile 中看到 https://github.com/onmyway133/CommonCrypto.swift/blob/master/Example/CommonCryptoSwiftDemo/Podfile 指向 git 存储库的原因。

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

    公共头文件方法

      $(SDKROOT)/usr/include/libxml2
    
    它有一个"Build Settings -> Other Linker Flags"的设置。
      -lxml2
    

    采用公共头文件方法的Cocoapods

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

    有趣的相关文章


    1
    哇!令我惊讶的是,从顶部方法(创建module.modulemap文件)的头路径非常有效,而以前的modulemap文件一直引起了很多问题。我一直在使用一个module.modulemap文件,其中包含对/CommonCrypto/CommonCrypto.h的绝对路径,在Applications/XCode.app/Contents/Developer/Platforms/iPhoneOS....中进行手动修改,这需要对已重命名XCode的人进行手动修改。将该行切换为查看"/usr/include/CommonCrypto/CommonCrypto.h"似乎对于拥有多个XCode版本的团队来说可以正常工作。非常感谢! - Natalia
    3
    我正在创建一个Pod,我设置了SWIFT_INCLUDE_PATHS和preserve_paths。当我运行pod lib lint时,构建失败并显示错误:no such module 'CommonCrypto'。我该怎么处理? - Klein Mioke
    2
    与问题无关,但我喜欢使用表情符号作为项目符号! - Fomentia
    2
    @onmyway133 如果你将 $(PODS_ROOT)/CommonCryptoSwift/Sources/CCommonCrypto 替换为 $(PODS_TARGET_SRCROOT)/Sources/CCommonCrypto,那么使用本地开发 pod 就可以了。对于本地 pod,PODS_TARGET_SRCROOT 已经被正确设置。 - Orkhan Alikhanov
    非常好的答案,救了我的命!非常感谢! - Hassan Shahbazi

    46

    好消息!Swift 4.2 (Xcode 10) 终于提供了 CommonCrypto!

    只需在您的 Swift 文件中添加import CommonCrypto即可。


    1
    应用商店只显示 9.4.7 作为可用更新,你是如何获得 Xcode 10 的? - Hammad Tariq
    1
    它还处于测试阶段,只需进行简单的谷歌搜索即可得知。 - mxcl
    太棒了!只需在您的Swift文件中执行import CommonCrypto - Mohammad Zaid Pathan
    1
    @SomoyDasGupta 是的。只需删除先前的导入,然后再次编译即可。换句话说,您不必执行MikeWeller答案中的步骤。 - COLD ICE
    @SomoyDasGupta 是的,我相信是这样的。 - COLD ICE
    显示剩余5条评论

    7

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


    我的团队有一位新成员不小心破坏了一个顶级答案提供的解决方案,因此我决定将其整合到一个名为CommonCryptoModule的小包装项目中。您可以手动安装它或通过Cocoapods安装:

    pod 'CommonCryptoModule', '~> 1.0.2'
    

    然后,你只需要在需要使用CommonCrypto的模块中导入该模块,如下所示:

    import CommonCryptoModule
    

    希望其他人也会发现这个有用。


    警告:如果您使用此方法,您的应用程序将被拒绝! - Segabond
    是的,我们也被拒绝了,非常奇怪,因为我们使用这种方法上传了几个月而没有任何问题。 - Nikita Kukushkin

    6

    如果您正在使用与Xcode 10一起的swift 4.2

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

    import CommonCrypto
    
    

    1
    @mxcl的回答的副本:https://dev59.com/J18e5IYBdhLWcg3w6NyW#50690346 - Rainer Schwarz

    5

    我认为我对Mike Weller的优秀工作有所改进。

    在“编译源”阶段之前添加一个运行脚本阶段,其中包含以下Bash脚本:

    # 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的自动搜索来查找框架。

    我将原始的CommonCrypto包含文件夹链接为框架的头文件夹,因此结果也适用于Objective C项目。


    请参考dvdblk的答案,其中提供了适用于CocoaPods使用的改进。 - jjrscott

    4
    模块映射方案可能很好,并且对SDK更改具有强大的鲁棒性,但我发现在实践中使用它们很笨拙,并且不像我希望的那样可靠,当将东西交给他人时。为了使所有内容更加简单易懂,我选择了另一种方法:
    只需复制头文件。
    我知道,这很脆弱。但是,苹果公司几乎从不对CommonCrypto进行重大更改,而我正在梦想他们不会在没有将CommonCrypto变成模块化头文件的同时以任何重要方式进行更改。
    通过“复制头文件”,我的意思是“将您需要的所有头文件剪切并粘贴到一个大型头文件中,就像预处理器所做的那样。”作为您可以复制或调整的示例,请参见RNCryptor.h
    请注意,所有这些文件都是根据APSL 2.0许可证授权的,此方法故意保留版权和许可证通知。我的串联步骤已获得MIT许可,仅适用于下一个许可证通知)。
    我不是说这是一个美丽的解决方案,但到目前为止,它似乎是一个非常简单的解决方案,既易于实现又易于支持。

    我发现这个编译非常可靠,而且很简单明了。 使用该框架的应用程序是否需要链接到任何特殊内容,或者CommonCrypto始终可用? - codingFriend1
    1
    我认为 Security.framework 会自动链接(因为我已经有一段时间没有开始新项目了)。如果您遇到错误,那就是需要链接的框架。 - Rob Napier
    这似乎是最简单的解决方案,而且在一台机器上效果很好,但是每当我在另一台机器上的另一个框架或应用程序中使用该框架时,就会出现'缺少模块'错误。 - Rich Everhart

    4

    @mogstad非常友好地将@stephencelis的解决方案封装成了Cocoapod:

    pod 'libCommonCrypto'

    其他可用的pod对我无效。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接