I have a C function that deals with C strings. The function actually allows strings to be NULL pointers. The declaration is like follows:
size_t embeddedSize ( const char *_Nullable string );
It is no problem to use this function like this in C:
size_t s1 = embeddedSize("Test");
size_t s2 = embeddedSize(NULL); // s2 will be 0
Now I'm trying to use it from Swift. The following code works
let s1 = embeddedSize("Test")
let s2 = embeddedSize(nil) // s2 will be 0
But what doesn't work is passing an optional string to it! This code will not compile:
let someString: String? = "Some String"
let s2 = embeddedSize(someString)
The compiler throws an error about the optional not being unwrapped and Xcode asks me if I maybe forgot to add !
or ?
. However, why would I want to unwrap it? NULL
or nil
are valid values to be fed to the function. See above, I just passed nil
to it directly and that compiled just well and returned the expected result. In my real code the string is fed from external and it is optional, I cannot force unwrap it, that will break if the string was nil
. So how can I call that function with an optional string?
In Swift 2, the C function
is mapped to Swift as
and you can pass a (non-optional) Swift string as the argument, as documented in Interacting with C APIs in the "Using Swift with Cocoa and Objective-C" reference:
You can also pass
nil
because in Swift 2,nil
is an allowed value forUnsafePointer
.As @zneak pointed out, the "automatic conversion" to UTF-8 does not work for optional strings in Swift 2, so you have to (conditionally) unwrap the string:
Using the
map
method ofOptional
and the nil-coalescing operator??
, this can be written more compactly asOne generic solution was suggested by @zneak.
Here is another possible solution.
String
has a methodwhich calls the closure with a pointer to the UTF-8 representation of the string, life-extended through the execution of
f
. So for a non-optional string, the following two statements are equivalent:We can define a similar method for optional strings. Since extensions of generic types can restrict the type placeholder only to protocols and not to concrete types, we have to define a protocol that
String
conforms to:Now the above C function can be called with the optional string argument as
For multiple C string arguments, the closure can be nested:
Apparently, the problem has been solved in Swift 3. Here the C function is mapped to
and passing a
String?
compiles and works as expected, both fornil
and non-nil
arguments.All the solutions that indirect the call by extra swift level, work fine if you only have one parameter. But I also have C functions like this (
strX
are not the real parameter names, the call is actually simplified):And here this indirection becomes impractical, as I need one indirection per argument, 5 indirections for the function above.
Here's the best (ugly) "hack" I came up with so far, that avoids this problem (I'm still happy to see any better solutions, maybe someone gets an idea seeing that code):
In case someone thinks about just passing the result of
data.bytes
back to the caller, this is a very bad idea! The pointer returned bydata.bytes
is only guaranteed to stay valid as long asdata
itself stays alive and ARC will killdata
as soon as it can. So the following is not valid code:There is no guarantee that data is still alive when this method returns, the returned pointer might be a dangling pointer! Then I thought about the following:
But that is not guaranteed to be safe either. The compiler sees that
str1
is not referenced anymore when it gets to(*1)
, so it may not keep it alive and when we reach the last line,cstr1
is already a dangling pointer.It is only safe as shown in my first sample, since there the
NSData
objects (str1
etc.) must be kept alive to thecalculateTotalLength()
function call and some methods (likebytes
ofNSData
orUTF8String
ofNSString
) are tagged to return an internal pointer, in which case the compiler will make sure the lifetime of the object is extended in the current scope as long as the object or such an internal pointer is still referenced. This mechanism makes sure, that the returned pointers (str1.bytes
etc.) will definitely stay valid until the C function call has returned. Without that special tagging, not even that was guaranteed! The compiler may otherwise release theNSData
objects directly after retrieving the byte pointers but prior to making the function call, as it had no knowledge that releasing the data object makes the pointers dangling.The most likely answer is that while string literals are convertible to
UnsafePointer<CChar>
, andnil
is convertible toUnsafePointer<CChar>
, andString
also is,String?
might not be in Swift 2.