the programming lenguaje language descargar apple c swift macos swift2

programming - ¿Por qué no puedo pasar una función Swift String a C opcional que permite punteros NULL?



the swift programming language pdf (3)

Tengo una función C que trata con cadenas de C. La función realmente permite que las cadenas sean punteros NULL. La declaración es la siguiente:

size_t embeddedSize ( const char *_Nullable string );

No es problema usar esta función como esta en C:

size_t s1 = embeddedSize("Test"); size_t s2 = embeddedSize(NULL); // s2 will be 0

Ahora estoy tratando de usarlo de Swift. El siguiente código funciona

let s1 = embeddedSize("Test") let s2 = embeddedSize(nil) // s2 will be 0

¡Pero lo que no funciona es pasarle una cadena opcional! Este código no se compilará

let someString: String? = "Some String" let s2 = embeddedSize(someString)

El compilador arroja un error sobre el hecho de que no se desenvuelve la opción y Xcode me pregunta si tal vez olvidé agregarlo ! o ? . Sin embargo, ¿por qué querría desenvolverlo? NULL o nil son valores válidos para ser alimentados a la función. Ver arriba, acabo de nil directamente a nil y compilé muy bien y devolví el resultado esperado. En mi código real, la cadena se alimenta desde el exterior y es opcional, no puedo obligar a desenvolverlo, que se romperá si la cadena fue nil . Entonces, ¿cómo puedo llamar a esa función con una cadena opcional?


La respuesta más probable es que mientras los literales de cadena son convertibles a UnsafePointer<CChar> , y nil es convertible a UnsafePointer<CChar> , y String también es, String? podría no estar en Swift 2.


Todas las soluciones que indirecta la llamada por nivel extra rápido funcionan bien si solo tiene un parámetro. Pero también tengo funciones C como esta ( strX no son los nombres reales de los parámetros, la llamada en realidad se simplifica):

size_t calculateTotalLength ( const char *_Nullable str1, const char *_Nullable str2, const char *_Nullable str3, const char *_Nullable str4, const char *_Nullable str5 );

Y aquí esta indirección se vuelve poco práctica, ya que necesito una indirección por argumento, 5 indirecciones para la función anterior.

Este es el mejor (feo) "truco" que se me ocurrió hasta ahora, que evita este problema (todavía estoy feliz de ver mejores soluciones, tal vez alguien tenga una idea al ver ese código):

private func SwiftStringToData ( string: String? ) -> NSData? { guard let str = string else { return nil } return str.dataUsingEncoding(NSUTF8StringEncoding) } let str1 = SwiftStringToData(string1) let str2 = SwiftStringToData(string2) let str3 = SwiftStringToData(string3) let str4 = SwiftStringToData(string4) let str5 = SwiftStringToData(string5) let totalLength = calculateTotalLength( str1 == nil ? UnsafePointer<Int8>(nil) : UnsafePointer<Int8>(str1!.bytes), str2 == nil ? UnsafePointer<Int8>(nil) : UnsafePointer<Int8>(str2!.bytes), str3 == nil ? UnsafePointer<Int8>(nil) : UnsafePointer<Int8>(str3!.bytes), str4 == nil ? UnsafePointer<Int8>(nil) : UnsafePointer<Int8>(str4!.bytes), str5 == nil ? UnsafePointer<Int8>(nil) : UnsafePointer<Int8>(str5!.bytes), )

En caso de que alguien piense en simplemente pasar el resultado de los data.bytes . ¡ data.bytes a la persona que llama, esta es una muy mala idea! El puntero devuelto por data.bytes solo se garantiza que permanecerá válido mientras los data permanezcan activos y ARC matará los data tan pronto como sea posible. Entonces el siguiente no es un código válido:

// --- !!! BAD CODE, DO NOT USE !!! --- private func SwiftStringToData ( string: String? ) -> UnsafePointer<Int8>? { guard let str = string else { UnsafePointer<Int8>(nil) } let data = str.dataUsingEncoding(NSUTF8StringEncoding) return UnsafePointer<Int8>(data.bytes) }

No hay garantía de que los datos estén todavía vivos cuando este método retorna, ¡el puntero devuelto puede ser un puntero colgante! Entonces pensé en lo siguiente:

// --- !!! BAD CODE, DO NOT USE !!! --- private func DataToCString ( data: NSData? ) -> UnsafePointer<Int8>? { guard let d = data else { UnsafePointer<Int8>(nil) } return UnsafePointer<Int8>(d.bytes) } let str1 = SwiftStringToData(string1) let cstr1 = DataToCString(str1) // (*1) // .... let totalLength = calculateTotalLength(cstr1, /* ... */)

Pero eso tampoco está garantizado. El compilador ve que ya no se hace referencia a str1 cuando llega a (*1) , por lo que no puede mantenerlo vivo y cuando llegamos a la última línea, cstr1 ya es un puntero colgante.

Solo es seguro como se muestra en mi primera muestra, ya que allí los objetos NSData ( str1 etc.) deben mantenerse vivos para la llamada de función calculateTotalLength() y algunos métodos (como bytes de NSData o UTF8String de NSString ) se etiquetan para devolver un puntero interno, en cuyo caso el compilador se asegurará de que la vida útil del objeto se amplíe en el alcance actual, siempre y cuando el objeto o un puntero interno aún estén referenciados . Este mecanismo se asegura de que los punteros devueltos ( str1.bytes etc.) permanezcan válidos hasta que la llamada a la función C haya regresado. Sin ese etiquetado especial, ¡ni siquiera eso estaba garantizado! De lo contrario, el compilador puede liberar los objetos NSData directamente después de recuperar los punteros de bytes, pero antes de realizar la llamada a la función, ya que no tenía conocimiento de que la liberación del objeto de datos hace que los punteros cuelguen.


En Swift 2, la función C

size_t embeddedSize ( const char *_Nullable string );

está asignado a Swift como

func embeddedSize(string: UnsafePointer<Int8>) -> Int

y puede pasar una cadena Swift (no opcional) como argumento, como se documenta en Interacción con las API de C en la referencia "Uso de Swift con Cocoa y Objective-C":

Punteros constantes

Cuando una función se declara tomando un UnsafePointer<Type> , puede aceptar cualquiera de los siguientes:

  • ...
  • Un valor de String , si Type es Int8 o UInt8 . La cadena se convertirá automáticamente a UTF8 en un búfer y se pasará un puntero a ese búfer a la función.
  • ...

También puede pasar nil porque en Swift 2, nil es un valor permitido para UnsafePointer .

Como señaló @zneak, la "conversión automática" a UTF-8 no funciona para las cadenas opcionales en Swift 2, por lo que debe (condicionalmente) desenvolver la cadena:

let someString: String? = "Some String" let s2: size_t if let str = someString { s2 = embeddedSize(str) } else { s2 = embeddedSize(nil) }

¿Usando el método de map de Optional y el operador de nil coalescencia ?? , esto se puede escribir de forma más compacta

let someString: String? = "Some String" let s2 = someString.map { embeddedSize($0) } ?? embeddedSize(nil)

Una solución genérica fue sugerida por @zneak .

Aquí hay otra posible solución. String tiene un método

func withCString<Result>(@noescape f: UnsafePointer<Int8> throws -> Result) rethrows -> Result

que llama al cierre con un puntero a la representación UTF-8 de la cadena, extendida de por vida a través de la ejecución de f . Entonces, para una cadena no opcional, las siguientes dos declaraciones son equivalentes:

let s1 = embeddedSize("Test") let s1 = "Test".withCString { embeddedSize($0) }

Podemos definir un método similar para cadenas opcionales. Como las extensiones de tipos genéricos pueden restringir el marcador de posición de tipo solo a protocolos y no a tipos concretos, debemos definir un protocolo al que String ajuste:

protocol CStringConvertible { func withCString<Result>(@noescape f: UnsafePointer<Int8> throws -> Result) rethrows -> Result } extension String: CStringConvertible { } extension Optional where Wrapped: CStringConvertible { func withOptionalCString<Result>(@noescape f: UnsafePointer<Int8> -> Result) -> Result { if let string = self { return string.withCString(f) } else { return f(nil) } } }

Ahora la función C anterior puede invocarse con el argumento de cadena opcional como

let someString: String? = "Some String" let s2 = someString.withOptionalCString { embeddedSize($0) }

Para múltiples argumentos de cadena C, el cierre se puede anidar:

let string1: String? = "Hello" let string2: String? = "World" let result = string1.withOptionalCString { s1 in string2.withOptionalCString { s2 in calculateTotalLength(s1, s2) } }

Aparentemente, el problema ha sido resuelto en Swift 3. Aquí la función C está mapeada a

func embeddedSize(_ string: UnsafePointer<Int8>?) -> Int

y pasando una String? compila y funciona como se esperaba, tanto para argumentos nil como no nil .