In Swift I am using C API that returns struct with char array (containing UTF8 null terminated string or null).
struct TextStruct {
char * text;
//other data
}
I use:
let text: String = String(cString: data.text)
This works, however, when data.text is nullptr, this fails with
fatal error: unexpectedly found nil while unwrapping an Optional value
Is there any workaround, or I have to check data.text manually before using cString ctor?
In addition to Gwendal Roué's solution: You can
annotate the C API to indicate whether the pointer can be null or not.
For example,
struct TextStruct {
char * _Nullable text;
//other data
};
is imported to Swift as
public struct TextStruct {
public var text: UnsafeMutablePointer<Int8>?
// ...
}
where var text is a "strong" optional instead of an implicitly
unwrapped optional. Then
let text = String(cString: data.text)
// value of optional type 'UnsafeMutablePointer<Int8>?' not unwrapped; ...
no longer compiles, and forces you to use optional binding or
other unwrapping techniques, and the "fatal error: unexpectedly found nil"
cannot happen anymore accidentally.
For more information, see "Nullability and Objective-C" from the Swift Blog –
despite the title, it can be used with pure C as well.
Yes, you have to check data.text, in order to make sure it can feed the String(cString:) initializer, which is documented to require a non-null pointer.
A technique is to use a if let statement. This is a classic technique for safely unwrapping optional values:
let str: String?
if let ptr = ptr {
str = String(cString: ptr)
} else {
str = nil
}
print("got \(str ?? "nil")")
Another technique is the Optional.map function:
let str = ptr.map { String(cString: $0) }
print("got \(str ?? "nil")")
You can check if the address of the pointer is set
let textIsSet = Int(bitPattern: data.text) != 0
let text: String? = textIsSet ? String(cString: data.text) : nil
Related
If the defined array is empty, why the data type inside the array does not work
I create a empty Character Array, and I confused that why the result of a is Array<Bool> is true.
In fact, i tried a is Array<T> (T: Bool, String, Int...), all of them is true.
var a: Array<Character> = []
a is Array<Bool>
The result is:
true
After some digging, I found that this is the intended behavior.
Swift checks whether an array can be downcast by attempting to downcast all of the source elements to the destination element type. Since an empty array contains no elements, that means down casting it to another array with a different element type is always successful.
Below are swift's code for handling the as? operator. I doubt that swift handles the is operator differently.
public func _arrayConditionalCast<SourceElement, TargetElement>(
_ source: [SourceElement]
) -> [TargetElement]? {
var successfulCasts = ContiguousArray<TargetElement>()
successfulCasts.reserveCapacity(source.count)
for element in source {
if let casted = element as? TargetElement {
successfulCasts.append(casted)
} else {
return nil
}
}
return Array(successfulCasts)
}
https://github.com/apple/swift/blob/main/stdlib/public/core/ArrayCast.swift (line 74)
Sources
A similar bug report an swift.org: SR-7738
Some convincing explanation: SR-6192
I have the following C struct:
typedef struct {
char** categories;
int category_size;
} category_fmc_s_type;
My Swift array has the following values:
let categories = ["Weekday", "Weekend"]
I want to populate the C Struct field 'categories' with 'Weekday' & 'Weekend'. To do this I call my toPointer():
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
I keep getting the following XCode 8 error:
Cannot convert value of type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>>' to expected argument type 'UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>!'
Any suggestions? I don't understand why there is the optional and '!' in the C-Struct definition implicitly.
As the compiler emits as an error, you need to unwrap after Int8 w/ "?" as follows.
fileprivate func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
for (index, value) in args.enumerated() {
buffer[index] = UnsafeMutablePointer<Int8>(mutating: (value as NSString).utf8String!)
}
return buffer
}
then,
func testMyCat() {
let categories = ["Weekday", "Weekend"]
let buffer = toPointer(categories)
var mycat = category_fmc_s_type()
mycat.categories = buffer // you would see compile error w/o "?"
}
the code above works w/o error. Martin's solution gives a compile error at
mycat.categories = &cargs (see the link)
I don't know why.
Check the reference of utf8String property of NSString:
Discussion
This C string is a pointer to a structure inside the string object,
which may have a lifetime shorter than the string object and will
certainly not have a longer lifetime. Therefore, you should copy the
C string if it needs to be stored outside of the memory context in
which you use this property.
The term memory context is not well-defined, but one thing sure is that you cannot expect the allocated region for the C string would live forever. When the member categories in the category_fmc_s_type is accessed, the pointers may be pointing to the already freed regions.
Applying the suggestion from Martin R to your code, your code would be like this:
fileprivate static func toPointer(_ args: [String]) -> UnsafeMutablePointer<UnsafeMutablePointer<Int8>?> {
let buffer = UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>.allocate(capacity: args.count)
buffer.initialize(from: args.lazy.map{strdup($0)})
return buffer
}
And remember, after you finish using the category_fmc_s_type, you need to deallocate the regions allocated by strdup(_:) and UnsafeMutablePointer.allocate(capacity:):
fileprivate static func freePointer(_ pointers: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>, count: Int) {
for i in 0..<count {
free(pointers[i])
}
pointers.deinitialize(count: count)
pointers.deallocate(capacity: count)
}
i found this error in this line : if caching === nil on my code:
struct dataArray {
var dataById = Dictionary<String, Any>()
}
var id :String?
func dataBegin() {
let idString = id as String!
let byCategory = [dataArray().dataById[idString]]
if byCategory === nil { //error:'[Any?]' is not convertible to 'Anyobject?'
// some code
}
}
=== is the identity operator, which is supposed to be used for checking if two references point to the same instance. You probably wanted to use == to check for nil.
However, the byCategory variable is initialized as a non optional array, and as such it cannot be nil, and consequently it cannot be checked for nil.
I think there's a mistake in the logic of that function.
Creating a time base info struct in C is easy, but in Swift the following does not work in the playground:
let timebaseInfo: mach_timebase_info_data_t = mach_timebase_info(&timebaseInfo)
The error is Variable used within its own initial value
I understand the error, but I am unable to think of a way to do this without dropping down to C. Is there a Swift only way that I am missing? Any help would be greatly appreciated. :-)
Edit: Actually I see "A" problem with the above, the "=" doesn't make sense. But I did try the following also:
let timebaseInfo: mach_timebase_info_data_t
mach_timebase_info(&timebaseInfo)'
With an error stating timebasedInfo used before initialization. :-(
The mach_timebase_info function is declared as
typealias mach_timebase_info_t = UnsafeMutablePointer<mach_timebase_info>
// ...
func mach_timebase_info(info: mach_timebase_info_t) -> kern_return_t
which means that you can pass an (initialized) mach_timebase_info variable
as an "in-out expression" with &:
var timebaseInfo = mach_timebase_info(numer: 0, denom: 0)
let status = mach_timebase_info(&timebaseInfo)
if status == KERN_SUCCESS {
// ...
}
For more information, see Interacting with C APIs in the "Using Swift with Cocoa and Objective-C" manual:
Mutable Pointers
When a function is declared as taking an UnsafeMutablePointer<Type>
argument, it can accept any of the following:
nil, which is passed as a null pointer
An UnsafeMutablePointer<Type> value
An in-out expression whose operand is a stored lvalue of type Type,
which is passed as the address of the lvalue
An in-out [Type] value, which is passed as a pointer to the start of
the array, and lifetime-extended for the duration of the call
You'll need to use UnsafeMutablePointer<Type>, as follows:
let p = UnsafeMutablePointer<mach_timebase_info_data_t>.alloc(1)
mach_timebase_info(&p.memory)
swift 5.5:
let timebaseInfo: mach_timebase_info_data_t = {
let pointer = UnsafeMutablePointer<mach_timebase_info_data_t>.allocate(capacity: 1)
precondition(mach_timebase_info(pointer) == KERN_SUCCESS)
let info = pointer.pointee
defer { pointer.deallocate() }
return info
}()
func convertToNanoseconds(_ value: UInt64) -> UInt64 {
return (value * UInt64(timebaseInfo.numer)) / UInt64(timebaseInfo.denom)
}
I have a Swift function that accepts Any and I want it to be able to accept an array of Strings, an array of Ints, a mixed array, or an array of arrays, etc. It also can accept just a String or an Int, etc, not in an array.
So I have this:
private func parse(parameter: Any) {
if parameter is Int {
// Int
} else if (parameter is Float) || (parameter is Double) {
// Double
} else if parameter is String {
// String
} else if parameter is Bool {
// Bool
} else if let array = parameter as? [Any] {
// Should catch all Arrays
} else {
assert(false, "Unsupported type") // [String] ends up here
}
}
But if I call parse(["Strings"]), the assert is raised. How can I catch all types of Arrays?
edit - there was some confusion as to what I'm trying to accomplish. I basically need to return a String based on the type, so Int -> "" and String -> "", so an array would make recursive calls to return "..."
This post is marked as a duplicate, but that other question is about Javascript, not Swift.
I finally found the way to do that, which is to use NSArray for casting.
private func parse(x: Any) {
if let o = x as? [Any] {
println("[Any]")
}
if let o = x as? [AnyObject] {
println("[AnyObject]")
}
if let o = x as? NSArray {
println("NSArray")
}
}
let a: [Any] = ["bar"]
let b: [AnyObject] = ["bar"]
let c = ["foo", 3.14]
parse(a) // ==> [Any]
parse(b) // ==> [AnyObject], and also NSArray
parse(c) // ==> NSArray
It look so that an array containing values of Any internally represented in NSArray.
(But should it be able to cast c to [Any]...? I'm suspecting it's a bug.)
The key to understanding typing and type related issues in Swift is that all roads lead to protocols.
The challenge of this problem is detecting any type of array, not just one concrete type. The OP's example failed because [Any] is not a base class or a generalized pattern of [String], that is to say, that (from what I can tell), in Swift [T] is not covariant on T. Beyond that, you cannot check for SequenceType or CollectionType since they have associated types (Generator.Element).
The idiomatic solution is thus to use a marker protocol to indicate which types you want to match your criteria. As illustrated below, you achieve this by creating an empty protocol, and associating it with the types of interest.
import Foundation
protocol NestedType {}
extension Array: NestedType {}
extension Set: NestedType {}
extension Dictionary: NestedType {}
extension NSSet: NestedType {}
protocol AnyTypeOfArray {}
extension Array: AnyTypeOfArray {}
extension NSArray: AnyTypeOfArray {}
protocol AnyTypeOfDictionary {}
extension Dictionary: AnyTypeOfDictionary {}
func printType(v:Any) {
if v is NestedType {
print("Detected a nested type")
}
if v is AnyTypeOfArray {
print("\t which is an array")
}
else if v is AnyTypeOfDictionary {
print("\t which is a dictionary")
}
}
printType([String:Int]())
printType([Int]())
printType(NSArray())
The output of which is:
Detected a nested type
which is a dictionary
Detected a nested type
which is an array
Detected a nested type
which is an array
One way you can do this is to separate the function out to two separate implementations (with the same name), one that takes anArray and one for everything else. You'll also need to make them generic functions instead of using the Any type. With that setup, Swift can use type inference to figure out the best function to call.
I'd implement it something like this (I'm just printlning the type to show where things end up):
func parse<T>(parameter: T) {
if parameter is Int {
println("Int")
} else if (parameter is Float) || (parameter is Double) {
println("Double")
} else if parameter is String {
println("String")
} else if parameter is Bool {
println("Bool")
} else {
assert(false, "Unsupported type")
}
}
func parse<T>(parameter: Array<T>) {
println("Array")
for element in parameter {
// Recursively parsing...
parse(element)
}
}
Then calling it like this:
parse(1) // Int
parse(0.1) // Double
parse("asdf") // String
parse(true) // Bool
parse(["asdf", "asdf"]) // Array -> String String
Outputs:
Int
Double
String
Bool
Array
String
String
You can use the _stdlib_getTypeName that returns the mangled type name for the given value.
For example:
var myString = "String"
var myInteger = 10
var myArray = [10,22]
var myDictionary = ["one": 1, "two": 2, "three": 3]
println("\(_stdlib_getTypeName(myString))")
println("\(_stdlib_getTypeName(myInteger))")
println("\(_stdlib_getTypeName(myArray))")
println("\(_stdlib_getTypeName(myDictionary))")
The result will be:
_TtSS // for String
_TtSi // for integer
_TtSa // for array
_TtVSs10Dictionary // for dictionary