Swift vs C Bridge Enumeration Internals and Metadata - c

Question:
Why aren't the outputs the same for what are basically the same enumeration? Does Swift strip metadata from C (I assume it is stripped by the Compiler, then provided again by the Swift Interface File as though it is a normal Swift enum)?
Details:
I have two similar enumerations, one defined in C and one defined in Swift.
However I'm getting different print results, specifically the Swift Enum is able to print the key as a representational String (in this case first), but the C Enum is only able to print the Enumeration name (in this case CNames).
This is the output of the program,
Enumerations in Swift and C
Running on Swift 5
CNames is 1
first is 1
Program ended with exit code: 0
I assume the Swift main file is using the Swift Generated Interface.
This is what my project looks like:
Here's my native Swift enum, `SNames:
//
// SNames.swift
// TestAppMain
//
import Foundation
public enum SNames : UInt {
case unknown = 0
case first = 1
case middle = 2
case last = 3
}
Here is my C enum, CNames:
#ifndef Names_h
#define Names_h
// NSUInteger type definition
#import <Foundation/NSObjCRuntime.h>
typedef NS_ENUM(NSUInteger, CNames) {
NameUnknown = 0,
NameFirst = 1,
NameMiddle = 2,
NameLast = 3,
};
#endif /* Names_h */
Here's the generated Swift 5 Interface:
// NSUInteger type definition
import Foundation.NSObjCRuntime
public enum CNames : UInt {
case unknown = 0
case first = 1
case middle = 2
case last = 3
}
Here is my bridging header:
#ifndef TestAppMain_Bridging_Header_h
#define TestAppMain_Bridging_Header_h
#import "CNames.h"
#endif /* TestAppMain_Bridging_Header_h */
Finally my main.swift is:
print("Enumerations in Swift and C")
#if swift(>=5)
print("Running on Swift 5")
#endif
let cFirst = CNames.first
let sFirst = SNames.first
// Swift Enum is able to output the specific
// case (i.e first) whereas the C Enum
// doesn't seem to have that info
// CNames is 1
print("\(cFirst) is \(cFirst.rawValue)")
// first is 1
print("\(sFirst) is \(sFirst.rawValue)")

This is just a limitation of C. C enums are nothing more than a visually grouped set of integer constants.
You'll notice that you get the same limitations when applying #objc to Swift enums:
enum NativeEnum: Int32 { case i = 123 }
print(NativeEnum.i) // => "i"
#objc enum ExportedEnum: Int32 { case i = 123 }
print(ExportedEnum.i) // => "ExportedEnum"

Related

Swift Int array to MetalBuffer<Int>

I'm studying app development with swift and metal kit
I want to convert an int type array to Metalbuffer, because I have to cover that array data in Shader.h
Before operating the shader.h, the array's values have not problems.
But, in and after the shader.h, the buffer's that converted from array have some problems.
for example, I send [1, 2, 3, 4] to Metalbuffer and I call the renderEncoder.setVertexBuffer(Metalbuffer).
then, In the Shader.h it appears like [1, 0, 2, 0]
what's the problem???
this is my sample code
var int_array = Array(reapeating: 1, count: 100)
init(){
Buffers = MetalBuffer<Int>(device: device, array: int_array, index: kBuffers.rawValue, options: [])
}
func updateIntArray(){
for i in 0..<100 {
int_array[i] = i % 20
}
Buffers = MetalBuffer<Int>(device: device, array: int_array, index: kBuffers.rawValue, options: [])
(other codes about rendering)
renderEncoder.setVertexBuffer(Buffers)
}
the updateIntArray is called per every frame by view controller.
I printed the sizeof(Buffers[i]) after the shader call, it was 4!
How can I preserve the data without being inserted with 0?
Thank you!
There's a lot of code missing from your question, but I will show you how I pass an array as a parameter to my fragment shader.
I have a ShaderTypes.h file to create a custom value type that can be used with both Swift and MSL (Metal Shading Language). You will need a bridging header for this if you want to use it on the Swift side.
#ifndef ShaderTypes_h
#define ShaderTypes_h
#include <simd/simd.h>
typedef struct {
float intensity;
float time;
float width;
float height;
float originX;
float originY;
}KeyPointValues;
My goal is to pass an array of KeyPointValues as a parameter to my fragment shader. On the swift side I do something like this:
var keyPoints = [KeyPointValues()]
for index in 0...10 {
keyPoints.append(KeyPointValues())
}
var count = keyPoints.count
guard let keyPointBuffer = context.device.makeBuffer(bytes: keyPoints, length: MemoryLayout<KeyPointValues>.stride * count) else { return }
commandEncoder.setFragmentBuffer(keyPointBuffer, offset: 0, index: 0)
commandEncoder.setFragmentBytes(&count, length: MemoryLayout.size(ofValue: count), index: 1)
You need to pass in count as well because there is no keyPoints.count counterpart in MSL.
Then the fragment shader looks something like this:
#include <metal_stdlib>
#include <simd/simd.h>
#import "ShaderTypes.h"
using namespace metal;
fragment half4 rosyFilter(device const KeyPointValues *keyPoints [[buffer(0)]],
constant uint &count [[ buffer(1) ]]
) {
for(uint index = 0; index < count; index++) {
float intensity = keyPoints[index].intensity;
}
}
Hopefully this can get you started in the right direction. You are using an array of Int, so it should be easier as there is probably no need to define a custom struct to use between Swift and MSL.
I am also somewhat new to Metal so I'm not sure if this is the best way of doing things. I appreciate any feedback from people with more experience.

Most efficient way of getting large std::vector into Swift?

I have an Objective-C class that populates a std:vector with millions of points. The structure of the vector is:
typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;
So a CGContour is a vector of CGPoints and CGContours is a vector of the CGContour vector.
I need to access this data in a Swift class somehow. I don't want to use an NSArray because it has a huge overhead compared to using vector (it is like 10x as big and slow).
What would be the most efficient way to get millions of CGPoints accessible in Swift from my Objective-C class?
Edit:
I am populating my CGContours vector like this:
contourVector = CGContours(contours.size());
populatedContourNum = 0
//contours is OpenCV's contours
for( long c = 0; c < contours.size(); c++) {
if (populatedContourNum >= contourVector.size()) {
contourVector.resize(contourVector.size() + 1);
}
contourVector[populatedContourNum] = CGContour(contours[c].size());
for( long pointNum = 0; pointNum < contours[c].size(); pointNum++ )
{
contourVector[populatedContourNum][pointNum] = CGPointMake(contours[c][pointNum].x * scale,
contours[c][pointNum].y * scale);
}
populatedContourNum++;
}
Some parts are not clear enough but I will try to show you some example.
First of all, you need to prepare a class which can access your contourVector. (I cannot see if it is an instance field or a global variable, if it is an instance field, you may use the existing class.)
Create a header for the prepared class, again you may utilize the existing header, but this header needs to be compiled both in C-context and in C++ context. So, if your existing header contains some declaration which cannot be compiled in C-context, you may need separated two headers or some #ifs.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface YourClass : NSObject
- (NSInteger)contoursSize;
- (NSInteger)contourSizeAtIndex:(NSInteger)index;
- (CGPoint *)contourAtIndex:(NSInteger)index;
//...
#end
NS_ASSUME_NONNULL_END
Then add 3 methods to the class specified in the header:
#import "YourClass.h"
#import <vector>
typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;
static CGContours contourVector;
#implementation YourClass
- (NSInteger)contoursSize {
return contourVector.size();
}
- (NSInteger)contourSizeAtIndex:(NSInteger)index {
return contourVector[index].size();
}
- (CGPoint *)contourAtIndex:(NSInteger)index {
return contourVector[index].data();
}
#end
Please do not forget to include the header inside your Project-Bridging-Header.h:
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "YourClass.h"
You need to create a Swift side wrapper class, as you cannot create UnsafeBufferPointer in Objective-C.
class YourClassWrapper {
let yourInstance = YourClass()
var count: Int {
return yourInstance.contoursSize()
}
subscript(index: Int) -> UnsafeBufferPointer<CGPoint> {
guard 0..<count ~= index else {fatalError("Index \(index) out of bounds \(0..<count)")}
let start = yourInstance.contour(at: index)
let count = yourInstance.contourSize(at: index)
return UnsafeBufferPointer(start: start, count: count)
}
}
With these preparations above, you can access each CGPoint as:
let wrapper = YourClassWrapper()
let point = wrapper[0][1]
Or you can get the pointer to the first element in CGContour as:
let ptr = wrapper[0].baseAddress!
You may need to modify some parts to fit this into your actual code. Hope you can make it.

How to capture invalid c enum in swift

I use C to cast my over-the-wire payloads, so int to enum conversion in c:
typedef CF_ENUM( uint8_t, MyEnum_t ) {
a = 0
b = 1
}
...
value = ( MyEnum_t * ) rawValue;
Because the value is over-the-wire, the actual raw value can be any uint8_t. It is possible that the rawValue is actually not 0 or 1.
Below I have a swift code sample showing setting an enum to an impossible/invalid value and a switch statement handling that value.
import Foundation
enum MyEnum: UInt8 {
case a = 0
case b = 1
}
let raw: UInt8 = 0b100 // 4
let value: MyEnum = unsafeBitCast(raw, to: MyEnum.self)
switch value {
case .a:
print("a:", value) // * not reached *
case .b:
print("b:", value) // "b: \n"
print("value == .a => \(value == .a)") // "x == .a => true\n"
print("value == .b => \(value == .b)") // "x == .b => false\n"
}
print(value.rawValue) // "0"
With a C raw value of 4, swift sample shows crazy results:
4 force casted prints nothing for its value (most likely char 4 or 0, both unprintable)
4 switches as MyEnum.b (1)
4 compares equally to MyEnum.a (0)
4 as MyEnum raw value prints 0 (MyEnum.a)
Is there any way to capture this error in swift? I prefer to not use these odd results to detect the anomaly.

Map modify array of objects in Swift 2.2 (3.0)

I want to be able to modify my array of objects using map in Swift of the fly, without looping through each element.
Before here were able to do something like this (Described in more details here:
gnomes = gnomes.map { (var gnome: Gnome) -> Gnome in
gnome.age = 140
return gnome
}
Thanks for Erica Sadun and others, new proposals have gone through and we're now getting rid of C-style loops and using var inside the loop.
In my case I'm first getting a warning to remove the var in then an error my gnome is a constant (naturally)
My question is : How do we alter arrays inside a map or the new styled loops for that matter to be fully prepared for Swift 3.0?
If you want to keep that syntax, just use a (mutable) temporary variable
gnomes = gnomes.map { (gnome: Gnome) -> Gnome in
var mutableGnome = gnome
mutableGnome.age = 140
return mutableGnome
}
(Below follows the case where Gnome is a reference type; a class -- since you haven't showed us how you've defined Gnome. For the case where Gnome as value type (a struct), see #vadian:s answer)
The removal of var will not effect using .map to mutate mutable members of an array of reference type objects. I.e., you could simply use your old approach (omitting however, the var in the .map closure signature).
class Gnome {
var age = 42
}
var gnomes = [Gnome(), Gnome(), Gnome()]
gnomes = gnomes.map {
$0.age = 150
return $0
}
/* result */
gnomes.forEach { print($0.age) } // 3x 150
However, in case you just want to modify your original array rather than assigning the result of .map to a new array, .forEach might be a more appropriate choice than .map.
gnomes.forEach { $0.age = 140 }
/* result */
gnomes.forEach { print($0.age) } // 3x 140
Given:
struct Gnome {
var age: Int = 0
}
var gnomes = Array(count: 5, repeatedValue: Gnome())
... there are two decent options. The first is as #vadian put it:
gnomes = gnomes.map{
var gnome = $0
gnome.age = 70
return gnome
}
Whilst the second keeps control over "ageing" private and simplifies mapping at the point of call:
struct Gnome {
private(set) var age: Int = 0
func aged(age: Int) -> Gnome {
var gnome = self
gnome.age = age
// any other ageing related changes
return gnome
}
}
gnomes = gnomes.map{ $0.aged(140) }
Of course, reference types still have their place in programming, which may well be a better fit in this case. The friction we are experiencing here suggests that we are trying to treat these structures as if they were objects. If that is the behaviour you need, then you should consider implementing Gnome as a class.

Convert objective-c typedef to its string equivalent

Assuming that I have a typedef declared in my .h file as such:
typedef enum {
JSON,
XML,
Atom,
RSS
} FormatType;
I would like to build a function that converts the numeric value of the typedef to a string. For example, if the message [self toString:JSON] was sent; it would return 'JSON'.
The function would look something like this:
-(NSString *) toString:(FormatType)formatType {
//need help here
return [];
}
Incidentally, if I try this syntax
[self toString:FormatType.JSON];
to pass the typedef value to the method, I get an error. What am I missing?
This is really a C question, not specific to Objective-C (which is a superset of the C language). Enums in C are represented as integers. So you need to write a function that returns a string given an enum value. There are many ways to do this. An array of strings such that the enum value can be used as an index into the array or a map structure (e.g. an NSDictionary) that maps an enum value to a string work, but I find that these approaches are not as clear as a function that makes the conversion explicit (and the array approach, although the classic C way is dangerous if your enum values are not continguous from 0). Something like this would work:
- (NSString*)formatTypeToString:(FormatType)formatType {
NSString *result = nil;
switch(formatType) {
case JSON:
result = #"JSON";
break;
case XML:
result = #"XML";
break;
case Atom:
result = #"Atom";
break;
case RSS:
result = #"RSS";
break;
default:
[NSException raise:NSGenericException format:#"Unexpected FormatType."];
}
return result;
}
Your related question about the correct syntax for an enum value is that you use just the value (e.g. JSON), not the FormatType.JSON sytax. FormatType is a type and the enum values (e.g. JSON, XML, etc.) are values that you can assign to that type.
You can't do it easily. In C and Objective-C, enums are really just glorified integer constants. You'll have to generate a table of names yourself (or with some preprocessor abuse). For example:
// In a header file
typedef enum FormatType {
JSON,
XML,
Atom,
RSS
} FormatType;
extern NSString * const FormatType_toString[];
// In a source file
// initialize arrays with explicit indices to make sure
// the string match the enums properly
NSString * const FormatType_toString[] = {
[JSON] = #"JSON",
[XML] = #"XML",
[Atom] = #"Atom",
[RSS] = #"RSS"
};
...
// To convert enum to string:
NSString *str = FormatType_toString[theEnumValue];
The danger of this approach is that if you ever change the enum, you have to remember to change the array of names. You can solve this problem with some preprocessor abuse, but it's tricky and ugly.
Also note that this assumes you have a valid enum constant. If you have an integer value from an untrusted source, you additionally need to do a check that your constant is valid, e.g. by including a "past max" value in your enum, or by checking if it's less than the array length, sizeof(FormatType_toString) / sizeof(FormatType_toString[0]).
My solution:
edit: I've added even a better solution at the end, using Modern Obj-C
1.Put names as keys in an array.
Make sure the indexes are the appropriate enums, and in the right order (otherwise exception).
note: names is a property synthesized as *_names*;
code was not checked for compilation, but I used the same technique in my app.
typedef enum {
JSON,
XML,
Atom,
RSS
} FormatType;
+ (NSArray *)names
{
static NSMutableArray * _names = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_names = [NSMutableArray arrayWithCapacity:4];
[_names insertObject:#"JSON" atIndex:JSON];
[_names insertObject:#"XML" atIndex:XML];
[_names insertObject:#"Atom" atIndex:Atom];
[_names insertObject:#"RSS" atIndex:RSS];
});
return _names;
}
+ (NSString *)nameForType:(FormatType)type
{
return [[self names] objectAtIndex:type];
}
//
2.Using Modern Obj-C you we can use a dictionary to tie descriptions to keys in the enum.Order DOES NOT matter.
typedef NS_ENUM(NSUInteger, UserType) {
UserTypeParent = 0,
UserTypeStudent = 1,
UserTypeTutor = 2,
UserTypeUnknown = NSUIntegerMax
};
#property (nonatomic) UserType type;
+ (NSDictionary *)typeDisplayNames
{
return #{#(UserTypeParent) : #"Parent",
#(UserTypeStudent) : #"Student",
#(UserTypeTutor) : #"Tutor",
#(UserTypeUnknown) : #"Unknown"};
}
- (NSString *)typeDisplayName
{
return [[self class] typeDisplayNames][#(self.type)];
}
Usage (in a class instance method):
NSLog(#"%#", [self typeDisplayName]);
Combining #AdamRosenfield answer, #Christoph comment and another trick to handle plain C enums I suggest:
// In a header file
typedef enum {
JSON = 0, // explicitly indicate starting index
XML,
Atom,
RSS,
FormatTypeCount, // keep track of the enum size automatically
} FormatType;
extern NSString *const FormatTypeName[FormatTypeCount];
// In a source file
NSString *const FormatTypeName[FormatTypeCount] = {
[JSON] = #"JSON",
[XML] = #"XML",
[Atom] = #"Atom",
[RSS] = #"RSS",
};
// Usage
NSLog(#"%#", FormatTypeName[XML]);
In the worst case - like if you change the enum but forget to change the names array - it will return nil for this key.
define typedef enum in class header:
typedef enum {
IngredientType_text = 0,
IngredientType_audio = 1,
IngredientType_video = 2,
IngredientType_image = 3
} IngredientType;
write a method like this in class:
+ (NSString*)typeStringForType:(IngredientType)_type {
NSString *key = [NSString stringWithFormat:#"IngredientType_%i", _type];
return NSLocalizedString(key, nil);
}
have the strings inside Localizable.strings file:
/* IngredientType_text */
"IngredientType_0" = "Text";
/* IngredientType_audio */
"IngredientType_1" = "Audio";
/* IngredientType_video */
"IngredientType_2" = "Video";
/* IngredientType_image */
"IngredientType_3" = "Image";
I would use the compiler's # string token (along with macros to make it all more compact):
#define ENUM_START \
NSString* ret; \
switch(value) {
#define ENUM_CASE(evalue) \
case evalue: \
ret = ##evalue; \
break;
#define ENUM_END \
} \
return ret;
NSString*
_CvtCBCentralManagerStateToString(CBCentralManagerState value)
{
ENUM_START
ENUM_CASE(CBCentralManagerStateUnknown)
ENUM_CASE(CBCentralManagerStateResetting)
ENUM_CASE(CBCentralManagerStateUnsupported)
ENUM_CASE(CBCentralManagerStateUnauthorized)
ENUM_CASE(CBCentralManagerStatePoweredOff)
ENUM_CASE(CBCentralManagerStatePoweredOn)
ENUM_END
}
I like the #define way of doing this:
// Place this in your .h file, outside the #interface block
typedef enum {
JPG,
PNG,
GIF,
PVR
} kImageType;
#define kImageTypeArray #"JPEG", #"PNG", #"GIF", #"PowerVR", nil
// Place this in the .m file, inside the #implementation block
// A method to convert an enum to string
-(NSString*) imageTypeEnumToString:(kImageType)enumVal
{
NSArray *imageTypeArray = [[NSArray alloc] initWithObjects:kImageTypeArray];
return [imageTypeArray objectAtIndex:enumVal];
}
source (source no longer available)
I made a sort of mix of all solutions found on this page to create mine, it's a kind of object oriented enum extension or
something.
In fact if you need more than just constants (i.e. integers), you probably need a model object (We're all talking about MVC, right?)
Just ask yourself the question before using this, am I right, don't you, in fact, need a real model object, initialized from a webservice, a plist, an SQLite database or CoreData ?
Anyway here comes the code (MPI is for "My Project Initials", everybody use this or their name, it seems) :
MyWonderfulType.h :
typedef NS_ENUM(NSUInteger, MPIMyWonderfulType) {
MPIMyWonderfulTypeOne = 1,
MPIMyWonderfulTypeTwo = 2,
MPIMyWonderfulTypeGreen = 3,
MPIMyWonderfulTypeYellow = 4,
MPIMyWonderfulTypePumpkin = 5
};
#import <Foundation/Foundation.h>
#interface MyWonderfulType : NSObject
+ (NSString *)displayNameForWonderfulType:(MPIMyWonderfulType)wonderfulType;
+ (NSString *)urlForWonderfulType:(MPIMyWonderfulType)wonderfulType;
#end
And MyWonderfulType.m :
#import "MyWonderfulType.h"
#implementation MyWonderfulType
+ (NSDictionary *)myWonderfulTypeTitles
{
return #{
#(MPIMyWonderfulTypeOne) : #"One",
#(MPIMyWonderfulTypeTwo) : #"Two",
#(MPIMyWonderfulTypeGreen) : #"Green",
#(MPIMyWonderfulTypeYellow) : #"Yellow",
#(MPIMyWonderfulTypePumpkin) : #"Pumpkin"
};
}
+ (NSDictionary *)myWonderfulTypeURLs
{
return #{
#(MPIMyWonderfulTypeOne) : #"http://www.theone.com",
#(MPIMyWonderfulTypeTwo) : #"http://www.thetwo.com",
#(MPIMyWonderfulTypeGreen) : #"http://www.thegreen.com",
#(MPIMyWonderfulTypeYellow) : #"http://www.theyellow.com",
#(MPIMyWonderfulTypePumpkin) : #"http://www.thepumpkin.com"
};
}
+ (NSString *)displayNameForWonderfulType:(MPIMyWonderfulType)wonderfulType {
return [MPIMyWonderfulType myWonderfulTypeTitles][#(wonderfulType)];
}
+ (NSString *)urlForWonderfulType:(MPIMyWonderfulType)wonderfulType {
return [MPIMyWonderfulType myWonderfulTypeURLs][#(wonderfulType)];
}
#end
Another solution:
typedef enum BollettinoMavRavTypes {
AMZCartServiceOperationCreate,
AMZCartServiceOperationAdd,
AMZCartServiceOperationGet,
AMZCartServiceOperationModify
} AMZCartServiceOperation;
#define AMZCartServiceOperationValue(operation) [[[NSArray alloc] initWithObjects: #"CartCreate", #"CartAdd", #"CartGet", #"CartModify", nil] objectAtIndex: operation];
In your method you can use:
NSString *operationCheck = AMZCartServiceOperationValue(operation);
Improved #yar1vn answer by dropping string dependency:
#define VariableName(arg) (#""#arg)
typedef NS_ENUM(NSUInteger, UserType) {
UserTypeParent = 0,
UserTypeStudent = 1,
UserTypeTutor = 2,
UserTypeUnknown = NSUIntegerMax
};
#property (nonatomic) UserType type;
+ (NSDictionary *)typeDisplayNames
{
return #{#(UserTypeParent) : VariableName(UserTypeParent),
#(UserTypeStudent) : VariableName(UserTypeStudent),
#(UserTypeTutor) : VariableName(UserTypeTutor),
#(UserTypeUnknown) : VariableName(UserTypeUnknown)};
}
- (NSString *)typeDisplayName
{
return [[self class] typeDisplayNames][#(self.type)];
}
Thus when you'll change enum entry name corresponding string will be changed.
Useful in case if you are not going to show this string to user.
#pixel added the most brilliant answer here:
https://stackoverflow.com/a/24255387/1364257
Please, upvote him!
He uses the neat X macro from the 1960's. (I've changed his code a bit for the modern ObjC)
#define X(a, b, c) a b,
enum ZZObjectType {
XXOBJECTTYPE_TABLE
};
typedef NSUInteger TPObjectType;
#undef X
#define XXOBJECTTYPE_TABLE \
X(ZZObjectTypeZero, = 0, #"ZZObjectTypeZero") \
X(ZZObjectTypeOne, , #"ZZObjectTypeOne") \
X(ZZObjectTypeTwo, , #"ZZObjectTypeTwo") \
X(ZZObjectTypeThree, , #"ZZObjectTypeThree")
+ (NSString*)nameForObjectType:(ZZObjectType)objectType {
#define X(a, b, c) #(a):c,
NSDictionary *dict = #{XXOBJECTTYPE_TABLE};
#undef X
return dict[objectType];
}
That's it. Clean and neat.
Thanks to #pixel! https://stackoverflow.com/users/21804/pixel
Given an enum definition like:
typedef NS_ENUM(NSInteger, AssetIdentifier) {
Isabella,
William,
Olivia
};
We can define a macro to convert an enum value to its corresponding string, as shown below.
#define AssetIdentifier(asset) \
^(AssetIdentifier identifier) { \
switch (identifier) { \
case asset: \
default: \
return ##asset; \
} \
}(asset)
The switch statement used in the block is for type checking, and also to get the auto-complete support in Xcode.
I had a large enumerated type I wanted to convert it into an NSDictionary lookup. I ended up using sed from OSX terminal as:
$ sed -E 's/^[[:space:]]{1,}([[:alnum:]]{1,}).*$/ #(\1) : #"\1",/g' ObservationType.h
which can be read as: 'capture the first word on the line and output #(word) : #"word",'
This regex converts the enum in a header file named 'ObservationType.h' which contains:
typedef enum : int {
ObservationTypePulse = 1,
ObservationTypeRespRate = 2,
ObservationTypeTemperature = 3,
.
.
}
into something like:
#(ObservationTypePulse) : #"ObservationTypePulse",
#(ObservationTypeRespRate) : #"ObservationTypeRespRate",
#(ObservationTypeTemperature) : #"ObservationTypeTemperature",
.
.
which can then be wrapped in a method using modern objective-c syntax #{ } (as explained by #yar1vn above) to create a NSDictionary lookup :
-(NSDictionary *)observationDictionary
{
static NSDictionary *observationDictionary;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
observationDictionary = [[NSDictionary alloc] initWithDictionary:#{
#(ObservationTypePulse) : #"ObservationTypePulse",
#(ObservationTypeRespRate) : #"ObservationTypeRespRate",
.
.
}];
});
return observationDictionary;
}
The dispatch_once boiler-plate is just to ensure that the static variable is initialised in a thread-safe manner.
Note: I found the sed regex expression on OSX odd - when I tried to use + to match 'one or more' it didn't work and had to resort to using {1,} as a replacement
I use a variation on Barry Walk's answer, that in order of importance:
Allows the compiler to check for missing case clauses (it can't if you have a default clause).
Uses an Objective-C typical name (rather than a Java like name).
Raises a specific exception.
Is shorter.
EG:
- (NSString*)describeFormatType:(FormatType)formatType {
switch(formatType) {
case JSON:
return #"JSON";
case XML:
return #"XML";
case Atom:
return #"Atom";
case RSS:
return #"RSS";
}
[NSException raise:NSInvalidArgumentException format:#"The given format type number, %ld, is not known.", formatType];
return nil; // Keep the compiler happy - does not understand above line never returns!
}
I combined several approaches here.
I like the idea of the preprocessor and the indexed list.
There's no extra dynamic allocation, and because of the inlining the compiler might be able to optimize the lookup.
typedef NS_ENUM(NSUInteger, FormatType) { FormatTypeJSON = 0, FormatTypeXML, FormatTypeAtom, FormatTypeRSS, FormatTypeCount };
NS_INLINE NSString *FormatTypeToString(FormatType t) {
if (t >= FormatTypeCount)
return nil;
#define FormatTypeMapping(value) [value] = ##value
NSString *table[FormatTypeCount] = {FormatTypeMapping(FormatTypeJSON),
FormatTypeMapping(FormatTypeXML),
FormatTypeMapping(FormatTypeAtom),
FormatTypeMapping(FormatTypeRSS)};
#undef FormatTypeMapping
return table[t];
}
First of all, with regards to FormatType.JSON: JSON is not a member of FormatType, it's a possible value of the type. FormatType isn't even a composite type — it's a scalar.
Second, the only way to do this is to create a mapping table. The more common way to do this in Objective-C is to create a series of constants referring to your "symbols", so you'd have NSString *FormatTypeJSON = #"JSON" and so on.
the following provides a solution such that to add a new enum requires
only a one-line edit, similar work to adding a single line in an enum {} list.
//------------------------------------------------------------------------------
// enum to string example
#define FOR_EACH_GENDER(tbd) \
tbd(GENDER_MALE) \
tbd(GENDER_FEMALE) \
tbd(GENDER_INTERSEX) \
#define ONE_GENDER_ENUM(name) name,
enum
{
FOR_EACH_GENDER(ONE_GENDER_ENUM)
MAX_GENDER
};
#define ONE_GENDER(name) #name,
static const char *enumGENDER_TO_STRING[] =
{
FOR_EACH_GENDER(ONE_GENDER)
};
// access string name with enumGENDER_TO_STRING[value]
// or, to be safe converting from a untrustworthy caller
static const char *enumGenderToString(unsigned int value)
{
if (value < MAX_GENDER)
{
return enumGENDER_TO_STRING[value];
}
return NULL;
}
static void printAllGenders(void)
{
for (int ii = 0; ii < MAX_GENDER; ii++)
{
printf("%d) gender %s\n", ii, enumGENDER_TO_STRING[ii]);
}
}
//------------------------------------------------------------------------------
// you can assign an arbitrary value and/or information to each enum,
#define FOR_EACH_PERSON(tbd) \
tbd(2, PERSON_FRED, "Fred", "Weasley", GENDER_MALE, 12) \
tbd(4, PERSON_GEORGE, "George", "Weasley", GENDER_MALE, 12) \
tbd(6, PERSON_HARRY, "Harry", "Potter", GENDER_MALE, 10) \
tbd(8, PERSON_HERMIONE, "Hermione", "Granger", GENDER_FEMALE, 10) \
#define ONE_PERSON_ENUM(value, ename, first, last, gender, age) ename = value,
enum
{
FOR_EACH_PERSON(ONE_PERSON_ENUM)
};
typedef struct PersonInfoRec
{
int value;
const char *ename;
const char *first;
const char *last;
int gender;
int age;
} PersonInfo;
#define ONE_PERSON_INFO(value, ename, first, last, gender, age) \
{ ename, #ename, first, last, gender, age },
static const PersonInfo personInfo[] =
{
FOR_EACH_PERSON(ONE_PERSON_INFO)
{ 0, NULL, NULL, NULL, 0, 0 }
};
// note: if the enum values are not sequential, you need another way to lookup
// the information besides personInfo[ENUM_NAME]
static void printAllPersons(void)
{
for (int ii = 0; ; ii++)
{
const PersonInfo *pPI = &personInfo[ii];
if (!pPI->ename)
{
break;
}
printf("%d) enum %-15s %8s %-8s %13s %2d\n",
pPI->value, pPI->ename, pPI->first, pPI->last,
enumGenderToString(pPI->gender), pPI->age);
}
}
Every answer here basically says the same thing, create a regular enum and then use a custom getter to switch between strings.
I employ a much simpler solution that is faster, shorter, and cleaner—using Macros!
#define kNames_allNames ((NSArray <NSString *> *)#[#"Alice", #"Bob", #"Eve"])
#define kNames_alice ((NSString *)kNames_allNames[0])
#define kNames_bob ((NSString *)kNames_allNames[1])
#define kNames_eve ((NSString *)kNames_allNames[2])
Then you can simply start to type kNam... and autocomplete will display the lists you desire!
Additionally, if you want to handle logic for all the names at once you can simply fast enumerate the literal array in order, as follows:
for (NSString *kName in kNames_allNames) {}
Lastly, the NSString casting in the macros ensures behavior similar to typedef!
Enjoy!
I think I just created the most elegant approach to solve this.
(inspired by #AdamRosenfield)
For example, you may declare the enum like this:
typedef NS_ENUM(int, MyEnum) {
MyEnumDefault,
MyEnumOff,
MyEnumOn,
MyEnumMixed,
};
static NSString * const __SGEnumMap_MyEnum[] = {
[MyEnumDefault] = #"default",
[MyEnumOff] = #"off",
[MyEnumOn] = #"on",
[MyEnumMixed] = #"mixed"
};
SGENUM_DEFINE_ToString(MyEnum)
SGENUM_DEFINE_FromString(MyEnum)
Then, you get the two functions to convert between enums and strings:
NSString *result = MyEnumToString(MyEnumOn);
NSLog(#"%#", result);
// on
MyEnum value = MyEnumFromString(#"off", -1); // -1 as the default value
NSLog(#"%d", result);
// 1
Is that cool? Here are the magic macros:
#define SGENUM_DEFINE_ToString(name) NS_INLINE NSString *name##ToString(name value){\
int count = sizeof(__SGEnumMap_##name) / sizeof(NSString *);\
if (value > count || value < 0) return nil;\
return __SGEnumMap_##name[value];\
}
#define SGENUM_DEFINE_FromString(name) NS_INLINE name name##FromString(NSString *input, int defaultValue) {\
int count = sizeof(__SGEnumMap_##name) / sizeof(NSString *);\
for (int i = 0; i < count; i++) {\
NSString *str = __SGEnumMap_##name[i];\
if ([str isEqual:input]) {\
return i;\
}\
}\
return defaultValue;\
}
Many answers all fairly good.
If you are after a generic, Objective C solution that uses some macros...
Key feature is it uses the enum as an index into a static array of NSString constants.
the array itself is wrapped into a function to make it more like the suite of NSStringFromXXX functions prevalent in the Apple APIs.
you will need to #import "NSStringFromEnum.h" found here
http://pastebin.com/u83RR3Vk
[EDIT]
also needs #import "SW+Variadic.h" found here http://pastebin.com/UEqTzYLf
Example 1 : completely define a NEW enum typedef, with string converters.
in myfile.h
#import "NSStringFromEnum.h"
#define define_Dispatch_chain_cmd(enum)\
enum(chain_done,=0)\
enum(chain_entry)\
enum(chain_bg)\
enum(chain_mt)\
enum(chain_alt)\
enum(chain_for_c)\
enum(chain_while)\
enum(chain_continue_for)\
enum(chain_continue_while)\
enum(chain_break_for)\
enum(chain_break_while)\
enum(chain_previous)\
enum(chain_if)\
enum(chain_else)\
interface_NSString_Enum_DefinitionAndConverters(Dispatch_chain_cmd)
in myfile.m:
#import "myfile.h"
implementation_NSString_Enum_Converters(Dispatch_chain_cmd)
to use :
NSString *NSStringFromEnumDispatch_chain_cmd(enum Dispatch_chain_cmd value);
NSStringFromEnumDispatch_chain_cmd(chain_for_c) returns #"chain_for_c"
enum Dispatch_chain_cmd enumDispatch_chain_cmdFromNSString(NSString *value);
enumDispatch_chain_cmdFromNSString(#"chain_previous") returns chain_previous
Example 2: provide conversion routines for an existing enum
also demonstrates using a settings string, and renaming the typename used in the functions.
in myfile.h
#import "NSStringFromEnum.h"
#define CAEdgeAntialiasingMask_SETTINGS_PARAMS CAEdgeAntialiasingMask,mask,EdgeMask,edgeMask
interface_NSString_Enum_Converters(CAEdgeAntialiasingMask_SETTINGS_PARAMS)
in myfile.m:
// we can put this in the .m file as we are not defining a typedef, just the strings.
#define define_CAEdgeAntialiasingMask(enum)\
enum(kCALayerLeftEdge)\
enum(kCALayerRightEdge)\
enum(kCALayerBottomEdge)\
enum(kCALayerTopEdge)
implementation_NSString_Enum_Converters(CAEdgeAntialiasingMask_SETTINGS_PARAMS)
Here is working -> https://github.com/ndpiparava/ObjcEnumString
//1st Approach
#define enumString(arg) (#""#arg)
//2nd Approach
+(NSString *)secondApproach_convertEnumToString:(StudentProgressReport)status {
char *str = calloc(sizeof(kgood)+1, sizeof(char));
int goodsASInteger = NSSwapInt((unsigned int)kgood);
memcpy(str, (const void*)&goodsASInteger, sizeof(goodsASInteger));
NSLog(#"%s", str);
NSString *enumString = [NSString stringWithUTF8String:str];
free(str);
return enumString;
}
//Third Approcah to enum to string
NSString *const kNitin = #"Nitin";
NSString *const kSara = #"Sara";
typedef NS_ENUM(NSUInteger, Name) {
NameNitin,
NameSara,
};
+ (NSString *)thirdApproach_convertEnumToString :(Name)weekday {
__strong NSString **pointer = (NSString **)&kNitin;
pointer +=weekday;
return *pointer;
}
Depending on your needs, you could alternatively use compiler directives to simulate the behaviour you are looking for.
#define JSON #"JSON"
#define XML #"XML"
#define Atom #"Atom"
#define RSS #"RSS"
Just remember the usual compiler shortcomings, (not type safe, direct copy-paste makes source file larger)

Resources