如何将指针复制到 Swift 中的数组?
How to copy a pointer to an array in Swift?
我基本上是在尝试将 C 结构包装在 Swift class 中。
Swift class 有一个 C 结构类型的实例 属性。
C 结构包含几个类型为 const char *
的属性。
为了给结构赋值,我为每个属性写了getters和setters:
public class MyClass: NSObject {
private var configuration = c_structure_config()
}
// Function of MyClass
// f.ex. on "registrationUri:"
private(set) public var registrationUri: String {
get {
return String(cString: configuration.reg_uri)
}
set {
if (configuration.reg_uri != nil) {
configuration.reg_uri.deallocate()
}
let maxLength = newValue.count + 1 // +1 for NULL
var buffer: [CChar] = [CChar](UnsafeMutableBufferPointer<Int8>.allocate(capacity: maxLength))
guard newValue.getCString(&buffer, maxLength: maxLength, encoding: .utf8) == true else {
fatalError("Could not allocate memory for Account Config reg uri")
}
// "configuration" is the instance property (see above)
// reg_uri is of type char const *
configuration.reg_uri = UnsafePointer<Int8>(buffer)
}
}
但是,这种方法会导致奇怪的崩溃和错误报告,抱怨指针重叠范围 (Fatal error: UnsafeMutablePointer.initialize overlapping range
)。
我知道每当设置字符串时我都会取消分配内存,这可能不是很有效。不过到目前为止我还没有找到更好的解决方案。
这里有什么问题(或者这是正确的,我做出了错误的假设,我必须在其他地方搜索)?
您的解决方案存在一些问题。
首先是ill-advised用String.count
计数得到底层C缓冲区的大小。原因是 String.count
returns 字符串的 实际字符数 ,而不是用于表示它的字节数。并非所有字符都适合 Int8
的 256 位(a.k.a。CChar
)。因此,如果您尝试将 属性 设置为 non-ascii 个字符,您的代码可能会崩溃。最好用String.utf8CString
属性,其中returns一个连续的CChar
数组。
其次,由于您的缓冲区是在 setter 内分配的,因此当您的 setter returns(Swift 数组是值类型的实例时,它将被释放生命周期受栈约束)。这意味着当你的setterreturns时,buffer
的基址对应的指针实际上是无效的。我怀疑这就是你报告的错误发生在运行时的原因。
最后,请不要在 guards 和 if 语句中测试 true
。
这是更正后的版本:
var registrationUri: String {
get {
return String(cString: reg_uri)
}
set {
if (reg_uri != nil) {
reg_uri.deallocate()
}
newValue.withCString { cString in
// No need to add 1, newValue.utf8CString already has the correct buffer capacity.
let capacity = newValue.utf8CString.count
let buffer: UnsafeMutablePointer<Int8> = .allocate(capacity: capacity)
buffer.assign(from: cString, count: capacity)
reg_uri = UnsafePointer(buffer)
}
}
}
// ...
myInstance.registrationUri = "こんいちは"
print(myInstance.registrationUri)
// Prints "こんいちは"
我基本上是在尝试将 C 结构包装在 Swift class 中。
Swift class 有一个 C 结构类型的实例 属性。
C 结构包含几个类型为 const char *
的属性。
为了给结构赋值,我为每个属性写了getters和setters:
public class MyClass: NSObject {
private var configuration = c_structure_config()
}
// Function of MyClass
// f.ex. on "registrationUri:"
private(set) public var registrationUri: String {
get {
return String(cString: configuration.reg_uri)
}
set {
if (configuration.reg_uri != nil) {
configuration.reg_uri.deallocate()
}
let maxLength = newValue.count + 1 // +1 for NULL
var buffer: [CChar] = [CChar](UnsafeMutableBufferPointer<Int8>.allocate(capacity: maxLength))
guard newValue.getCString(&buffer, maxLength: maxLength, encoding: .utf8) == true else {
fatalError("Could not allocate memory for Account Config reg uri")
}
// "configuration" is the instance property (see above)
// reg_uri is of type char const *
configuration.reg_uri = UnsafePointer<Int8>(buffer)
}
}
但是,这种方法会导致奇怪的崩溃和错误报告,抱怨指针重叠范围 (Fatal error: UnsafeMutablePointer.initialize overlapping range
)。
我知道每当设置字符串时我都会取消分配内存,这可能不是很有效。不过到目前为止我还没有找到更好的解决方案。
这里有什么问题(或者这是正确的,我做出了错误的假设,我必须在其他地方搜索)?
您的解决方案存在一些问题。
首先是ill-advised用String.count
计数得到底层C缓冲区的大小。原因是 String.count
returns 字符串的 实际字符数 ,而不是用于表示它的字节数。并非所有字符都适合 Int8
的 256 位(a.k.a。CChar
)。因此,如果您尝试将 属性 设置为 non-ascii 个字符,您的代码可能会崩溃。最好用String.utf8CString
属性,其中returns一个连续的CChar
数组。
其次,由于您的缓冲区是在 setter 内分配的,因此当您的 setter returns(Swift 数组是值类型的实例时,它将被释放生命周期受栈约束)。这意味着当你的setterreturns时,buffer
的基址对应的指针实际上是无效的。我怀疑这就是你报告的错误发生在运行时的原因。
最后,请不要在 guards 和 if 语句中测试 true
。
这是更正后的版本:
var registrationUri: String {
get {
return String(cString: reg_uri)
}
set {
if (reg_uri != nil) {
reg_uri.deallocate()
}
newValue.withCString { cString in
// No need to add 1, newValue.utf8CString already has the correct buffer capacity.
let capacity = newValue.utf8CString.count
let buffer: UnsafeMutablePointer<Int8> = .allocate(capacity: capacity)
buffer.assign(from: cString, count: capacity)
reg_uri = UnsafePointer(buffer)
}
}
}
// ...
myInstance.registrationUri = "こんいちは"
print(myInstance.registrationUri)
// Prints "こんいちは"