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

前言

在我们编码过程中,枚举会经常用到, 尤其是用来表示多种状态时.
然而, 在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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,347评论 19 139
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 8,455评论 0 4
  • · 事情经过 昨天傍晚因为女儿在学校的事情而恼火,情绪一度跌到谷底。一半恼的是女儿的情商,一半恼的是告状的家长那一...
    玄月之佑阅读 4,736评论 5 4
  • 这个月开始读《硅谷钢铁侠》,书读到一半,就开始走神了。因为马斯克的前妻贾斯汀触动了我,她和马斯克有良好的感情基础,...
    熊熊如焰阅读 3,265评论 0 1
  • 我常常幻想自己在一个夕阳西下的海边惬意享受自然的美景,或者在一个山清水秀的桃花源呼吸新鲜空气,或者在奔驰的列车上聆...
    可可咳咳阅读 1,891评论 0 0