本文介绍了将Objective-C(#define)宏转换为Swift的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简单来说,我正在尝试将一个 #define 宏转换成某种Swift数据结构。



详细信息



我想尝试并复制以下<$ c ++ c> #define 从Objective-C到Swift。

  #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的港口。一般来说只是它的精神。



要开始,我做了一个枚举调用 CryptoAlgorithm 。为了这个问题,我只关心支持两种加密算法;但是应该没有什么可以阻止我进一步扩展它。

 枚举CryptoAlgorithm {
case MD5,SHA1
}

到目前为止这么好。现在要实现 digestLength

 枚举加密算法{
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

 枚举加密算法{
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_CTX 和 CC_SHA1_CTX ,然后被扩展并返回?



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

解决方案

不知道我是否喜欢原始的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的方式是使用协议,例如:

 协议CryptoAlgorithm {
typealias上下文
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)//这实际上不会失败
self.context = ctx //这很危险....但匹配上面。 (见下文)
}
let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}

但是我很有可能会隐藏上下文,只需要:

 协议CryptoAlgorithm {
init()
var digestLength:Int {get}
}

struct SHA1:CryptoAlgorithm {
private var context = CC_SHA1_CTX()
init (){
CC_SHA1_Init(& context)//这很可能是多余的。
}
let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}

为什么你需要暴露一个事实,它是CommonCrypto的封面?为什么要依靠呼叫者来保持上下文呢?如果超出范围,则稍后的呼叫将崩溃。我会坚持到里面的内容。






更深入地了解你的原始问题,考虑一下(编译,但不是测试):

  //摘要是引用类型,因为它们是有状态的。复制它们可能会导致混乱的结果。 
协议摘要: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}
}

//所有摘要的一些帮助者使他们更快地行动,而不必处理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)}
}
//帮助器包装创建一个缓冲区,更新缓冲区,返回缓冲区
private func perform(f:(UnsafeMutablePointer< UInt8>) - >()) - > [UInt8] {
var hash = [UInt8](count:length,repeatedValue:0)
f(& hash)
return hash
}
}

//创建新摘要的示例
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
}

//这里是你改变另外一个
final class SHA256 :Dig​​est {
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
}

//类型橡皮擦,所以我们可以谈论任意摘要,而不用担心底层的关联类型。
//参见http://robnapier.net/erasure
//所以现在我们可以这样说:let digests = [AnyDigest(SHA1()),AnyDigest(SHA256())]
//如果这是正常的用例,您可以将Digest重命名为DigestAlgorithm,并将AnyDigest重命名为Digest
//,以方便
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)}
}
}


Put simply I am trying to convert a #define macro into a native Swift data structure of some sort. Just not sure how or what kind.

Details

I would like to try and replicate the following #define from Objective-C to Swift. Source: 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

Obviously #define does not exist in Swift; therefore I'm not looking for a 1:1 port. More generally just the spirit of it.

To start, I made an enum called CryptoAlgorithm. I only care to support two crypto algorithms for the sake of this question; but there should be nothing stopping me from extending it further.

enum CryptoAlgorithm {
  case MD5, SHA1
}

So far so good. Now to implement the 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)
  }
}

Again, so far so good. Now to implement the 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
    }
  }
}

Crash and burn. 'CC_MD5_CTX' is not identical to 'CC_SHA1_CTX'. The problem is that CC_SHA1_Init is a UnsafeMutablePointer<CC_SHA1_CTX> -> Int32. Therefore, the two return types are not the same.

Is an enum the wrong approach? Should I be using generics? If so, how should the generic be made? Should I provide a protocol that both CC_MD5_CTX and CC_SHA1_CTX and then are extended by and return that?

All suggestions are welcome (except to use an Objc bridge).

解决方案

I don't know if I love where this is going in the original ObjC code, because it's pretty type-unsafe. In Swift you just need to make all the type unsafety more explicit:

    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)) }
        }
    }

The more "Swift" way of approaching this would be with protocols, such as:

protocol CryptoAlgorithm {
    typealias Context
    init(_ ctx: UnsafeMutablePointer<Context>)
    var digestLength: Int { get }
}

Then you'd have something like (untested):

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)
}

But I'd be strongly tempted to hide the context, and just make it:

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)
}

Why do you need to expose the fact that it's CommonCrypto under the covers? And why would you want to rely on the caller to hold onto the context for you? If it goes out of scope, then later calls will crash. I'd hold onto the context inside.


Getting more closely to your original question, consider this (compiles, but not tested):

// 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) }
    }
}

这篇关于将Objective-C(#define)宏转换为Swift的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-19 17:02