利用宏定义实现枚举转字符串

前言

在我们编码过程中,枚举会经常用到, 尤其是用来表示多种状态时.
然而, 在OC中, 对枚举进行打印调试 或者 拼接方法 的操作的编程体验是非常差的.

例子

以下这种情况你应该会经常遇到:

// 你工作时的几种状态
typedef NS_ENUM(NSInteger, WorkStatus) {
    /** 摸鱼*/
    WorkStatusUnKnown,
    /** 认真工作*/
    WorkStatusWorking,
    /** 休息*/
    WorkStatusSleeping,
};

当我们想要打印这个枚举时, 默认输出的是这个枚举标识符所对应的值, 这样的效果是非常不理想的, 只输出数字,不直观(我们之所以使用枚举来定义状态, 不就是要直观的表示吗?), 为了达到直观这一目的, 写一个将枚举标识符转换成字符串的方法就势在必得了:

- (NSString *)WorkStatusDescription:(WorkStatus)status
{
    NSString *desc = nil;
    switch (status) {
        case WorkStatusUnKnown:
            desc = @"WorkStatusUnKnown";
            break;
        case WorkStatusWorking:
            desc = @"WorkStatusWorking";
            break;
        case WorkStatusSleeping:
            desc = @"WorkStatusSleeping";
            break;
        default:
            desc = @"NoOne";
            break;
    }
    return desc;
}

问题

这样操作, 也许解决了不直观的问题, 但是细看之下还是有两点很大的问题.
1. 在某一个类的空间内声明定义转换方法, 对于作用域外(其他类)的地方使用非常不便.
2. 当对枚举的标识符进行增删改操作时, 必须也要同时修改转换方法内的代码, 非常不灵活.

优化

优化问题1

针对于 问题 1, 我们可以通过在头文件中声明定义函数来解决:

static NSString * WorkStatusDescription(WorkStatus status) __attribute__((unused));

static NSString * WorkStatusDescription(WorkStatus status) {
    NSString *desc = nil;
    switch (status) {
        case WorkStatusUnKnown:
            desc = @"WorkStatusUnKnown";
            break;
        case WorkStatusWorking:
            desc = @"WorkStatusWorking";
            break;
        case WorkStatusSleeping:
            desc = @"WorkStatusSleeping";
            break;
        default:
            desc = @"NoOne";
            break;
    }
    return desc;
}

有两点需要解释:
1. 使用static可以防止发生函数重复声明定义的错误. (使用NS_INLINE也可以.)
2. __attribute__((unused)) 表示告诉编译器忽略Unused Warning.

优化问题2

解决 问题 2 的关键在于如何将一个枚举标识符灵活的转换成字符串. 根据这个思路, 很自然的就可以联想到 使用 宏定义中 # 可以将参数转换成字符串的特性来解决.

// 定义枚举标识符和其对应的值的宏
#define ENUM_VALUE(name,assign) name assign,

// 将枚举标识符转换成字符串的宏
#define ENUM_CASE(name,assign) case name: return @#name;

// 将字符串转换为枚举标识符的宏
#define ENUM_STRCMP(name,assign) if ([string isEqualToString:@#name]) return name;

/// 声明函数 及 定义枚举
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
typedef NS_ENUM(NSUInteger, EnumType) { \
    ENUM_DEF(ENUM_VALUE) \
}; \
static NSString *stringFrom##EnumType(EnumType value) __attribute__((unused)); \
static EnumType EnumType##FromString(NSString *string) __attribute__((unused)); \
static NSString *stringFrom##EnumType(EnumType value) { \
    switch(value) { \
        ENUM_DEF(ENUM_CASE) \
        default: return @""; \
    } \
} \
\
static EnumType EnumType##FromString(NSString *string) { \
    ENUM_DEF(ENUM_STRCMP) \
    return (EnumType)0; \
}

为了一气呵成, 已经将针对于 问题 1 优化合并到上面这个代码块中.

使用

// 导入定义宏所在的头文件.
#import "enum_generator.h"

// 使用定义的宏声明枚举
#define WorkStatus(XX) \
XX(WorkStatusUnKnown,) \
XX(WorkStatusWorking,) \
XX(WorkStatusSleeping,=50)
// 生成定义的枚举 与 转换方法.
DECLARE_ENUM(WorkStatus,WorkStatus)

为了更直观的感受, 我们进入预编译阶段, 查看宏生成的代码(为了看起来清晰 已经进行手动换行):

// DECLARE_ENUM(WorkStatus,WorkStatus) 所生成的代码
typedef enum WorkStatus : NSUInteger WorkStatus; enum WorkStatus : NSUInteger {
    WorkStatusUnKnown ,
    WorkStatusWorking ,
    WorkStatusSleeping =50,
};

static NSString *stringFromWorkStatu(WorkStatus value) __attribute__((unused));
static WorkStatus WorkStatusFromString(NSString *string) __attribute__((unused));

static NSString *stringFromWorkStatus(WorkStatus value) {
    switch(value) {
        case WorkStatusUnKnown:
            return @"WorkStatusUnKnown";
        case WorkStatusWorking:
            return @"WorkStatusWorking";
        case WorkStatusSleeping:
            return @"WorkStatusSleeping";
        default:
            return @"";
    }
}

static WorkStatus WorkStatusFromString(NSString *string) {
    if ([string isEqualToString:@"WorkStatusUnKnown"]) return WorkStatusUnKnown;
    if ([string isEqualToString:@"WorkStatusWorking"]) return WorkStatusWorking;
    if ([string isEqualToString:@"WorkStatusSleeping"]) return WorkStatusSleeping;
    return (WorkStatus)0;
}

测试

    WorkStatus testWorkStatus = WorkStatusUnKnown;
    NSLog(@"workstatus is: %@", stringFromWorkStatus(testWorkStatus));
    if (testWorkStatus == WorkStatusFromString(@"WorkStatusUnKnown")) {
        NSLog(@"确认在摸鱼");
    }

    // 输出:
    // workstatus is: WorkStatusUnKnown
    // 确认在摸鱼

More

灵感来自 https://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c/202511#202511

Demo https://github.com/onekyle/EnumStringConvert/tree/master

如果你有更好的想法 请不吝赐教.

订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x