How to handle future enum values in Dart 2.17+ - dart

My app is connected to a custom made peripheral and draws some values from it as ints which I want to convert to enums to streamline the app code.
As the hardware evolves overtime, it is of known fact that new values will come.
In my Flutter app, I handle known values (by the time of app writing) through enum extensions, providing an 'unknown' value as follows:
// Battery live data originator protocol
enum BatteryLiveDataProtocol {
unknown, // 0
rs485, // 1
ttl, // 2
canbus, // 3
uart, //4
;
}
extension BatteryLiveDataProtocolExtension on BatteryLiveDataProtocol {
/// Build the enum out of the int number
static BatteryLiveDataProtocol fromNumber(int number) {
switch (number) {
case 1:
return ttl;
case 2:
return rs485;
case 3:
return canbus;
case 0:
default:
return unknown;
}
}
/// Returns the number correpsonding to the enum
int get number {
switch (this) {
case rs485:
return 1;
case ttl:
return 2;
case canbus:
return 3;
case unknown:
default:
return 0;
}
}
As you can see, this code support future version in a quite handily:
const futureValue = 13;
final p = BatteryLiveDataProtocol.fromNumber(futureValue);
Since the introduction of Dart 2.17 (& Flutter 3) I'd like to take advantage of the enhanced enums as follows:
// Battery live data originato protocol
enum BatteryLiveDataProtocol {
unknown(),
rs485(1),
ttl(2),
canbus(3),
uart(4),
;
const BatteryLiveDataProtocol(this.value);
final num value;
}
But then how can I handle future enum values?
I could do that, everywhere I want to convert the int to the enum:
BatteryLiveDataProtocol p;
try {
const futureValue = 13;
p = BatteryLiveDataProtocol(futureValue);
} catch(e) {
p = BatteryLiveDataProtocol.unknown;
}
But this is cumbersome, isn't it? It'll be cool to have the possibility to provide the body of const BatteryLiveDataProtocol(this.value); ourselves.
[UPDATE] Btw, not sure it was clear enough, but it is still possible to create a static function within the enhanced enum as follows:
static BatteryLiveDataProtocol fromValue(int v) {
BatteryLiveDataProtocol p;
try {
p = BatteryLiveDataProtocol[v];
} catch (e) {
p = unknown;
}
return p;
}
And use it as follows:
const futureValue = 13;
final p = BatteryLiveDataProtocol.fromValue(futureValue);
// Makes p being set to "unknown".
So my question #1: is there a better/clever (aka handy) way to handle future values?
Question #2: Others language handle future enum (in Swift for example): is there a way to generate unknown(actualValue) as a dynamic enum value?

So my question #1: is there a better/clever (aka handy) way to handle future values?
I think you can improve your enum by moving the definition of fromNumber and number into the enum itself rather than as an extension.
Additionally I think you can improve the implementations of both fromNumber and number.
For number, enums already have a getter called index which returns the same value as what you want, so you can simply return the index in your number method.
For fromNumber we can turn it into a factory constructor, and since all enums have a static values list (that contains all enum values in the specific order they are defined), we can get the value we want by indexing into the values list. And for any invalid index we would return unknown.
The benefit of these changes to number and fromNumber is that you will no longer need to update these methods when you add another enum value.
The updated enum is as follows:
enum BatteryLiveDataProtocol {
unknown, // 0
rs485, // 1
ttl, // 2
canbus, // 3
uart, //4
;
factory BatteryLiveDataProtocol.fromNumber(int number) =>
number >= values.length || number < 0 ? unknown : values[number];
int get number => index;
}
Question #2: Others language handle future enum (in Swift for example): is there a way to generate unknown(actualValue) as a dynamic enum value?
No, dart's enhanced enums do not work like swift enums, enums in dart are closer to java enums. Swift calls this feature associated values, but dart enums do not have this feature.

Thanks to #mmcdon20 for the factory proposal (and btw, I couldn't replace static by factory in the code below).
But one want to make sure one handle any values: values and index may be different as in the modified example I propose below where the value of unknown is now set to -1 (index is 0).
So the code has to take care of it:
/// Battery live data originato protocol
enum BatteryLiveDataProtocol {
unknown(-1),
rs485(1),
ttl(2),
canbus(3),
uart(4),
;
const BatteryLiveDataProtocol(this.value);
final num value;
factory BatteryLiveDataProtocol.fromValue(int v) =>
values.firstWhere((x) => x.value == v, orElse: () => unknown);
}

Related

How to print value of an enum? [duplicate]

Before enums were available in Dart I wrote some cumbersome and hard to maintain code to simulate enums and now want to simplify it. I need to get the name of the enum as a string such as can be done with Java but cannot.
For instance little test code snippet returns 'day.MONDAY' in each case when what I want is 'MONDAY"
enum day {MONDAY, TUESDAY}
print( 'Today is $day.MONDAY');
print( 'Today is $day.MONDAY.toString()');
Am I correct that to get just 'MONDAY' I will need to parse the string?
Dart 2.7 comes with new feature called Extension methods. Now you can write your own methods for Enum as simple as that!
enum Day { monday, tuesday }
extension ParseToString on Day {
String toShortString() {
return this.toString().split('.').last;
}
}
main() {
Day monday = Day.monday;
print(monday.toShortString()); //prints 'monday'
}
Bit shorter:
String day = theDay.toString().split('.').last;
Update Dart 2.15:
enum Day {
monday,
tuesday,
}
You can use name property on the enum.
String monday = Day.monday.name; // 'monday'
Old solution:
1. Direct way:
var dayInString = describeEnum(Day.monday);
print(dayInString); // prints 'monday'
2. Using Extension:
extension DayEx on Day {
String get name => describeEnum(this);
}
You can use it like:
void main() {
Day monday = Day.monday;
print(monday.name); // 'monday'
}
It used to be correct that the only way to get the source name
of the enum value was through the toString method which returns "day.MONDAY", and not the more useful "MONDAY".
Since Dart 2.15, enums have exposed a extension getter which returns just the source name, so day.MONDAY.name == "MONDAY".
Since Dart 2.17, you can also add your own members to enum declarations, and choose to provide another name for
a value than justits source name, e.g., with a more appropriate capitalization:
enum Day {
monday("Monday"),
tuesday("Tuesday"),
// ...
saturday("Saturday");
sunday("Sunday");
final String name;
Day(this.name);
// And other members too.
bool get isWorkday => index < saturday.index;
}
Then you get Day.sunday.name == "Sunday" (hiding the extension name getter which would return "sunday").
Before these features, you could only get the name from the toString string, extracting the rest of the string as:
day theDay = day.MONDAY;
print(theDay.toString().substring(theDay.toString().indexOf('.') + 1));
which was admittedly hardly convenient.
Another way to get the enum name as a string, one which is shorter, but also less efficient because it creates an unnecessary string for first part of the string too, was:
theDay.toString().split('.').last
If performance doesn't matter, that's probably what I'd write, just for brevity.
If you want to iterate all the values, you can do it using day.values:
for (day theDay in day.values) {
print(theDay);
}
Simplest way to get the name of an enum is a standard method from the flutter/foundation.dart
describeEnum(enumObject)
enum Day {
monday, tuesday, wednesday, thursday, friday, saturday, sunday
}
void validateDescribeEnum() {
assert(Day.monday.toString() == 'Day.monday');
assert(describeEnum(Day.monday) == 'monday');
}
enum day {MONDAY, TUESDAY}
print( 'Today is ${describeEnum(day.MONDAY)}' );
console output: Today is MONDAY
There is a more elegant solution:
enum SomeStatus {
element1,
element2,
element3,
element4,
}
const Map<SomeStatus, String> SomeStatusName = {
SomeStatus.element1: "Element 1",
SomeStatus.element2: "Element 2",
SomeStatus.element3: "Element 3",
SomeStatus.element4: "Element 4",
};
print(SomeStatusName[SomeStatus.element2]) // prints: "Element 2"
Sometimes I need to separate ui-value and real-value, so I defined keys and values using Map. This way, we have more flexiblity. And by using extension (since Dart 2.7), I made a method to read its key and value.
enum Status {
progess,
done,
}
extension StatusExt on Status {
static const Map<Status, String> keys = {
Status.progess: 'progess',
Status.done: 'done',
};
static const Map<Status, String> values = {
Status.progess: 'In Progress',
Status.done: 'Well done',
};
String get key => keys[this];
String get value => values[this];
// NEW
static Status fromRaw(String raw) => keys.entries
.firstWhere((e) => e.value == raw, orElse: () => null)
?.key;
}
// usage 1
Status status = Status.done;
String statusKey = status.key; // done
String statusValue = status.value; // Well done
// usage 2 (easy to make key and value list)
List<Status> statuses = Status.values;
List<String> statusKeys = statuses.map((e) => e.key).toList();
List<String> statusValues = statuses.map((e) => e.value).toList();
// usage 3. create Status enum from string.
Status done1 = StatusExt.fromRaw('done') // Status.done
Status done2 = StatusExt.fromRaw('dude') // null
With Dart 2.17 we now have general support for members on enums. That means we can add fields holding state, constructors that set that state, methods with functionality, and even override existing members.
Example:
enum Day {
MONDAY("Monday"),
TUESDAY("Tuesday");
const Day(this.text);
final String text;
}
Output:
void main() {
const day = Day.MONDAY;
print(day.text); /// Monday
}
For above functionality override dart version like below which target 2.17 and greater
environment:
sdk: ">=2.17.0 <3.0.0"
I got so over this I made a package:
https://pub.dev/packages/enum_to_string
Also has a handy function that takes enum.ValueOne and parses it to "Value one"
Its a simple little library but its unit tested and I welcome any additions for edge cases.
I use the functions below to get the name of the enum value and, vise versa, the enum value by the name:
String enumValueToString(Object o) => o.toString().split('.').last;
T enumValueFromString<T>(String key, Iterable<T> values) => values.firstWhere(
(v) => v != null && key == enumValueToString(v),
orElse: () => null,
);
When using Dart 2.7 and newer, extension methods would work here (as well as for any other Objects):
extension EnumX on Object {
String asString() => toString().split('.').last;
}
The implementation above is not dependant on the specific enums.
Usage examples:
enum Fruits {avocado, banana, orange}
...
final banana = enumValueFromString('banana', Fruits.values);
print(enumValueToString(banana)); // prints: "banana"
print(banana.asString()); // prints: "banana"
Edit from 2020-04-05: Added nullability checks. values parameter could be Iterable, not necessarily List. Added extensions method implementation. Removed <Fruits> annotation from the example to show that the class name duplication is not required.
I use structure like below:
abstract class Strings {
static const angry = "Dammit!";
static const happy = "Yay!";
static const sad = "QQ";
}
Dart 2.15 includes an extension to make this easy:
enum day {MONDAY, TUESDAY}
print( 'Today is ${day.MONDAY.name}');
Until the changes in https://github.com/dart-lang/sdk/commit/18f37dd8f3db6486f785b2c42a48dfa82de0948b are rolled out to a stable version of Dart, the other clever but more complex answers here are very useful.
One more way:
enum Length {
TEN,
TWENTY,
THIRTY,
NONE,
}
extension LengthValue on Length {
static const _values = [10, 20, 30, 0];
int get value => _values[this.index];
}
since dart 2.15 just use ".name"
enum day {monday, tuesday}
print( 'Today is ${day.monday.name}');
My approach is not fundamentally different, but might be slightly more convenient in some cases:
enum Day {
monday,
tuesday,
}
String dayToString(Day d) {
return '$d'.split('.').last;
}
In Dart, you cannot customize an enum's toString method, so I think this helper function workaround is necessary and it's one of the best approaches. If you wanted to be more correct in this case, you could make the first letter of the returned string uppercase.
You could also add a dayFromString function
Day dayFromString(String s) {
return Day.values.firstWhere((v) => dayToString(v) == s);
}
Example usage:
void main() {
Day today = Day.monday;
print('Today is: ${dayToString(today)}');
Day tomorrow = dayFromString("tuesday");
print(tomorrow is Day);
}
enum day {MONDAY, TUESDAY}
print(day.toString().split('.')[1]);
OR
print(day.toString().split('.').last);
Create a class to help:
class Enum {
Enum._();
static String name(value) {
return value.toString().split('.').last;
}
}
and call:
Enum.name(myEnumValue);
One of the good ways I found in the answer is
String day = theDay.toString().split('.').last;
But I would not suggest doing this as dart provide us a better way.
Define an extension for the enum, may be in the same file as:
enum Day {
monday, tuesday, wednesday, thursday, friday, saturday, sunday
}
extension DayExtension on Day {
String get value => describeEnum(this);
}
You need to do import 'package:flutter/foundation.dart'; for this.
I had the same problem in one of my projects and existing solutions were not very clean and it didn't support advanced features like json serialization/deserialization.
Flutter natively doesn't currently support enum with values, however, I managed to develop a helper package Vnum using class and reflectors implementation to overcome this issue.
Address to the repository:
https://github.com/AmirKamali/Flutter_Vnum
To answer your problem using Vnum, you could implement your code as below:
#VnumDefinition
class Visibility extends Vnum<String> {
static const VISIBLE = const Visibility.define("VISIBLE");
static const COLLAPSED = const Visibility.define("COLLAPSED");
static const HIDDEN = const Visibility.define("HIDDEN");
const Visibility.define(String fromValue) : super.define(fromValue);
factory Visibility(String value) => Vnum.fromValue(value,Visibility);
}
You can use it like :
var visibility = Visibility('COLLAPSED');
print(visibility.value);
There's more documentation in the github repo, hope it helps you out.
Instead of defining extension for every enum, we can define extension on object and get access to .enumValue from any enum.
void main() {
// ❌ Without Extension ❌
print(Countries.Cote_d_Ivoire.toString().split('.').last.replaceAll("_", " ")); // Cote d Ivoire
print(Movies.Romance.toString().split('.').last.replaceAll("_", " ")); //Romance
// ✅ With Extension ✅
print(Countries.Cote_d_Ivoire.enumValue); // Cote d Ivoire
print(Movies.Romance.enumValue); //Romance
}
enum Countries { United_States, United_Kingdom, Germany, Japan, Cote_d_Ivoire }
enum Movies { Romance, Science_Fiction, Romantic_Comedy, Martial_arts }
extension PrettyEnum on Object {
String get enumValue => this.toString().split('.').last.replaceAll("_", " ");
}
With this, you can even define multi-word enum where words are separated by _(underscore) in its name.
As from Dart 2.15, you can get enum value from
print(MyEnum.one.name);
// and for getting enum from value you use
print(MyEnum.values.byName('two');
try this solution:
extension EnumValueToString on Enum {
String valueAsString() {
return describeEnum(this);
}
}
how to used it:
enum.valueAsString()
dart 2.15 is now supporting this
you can just type
print(day.MONDAY.name); //gives you: MONDAY
Since Dart 2.15, we can just do Day.monday.name, where
enum Day { monday, tuesday }
As from Dart version 2.15, you can access the String value of an enum constant using .name:
enum day {MONDAY, TUESDAY}
void main() {
print('Today is ${day.MONDAY.name}');
// Outputs: Today is MONDAY
}
You can read in detail about all the enum improvements in the official Dart 2.15 release blog post.
as of dart 2.15 you can use .name to get the name of enum elements.
enum day {MONDAY, TUESDAY}
print(day.MONDAY.name); // prints MONDAY
Dart version 2.15 has introduced name property on enums.
Example
void main() {
MyEnum.values.forEach((e) => print(e.name));
}
enum MyEnum { value1, Value2, VALUE2 }
Output:
value1
Value2
VALUE2
For those who require enum with values use this approach as Dart doesn't support this:
class MyEnumClass {
static String get KEY_1 => 'value 1';
static String get KEY_2 => 'value 2';
static String get KEY_3 => 'value 3';
...
}
// Usage:
print(MyEnumClass.KEY_1); // value 1
print(MyEnumClass.KEY_2); // value 2
print(MyEnumClass.KEY_3); // value 3
...
And sure you may put whatever types you need.
now with null safety it looks like this
String enumToString(Object? o) => o != null ? o.toString().split('.').last : '';
T? enumFromString<T>(String key, List<T> values) {
try {
return values.firstWhere((v) => key == enumToString(v));
} catch(e) {
return null;
}
}

How to return two specific types from a generic method?

T getValue<T>(int i) {
if (T == String) return '$i'; // Error
return i; // Error
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}
I want getValue to return string if T is String and int otherwise. How to do that?
You can't restrict the type parameter to just int or String, so it will have to accept more than that (at least their least common supertype, Object, so basically any type).
It's not a particularly helpful way to code. It's possible, but not recommended:
T getValue<T>(int i) {
if (i is T) return i;
return "$i" as T;
}
This will return the int if T allows it (so T being any of int, or a super type of int, which is num, Object, dynamic or void, or any number of Comparable<X> wrappings around any any of those supertypes), and otherwise try to return a string. That will fail with a type error unless T is String (since we've already ruled out all supertypes of String).
You can still call it as getValue<bool>(42) and watch it fail, so the type argument doesn't help with correctness.
It's not particularly effective. I'd rather do:
dynamic getValue(int i, {bool toString = false}) {
if (toString) return "$i";
return i;
}
and call it as:
String x = getValue(42, toString: true); // Add `as String` if you disable downcasts.
int y = getValue(42); // And `as int` here.
The type parameter is really just making things harder. You are going to cast or type-check the result anyway, so might as well do it at the call point, rather than introduce type variables that aren't actually preventing misuse anyway.
(I'd probably just do two different functions, but I assume that there is a reason for wanting one function).
As I mentioned in the comments, I don't see any way that you could use your generic as the return type of your getValue function. Even assuming the return under the if statement worked, there is nothing that can be done about trying to return int i when List is passed as the type. You'll be trying to return an int as a List.
If you change it to dynamic, your code will work fine as it's just using the generic as another parameter.
dynamic getValue<T>(int i) {
if (T == String) return '$i';
return i;
}
void main() {
var s = getValue<String>(1);
var i = getValue<int>(1);
}

Enum: Count and list all cases (from outside!)

I've got this enum (Xcode 10/Swift 5) in its own file ("MyEnum.swift"):
enum MyEnum: Int, CaseIterable {
case A = 0
case B = 1
case C = 2
case D = 3
case E = 4
//Each case needs its own number and description!
var description: String {
switch self {
case .A:
return "abc"
case .B:
return "bcd"
case .C:
return "cde"
case .D:
return "def"
case .E:
return "efg"
}
}
}
... and want to add the descriptions to a PickerView. I know how to set up the functions for the view but I'm now stuck on counting the enum cases and adding the descriptions.
According to the documentation and different questions, adding CaseIterable is supposed to make it possible to call
MyEnum.allCases.count
... but I can only access allCases from within the enum file. From outside (so from my ViewController class) I can only call MyEnum.AllCases, which hasn't got count and, to be honest, I'm not even sure what AllCases returns exactly (it's not a normal array you can use count on).
Adding this code (source) to the enum file at least makes it possible to count the cases:
static let count: Int = {
var max: Int = 0
while MyEnum(rawValue: max) != .none { max += 1 }
return max
}()
...but isn't there supposed to be an easier way to do this with Swift 4.2+?
How do I get a list of the case descriptions to fill my PickerView with (so "abc", "bcd",... - preferably without hardcoding it)?
I tried in 1 file:
enum TestEnum: CaseIterable {
case test1
case test2
}
in another file of another class I wrote:
let count = TestEnum.allCases.count
And it works, but I noticed that when I was typing "allCases" wasn't shown
I manually needed to write it
allCases is declared public, so you should be able to access it from another file:
/// A collection of all values of this type.
public static var allCases: Self.AllCases { get }
like this:
let descriptions = MyEnum.allCases.map { $0.description }
Try cleaning the project and rebuild.
Also, if something is not in Xcode's autocomplete list, it doesn't mean that you can't access that thing. Xcode's complete list has a ton of bugs according to my own experience.
For the sake of completeness and in case allCases doesn't work at all (even after a restart) add this to the enum file:
static let allValues = [MyEnum.A, MyEnum.B, MyEnum.C, MyEnum.D, MyEnum.E]
It's hardcoded but at least it gives you access to everything:
count: MyEnum.allValues.count
description (e.g. "abc"): MyEnum.allValues[0].description
rawValue (e.g. 0):MyEnum.allValues[0].rawValue
The same issue happened to me. What ended up working is restarting xcode.

How to check if value is enum?

I would like to check if a value in dart is an Enum, and if so, serialize it automatically.
My sticking point is how to test if a value is an enum.
This is how I am testing for other types
if (T == int)
val = prefs.getInt(this._key);
else if (T == double)
val = prefs.getDouble(this._key);
else if (T == bool)
val = prefs.getBool(this._key);
else if (T == String)
val = prefs.getString(this._key);
else if ([""] is T)
val = prefs.getStringList(this._key);
but I cannot seem to compare T to an enum
Since all enums have the same methods I was hoping to sense them and handle them accordingly
As of late 2021, all enums extend Enum
You can now finally use is Enum directly:
enum MyEnum {one, two, three}
var x = "a";
var y = MyEnum.one;
print(x is Enum); // false
print(y is Enum); // true
Which also means you can now create extensions for enums:
extension EnumExtension on Enum { ... }
If you need to check is dynamic value enum type. You can use the next approach. The main idea is quite similar to #Oniya Daniel's answer.
enum Fruit {
banana,
apple,
orange,
}
Next method to check isEnum condition
bool isEnum(dynamic data) {
final split = data.toString().split('.');
return split.length > 1 && split[0] == data.runtimeType.toString();
}
Test result below
test('check enum runtime', () {
expect(isEnum(Fruit.banana), true);
expect(isEnum(null), false);
expect(isEnum(''), false);
expect(isEnum('banana'), false);
});
P.S.: to get enum String value good to use describeEnum(<enum_value>) from the package:flutter/foundation.dart
I don't have enough reputation to comment, or I would have added a statement to the answer by #Dmitry Puzak. In Dmitry's method, if data is passed as the String "String.3.0.1", for instance, the method will incorrectly identify the parameter as an enum. The flutter-provided method, describeEnum, attempts to check that it's parameter is an enum and throws an exception if it's not, but in my experience the exception isn't always thrown when the parameter to describeEnum is a String value containing dots.
For a project I'm working, I modified Dmitry's isEnum method to the following, adding the first if block inside the method:
bool isEnum(dynamic data) {
if (data.runtimeType == "".runtimeType) {
return false;
}
final split = data.toString().split('.');
return split.length > 1 && split[0] == data.runtimeType.toString();
}
This makes sure to return false for any String parameter.
I can't offhand think of any other instance where the runtime type of a variable could possibly match the first part of the split done on the toString() return value.
Convert the enum to a list and check if the list contains the value you intend to check.
enum ObjectType {
TypeA, TypeB
}
// checking against enum values
print(ObjectType.values.contains(ObjectType.TypeA)); // true
print(ObjectType.values.contains(ObjectType.TypeB)); // true
// checking against string value
print(ObjectType.values.contains("ObjectType.TypeA")); // false
// checking against boolean
print(ObjectType.values.contains(false)); // false
// checking against int
print(ObjectType.values.contains(234)); // false
// checking against double
print(ObjectType.values.contains(2.345)); // false

Swift String from imported unsigned char 2D array

I am using a 3rd party C library in my iOS application, which I am in the process of converting from Objective-C to Swift. I hit an obstacle when attempting to read one of the structs returned by the C library in Swift.
The struct looks similar to this:
typedef unsigned int LibUint;
typedef unsigned char LibUint8;
typedef struct RequestConfiguration_ {
LibUint8 names[30][128];
LibUint numberNames;
LibUint currentName;
} RequestConfiguration;
Which is imported into Swift as a Tuple containing 30 Tuples of 128 LibUint8 values. After a long time of trial and error using nested withUnsafePointer calls, I eventually began searching for solutions to iterating a Tuple in Swift.
What I ended up using is the following functions:
/**
* Perform iterator on every children of the type using reflection
*/
func iterateChildren<T>(reflectable: T, #noescape iterator: (String?, Any) -> Void) {
let mirror = Mirror(reflecting: reflectable)
for i in mirror.children {
iterator(i.label, i.value)
}
}
/**
* Returns a String containing the characters within the Tuple
*/
func libUint8TupleToString<T>(tuple: T) -> String {
var result = [CChar]()
let mirror = Mirror(reflecting: tuple)
for child in mirror.children {
let char = CChar(child.value as! LibUint8)
result.append(char)
// Null reached, skip the rest.
if char == 0 {
break;
}
}
// Always null terminate; faster than checking if last is null.
result.append(CChar(0))
return String.fromCString(result) ?? ""
}
/**
* Returns an array of Strings by decoding characters within the Tuple
*/
func libUint8StringsInTuple<T>(tuple: T, length: Int = 0) -> [String] {
var idx = 0
var strings = [String]()
iterateChildren(tuple) { (label, value) in
guard length > 0 && idx < length else { return }
let str = libUint8TupleToString(value)
strings.append(str)
idx++
}
return strings
}
Usage
func handleConfiguration(config: RequestConfiguration) {
// Declaration types are added for clarity
let names: [String] = libUint8StringsInTuple(config.names, config.numberNames)
let currentName: String = names[config.currentName]
}
My solution uses reflection to iterate the first Tuple, and reflection to iterate the second, because I was getting incorrect strings when using withUnsafePointer for the nested Tuples, which I assume is due to signage. Surely there must be a way to read the C strings in the array, using an UnsafePointer alike withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) }.
To be clear, I'm looking for the fastest way to read these C strings in Swift, even if that involves using Reflection.
Here is a possible solution:
func handleConfiguration(var config: RequestConfiguration) {
let numStrings = Int(config.numberNames)
let lenStrings = sizeofValue(config.names.0)
let names = (0 ..< numStrings).map { idx in
withUnsafePointer(&config.names) {
String.fromCString(UnsafePointer<CChar>($0) + idx * lenStrings) ?? ""
}
}
let currentName = names[Int(config.currentName)]
print(names, currentName)
}
It uses the fact that
LibUint8 names[30][128];
are 30*128 contiguous bytes in memory. withUnsafePointer(&config.names)
calls the closure with $0 as a pointer to the start of that
memory location, and
UnsafePointer<CChar>($0) + idx * lenStrings
is a pointer to the start of the idx-th subarray. The above code requires
that each subarray contains a NUL-terminated UTF-8 string.
The solution suggested by Martin R looks good to me and, as far as I can see from my limited testing, does work. However, as Martin pointed out, it requires that the strings be NUL-terminated UTF-8. Here are two more possible approaches. These follow the principle of handling the complexity of C data structures in C instead of dealing with it in Swift. Which of these approaches you choose depends on what specifically you are doing with RequestConfiguration in your app. If you are not comfortable programming in C, then a pure Swift approach, like the one suggested by Martin, might be a better choice.
For the purposes of this discussion, we will assume that the 3rd party C library has the following function for retrieving RequestConfiguration:
const RequestConfiguration * getConfig();
Approach 1: Make the RequestConfiguration object available to your Swift code, but extract names from it using the following C helper function:
const unsigned char * getNameFromConfig(const RequestConfiguration * rc, unsigned int nameIdx)
{
return rc->names[nameIdx];
}
Both this function's signature and the RequestConfiguration type must be available to the Swift code via the bridging header. You can then do something like this in Swift:
var cfg : UnsafePointer<RequestConfiguration> = getConfig()
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig(cfg, cfg.memory.currentName)))
{
print(s)
}
This approach is nice if you need the RequestConfiguration object available to Swift in order to check the number of names in multiple places, for example.
Approach 2: You just need to be able to get the name at a given position. In this case the RequestConfiguration type does not even need to be visible to Swift. You can write a helper C function like this:
const unsigned char * getNameFromConfig1(unsigned int idx)
{
const RequestConfiguration * p = getConfig();
return p->names[idx];
}
and use it in Swift as follows:
if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig1(2)))
{
print(s)
}
This will print the name at position 2 (counting from 0). Of course, with this approach you might also want to have C helpers that return the count of names as well as the current name index.
Again, with these 2 approaches it is assumed the strings are NUL-terminated UTF-8. There are other approaches possible, these are just examples.
Also please note that the above assumes that you access RequestConfiguration as read-only. If you also want to modify it and make the changes visible to the 3rd party library C code, then it's a different ballgame.

Resources