Подтвердить что ты не робот

Преобразование макроса Objective-C (#define) в Swift

Проще говоря, я пытаюсь преобразовать макрос #define в собственную структуру данных Swift. Просто не уверен, как и какой.

Подробнее

Я хотел бы попробовать и скопировать следующий #define из Objective-C в 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

Очевидно, #define не существует в Swift; поэтому я не ищу порт 1:1. В более общем смысле это просто дух.

Чтобы начать, я сделал enum, называемый CryptoAlgorithm. Я всего лишь поддерживаю два криптоалгоритма ради этого вопроса; но ничто не мешает мне продлить его дальше.

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' is not identical to 'CC_SHA1_CTX'. Проблема в том, что CC_SHA1_Init является a UnsafeMutablePointer<CC_SHA1_CTX> -> Int32. Поэтому два типа возврата не совпадают.

Является ли enum неправильным подходом? Должен ли я использовать дженерики? Если да, то каким образом должен быть создан общий? Должен ли я предоставить протокол, который как CC_MD5_CTX, так и CC_SHA1_CTX, а затем расширен и возвращается, что?

Все предложения приветствуются (кроме использования моста Objc).

4b9b3361

Ответ 1

Я не знаю, люблю ли я, где это происходит в исходном коде 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)) }
        }
    }

Чем более "быстрый" способ приближения к этому будет связан с протоколами, такими как:

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

Тогда у вас будет что-то вроде (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)
}

Но мне было бы очень сложно скрывать контекст и просто сделать это:

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