【问题标题】:Errors when update code to avoid deprecation warnings withUnsafeMutableBytes in swift 5更新代码以避免在 swift 5 中使用 UnsafeMutableBytes 出现弃用警告时出错
【发布时间】:2019-03-27 18:30:14
【问题描述】:

我已更新到 swift 5,但我使用的依赖项之一无法在 swift 5 中编译。我已修复它,但现在我在整个文件中收到 350 多个弃用警告。它们都与此类似:

withUnsafeMutableBytes 已弃用:改用withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R

这是代码片段(基本上只是调用 c 库的函数):

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { kPtr in
    flutter_sodium.crypto_generichash_keygen(kPtr)
}

作为参考,上面的crypto_generichash_keybytes()只返回一个size_t,crypto_generichash_keygen的签名是void crypto_generichash_keygen(unsigned char k[crypto_generichash_KEYBYTES]);

我发现(如this answer 所述)解决此问题的方法应该是调用 kPtr.baseAddress:

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { kPtr in
    flutter_sodium.crypto_generichash_keygen(kPtr.baseAddress)
}

因为应该使用withUnsafeMutableBytes<ResultType> 变体而不是已弃用的withUnsafeMutableBytes<ResultType, ContentType>。但是,这反而会导致错误

“UnsafeMutablePointer<_>”类型的值没有成员“baseAddress”。

如果我明确指定 resultType 和 kPtr:

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { (kPtr: UnsafeMutableRawBufferPointer) -> Void in
    flutter_sodium.crypto_generichash_keygen(kPtr.baseAddress)
}

我反而得到了

UnsafeMutableRawBufferPointer' 不能转换为 'UnsafeMutablePointer<_>'。

有没有快速的专家可以帮助我找出正确的方法来做到这一点?我知道警告只是警告,但我更喜欢编译时没有警告的代码。

在发布此问题之前,我查看了Swift 5.0: 'withUnsafeBytes' is deprecated: use `withUnsafeBytes<R>(...),这对我的情况没有帮助,因为我没有加载指针而是使用数据。另外,我已经完全按照文档告诉我的去做了,但这仍然没有帮助。

编辑:为了更清楚一点,350 多个警告中的一些与代码中分配了 Data 的代码有关,但是其中一些是我从外部来源收到 Data 的地方。看起来像这样:

let args = call.arguments as! NSDictionary
let server_pk = (args["server_pk"] as! FlutterStandardTypedData).data
let server_sk = (args["server_sk"] as! FlutterStandardTypedData).data
let client_pk = (args["client_pk"] as! FlutterStandardTypedData).data

var rx = Data(count: flutter_sodium.crypto_kx_sessionkeybytes())
var tx = Data(count: flutter_sodium.crypto_kx_sessionkeybytes())
let ret = rx.withUnsafeMutableBytes { rxPtr in
  tx.withUnsafeMutableBytes { txPtr in
    server_pk.withUnsafeBytes { server_pkPtr in
      server_sk.withUnsafeBytes { server_skPtr in
        client_pk.withUnsafeBytes { client_pkPtr in
          flutter_sodium.crypto_kx_server_session_keys(rxPtr, txPtr, server_pkPtr, server_skPtr, client_pkPtr)
        }
      }
    }
  }
}

用对应的方法调用

SODIUM_EXPORT
int crypto_kx_client_session_keys(unsigned char rx[crypto_kx_SESSIONKEYBYTES],
                                  unsigned char tx[crypto_kx_SESSIONKEYBYTES],
                                  const unsigned char client_pk[crypto_kx_PUBLICKEYBYTES],
                                  const unsigned char client_sk[crypto_kx_SECRETKEYBYTES],
                                  const unsigned char server_pk[crypto_kx_PUBLICKEYBYTES])
            __attribute__ ((warn_unused_result));

(而且我知道代码并不是真正的最佳 swift,但是在处理 dart 和 swift 之间的互操作性时,这是 Flutter 团队提出的如何做到这一点)。

当我提出这个问题时,我试图将其提炼为最简单的情况,但该情况的具体答案与我遇到的整体问题不同。

【问题讨论】:

标签: swift


【解决方案1】:

我不会在这里使用Data - Data 表示“原始”字节的无类型集合,但是crypto_generichash_keygen 想要一个指向类型内存的可变指针。不推荐使用 withUnsafeMutableBytesUnsafeMutablePointer&lt;T&gt; 变体的原因是,在无类型内存上提供从根本上说是错误的抽象。

在 Swift 中获取类型化内存缓冲区的最简单方法是使用 Array

var k = [UInt8](repeating: 0, count: crypto_generichash_keybytes())
flutter_sodium.crypto_generichash_keygen(&k)

之后,您可以随时通过说 Data(k) 将结果缓冲区转换为 Data 值。

另一种选择是使用UnsafeMutableBufferPointer

let k = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: crypto_generichash_keybytes())
defer {
  k.deallocate()
}
flutter_sodium.crypto_generichash_keygen(k.baseAddress!)
// Now use the buffer `k` – just make sure you finish using it before the end of
// the scope when `deallocate()` gets called!

Array 不同,这避免了在传递给 C API 之前必须用零预填充结果缓冲区,但这可能不是问题。但就像Array 一样,你可以通过说Data(k) 将这样的缓冲区变成Data


如果您从某个外部来源获得 Data 值并需要将其作为类型化指针传递给 API,最简单和最安全的选择是在传递它之前将其转换为数组: Array(someData).

例如:

let args = call.arguments as! NSDictionary
let server_pk = (args["server_pk"] as! FlutterStandardTypedData).data
let server_sk = (args["server_sk"] as! FlutterStandardTypedData).data
let client_pk = (args["client_pk"] as! FlutterStandardTypedData).data

var rx = [UInt8](repeating: 0, count: flutter_sodium.crypto_kx_sessionkeybytes())
var tx = [UInt8](repeating: 0, count: flutter_sodium.crypto_kx_sessionkeybytes())

flutter_sodium.crypto_kx_server_session_keys(
  &rx, &tx, Array(server_pk), Array(server_sk), Array(client_pk)
)

可能可以使用 withUnsafeBytes 并在底层指针上调用 bindMemory,但我不鼓励这样做,因为它会改变底层内存的类型,这可能会微妙地影响任何其他 Swift 代码共享该内存,因为您要从它下面切换类型。

【讨论】:

  • 啊,这很有趣。我可以看到在这种特殊情况下使用数组将是一个更明智的选择。而且由于 Data 当前将缓冲区归零,因此我认为这也不是 Array 的性能问题——而且我更喜欢 Array 的做法,而不是摆弄 UnsafeMutableBufferPointers。我一直认为,如果名称中包含“不安全”,则应避免使用。
  • 但是,我也在处理将 Data 对象直接传递给我的情况(从 Flutter 的抽象层接收来自 Dart 代码的原始数据) - 我应该更清楚关于这个问题。在那种情况下,我真的没有选择从数组开始,而是重新尝试使用 withUnsafeMutableByteswithUnsafeBytes
  • 我已经更详细地更新了我的问题;正如我在更新中所说,我试图将问题提炼为最简单的情况,但我认为你的答案对于我自己分配 Data 的情况是 100% 正确的,但对这些情况没有帮助我正在使用从其他地方获得的Data
  • @rmtmckenzie 老实说,处理这个问题的最简单方法可能是将您获得的Data 值转换为数组,然后再将它们传递给API,例如flutter_sodium.crypto_kx_server_session_keys(rxPtr, Array(tx), Array(server_pk), Array(server_sk), Array(client_pk))。这也很好地消除了厄运金字塔。
  • 可能可以使用withUnsafeBytes并在底层指针上调用bindMemory,但我不鼓励这样做,因为它会改变底层内存的类型,这可能会巧妙地通过从其下方切换类型来影响共享该内存的其他代码的可靠性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 2011-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多