将Objective-C中的(#define)宏转换为Swift

12

简单来说,我正在尝试将一个 #define 宏定义转换为一种原生的Swift数据结构。只是不确定应该如何转换和使用哪种数据结构。

细节

我想尝试复制以下Objective-C中的#define到Swift中。来源:JoeKun/FileMD5Hash

#define FileHashComputationContextInitialize(context, hashAlgorithmName)                    \
    CC_##hashAlgorithmName##_CTX hashObjectFor##hashAlgorithmName;                          \
    context.initFunction      = (FileHashInitFunction)&CC_##hashAlgorithmName##_Init;       \
    context.updateFunction    = (FileHashUpdateFunction)&CC_##hashAlgorithmName##_Update;   \
    context.finalFunction     = (FileHashFinalFunction)&CC_##hashAlgorithmName##_Final;     \
    context.digestLength      = CC_##hashAlgorithmName##_DIGEST_LENGTH;                     \
    context.hashObjectPointer = (uint8_t **)&hashObjectFor##hashAlgorithmName

显然,在Swift中不存在#define,因此我不是在寻找1:1端口。更一般地说,只是它的精神。

首先,我创建了一个名为CryptoAlgorithmenum。为了回答这个问题,我只关心支持两种加密算法;但是我应该没有阻止将其扩展到更多算法。

enum CryptoAlgorithm {
  case MD5, SHA1
}

目前为止一切顺利。现在需要实现 digestLength

enum CryptoAlgorithm {
  case MD5, SHA1

  var digestLength: Int {
    switch self {
    case .MD5:
      return Int(CC_MD5_DIGEST_LENGTH)
    case .SHA1:
      return Int(CC_SHA1_DIGEST_LENGTH)
  }
}

再次强调,目前为止一切顺利。现在需要实现initFunction

enum CryptoAlgorithm {
  case MD5, SHA1

  var digestLength: Int {
    switch self {
    case .MD5:
      return Int(CC_MD5_DIGEST_LENGTH)
    case .SHA1:
      return Int(CC_SHA1_DIGEST_LENGTH)
  }

  var initFunction: UnsafeMutablePointer<CC_MD5_CTX> -> Int32 {
    switch self {
    case .MD5:
      return CC_MD5_Init
    case .SHA1:
      return CC_SHA1_Init
    }
  }
}

崩溃并烧毁。 'CC_MD5_CTX' 不同于 'CC_SHA1_CTX'。问题在于 CC_SHA1_Init 是一个 UnsafeMutablePointer<CC_SHA1_CTX> -> Int32。因此,这两个返回类型不同。

枚举是否是错误的方法?我应该使用泛型吗?如果是这样,应该如何创建这个泛型?我应该提供一个协议,让 CC_MD5_CTXCC_SHA1_CTX 扩展并返回它吗?

欢迎所有建议(除了使用Objc桥接)。


var initFunction: UnsafeMutablePointer<Void> -> Int32 { ... } 这个怎么样? - Code Different
很遗憾,'Void'与'CC_MD5_CTX'不完全相同。也尝试了'Any',但无济于事。'Any'与'CC_MD5_CTX'不完全相同。 - Ryan
你能否包含CC_MD5_InitCC_SHA1_Init的定义? - Code Different
当然,它们都来自CommonCrypto库。[来源:CommonDigest.h] (http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-7/CommonCrypto/CommonDigest.h) - Ryan
1个回答

10

我不确定我是否喜欢原始的ObjC代码的方向,因为它非常不安全。在Swift中,你只需要使所有类型不安全更加明确:

    var initFunction: UnsafeMutablePointer<Void> -> Int32 {
        switch self {
        case .MD5:
            return { CC_MD5_Init(UnsafeMutablePointer<CC_MD5_CTX>($0)) }
        case .SHA1:
            return { CC_SHA1_Init(UnsafeMutablePointer<CC_SHA1_CTX>($0)) }
        }
    }

更“Swift”的方式是采用协议来实现,例如:
protocol CryptoAlgorithm {
    typealias Context
    init(_ ctx: UnsafeMutablePointer<Context>)
    var digestLength: Int { get }
}

然后你会得到类似这样的代码(未经测试):
struct SHA1: CryptoAlgorithm {
    typealias Context = CC_SHA1_CONTEXT
    private let context: UnsafeMutablePointer<Context>
    init(_ ctx: UnsafeMutablePointer<Context>) {
        CC_SHA1_Init(ctx) // This can't actually fail
        self.context = ctx // This is pretty dangerous.... but matches above. (See below)
    }
    let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}

不过我更倾向于隐藏上下文,只需这样写:

protocol CryptoAlgorithm {
    init()
    var digestLength: Int { get }
}

struct SHA1: CryptoAlgorithm {
    private var context = CC_SHA1_CTX()
    init() {
        CC_SHA1_Init(&context) // This is very likely redundant.
    }
    let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}

为什么你需要暴露它在CommonCrypto下的事实?为什么你想要依赖调用者来为你保存上下文?如果它超出范围,那么后续的调用将会崩溃。我会在内部保留上下文。
更接近你最初的问题,考虑以下内容(可以编译,但未经测试):
// Digests are reference types because they are stateful. Copying them may lead to confusing results.
protocol Digest: class {
    typealias Context
    var context: Context { get set }
    var length: Int { get }
    var digester: (UnsafePointer<Void>, CC_LONG, UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8> { get }
    var updater: (UnsafeMutablePointer<Context>, UnsafePointer<Void>, CC_LONG) -> Int32 { get }
    var finalizer: (UnsafeMutablePointer<UInt8>, UnsafeMutablePointer<Context>) -> Int32 { get }
}

// Some helpers on all digests to make them act more Swiftly without having to deal with UnsafeMutablePointers.
extension Digest {
    func digest(data: [UInt8]) -> [UInt8] {
        return perform { digester(UnsafePointer<Void>(data), CC_LONG(data.count), $0) }
    }
    func update(data: [UInt8]) {
        updater(&context, UnsafePointer<Void>(data), CC_LONG(data.count))
    }
    func final() -> [UInt8] {
        return perform { finalizer($0, &context) }
    }
    // Helper that wraps up "create a buffer, update buffer, return buffer"
    private func perform(f: (UnsafeMutablePointer<UInt8>) -> ()) -> [UInt8] {
        var hash = [UInt8](count: length, repeatedValue: 0)
        f(&hash)
        return hash
    }
}

// Example of creating a new digest
final class SHA1: Digest {
    var context = CC_SHA1_CTX()
    let length = Int(CC_SHA1_DIGEST_LENGTH)
    let digester = CC_SHA1
    let updater = CC_SHA1_Update
    let finalizer = CC_SHA1_Final
}

// And here's what you change to make another one
final class SHA256: Digest {
    var context = CC_SHA256_CTX()
    let length = Int(CC_SHA256_DIGEST_LENGTH)
    let digester = CC_SHA256
    let updater = CC_SHA256_Update
    let finalizer = CC_SHA256_Final
}

// Type-eraser, so we can talk about arbitrary digests without worrying about the underlying associated type.
// See http://robnapier.net/erasure
// So now we can say things like `let digests = [AnyDigest(SHA1()), AnyDigest(SHA256())]`
// If this were the normal use-case, you could rename "Digest" as "DigestAlgorithm" and rename "AnyDigest" as "Digest"
// for convenience
final class AnyDigest: Digest {
    var context: Void = ()
    let length: Int
    let digester: (UnsafePointer<Void>, CC_LONG, UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8>
    let updater: (UnsafeMutablePointer<Void>, UnsafePointer<Void>, CC_LONG) -> Int32
    let finalizer: (UnsafeMutablePointer<UInt8>, UnsafeMutablePointer<Void>) -> Int32

    init<D: Digest>(_ digest: D) {
        length = digest.length
        digester = digest.digester
        updater = { digest.updater(&digest.context, $1, $2) }
        finalizer = { (hash, _) in digest.finalizer(hash, &digest.context) }
    }
}

抱歉耽搁了。我真的不想暴露它是CommonCrypto。如果有更好的方法,我全听着呢。我真正想做的就是定义一些带有签名的函数,例如func digest(also: CryptoAlgorithm)。在该函数中,根据所有支持的摘要算法进行泛化。对于你的观点,无论是概念上的“digest”函数还是“enum”都具有广义的“CC_INIT”引用,这对我来说并不重要。我只是试图找到一种始终有效的定义变量的方法。 - Ryan
通过减少一些代码重复,并使摘要类型纯粹配置,我更新了一个实现,我认为更接近您的最初目标。 - Rob Napier

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