xcode 将 Objective-C 枚举常量转换为字符串名称

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18088041/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 03:40:06  来源:igfitidea点击:

Convert Objective-C enum constants to string names

objective-cxcodellvmlldb

提问by Adam

Previously, this was impossible (you have to write it all out by hand / create a static array / put all the values into a dictionary and read them back ... etc)

以前,这是不可能的(您必须手动将其全部写出/创建一个静态数组/将所有值放入字典中并读取它们......等等)

But I've noticed that the latest Xcode's lldb (4.6, maybe earlier versions too) is automatically converting enum-constants to strings.

但是我注意到最新的 Xcode 的 lldb(4.6,也可能是更早的版本)会自动将枚举常量转换为字符串。

My problem is that we use a lot of libraries - including Apple's own! - which use annoying public enums with no "value-to-string" method offered. So I end up having to (many, many times over) do the "well, since Mr. Library Author didn't do this, now I have to make the static array for them...".

我的问题是我们使用了很多库——包括苹果自己的!- 使用烦人的公共枚举,没有提供“值到字符串”的方法。所以我最终不得不(很多次)做“好吧,因为图书馆作者先生没有这样做,现在我必须为他们制作静态数组......”。

I kept hoping Apple would provide a way out of this - is it finally here? Or is this some trick that only the debugger can do - mere runtime code has no access to it?

我一直希望 Apple 能提供一种解决方法——它终于来了吗?或者这是只有调试器才能做的一些技巧 - 仅仅运行时代码无法访问它?

采纳答案by Jason Molenda

lldb doesn't have any special capabilities regarding printing enum names. I think what you're seeing is the result of the enum values being recorded in the debug info (or not). For instance,

lldb 在打印枚举名称方面没有任何特殊功能。我认为您看到的是调试信息中记录的枚举值的结果(或不记录)。例如,

enum myenums {a = 0, b, c};
int main ()
{
 enum myenums var = b;
 return (int) var;  // break here
}

% xcrun clang -g a.c
% xcrun lldb a.out
(lldb) br s -p break
Breakpoint 1: where = a.out`main + 18 at a.c:5, address = 0x0000000100000f92
(lldb) r
[...]
-> 5     return (int) var;  // break here
   6    }
(lldb) p var
(myenums) 
0x0000005a:     TAG_enumeration_type [5] *
                 AT_name( "myenums" )
                 AT_byte_size( 0x04 )
                 AT_decl_file( "/private/tmp/a.c" )
                 AT_decl_line( 1 )

0x00000062:         TAG_enumerator [6]  
                     AT_name( "a" )
                     AT_const_value( 0x0000000000000000 )

0x00000068:         TAG_enumerator [6]  
                     AT_name( "b" )
                     AT_const_value( 0x0000000000000001 )

0x0000006e:         TAG_enumerator [6]  
                     AT_name( "c" )
                     AT_const_value( 0x0000000000000002 )
= b (lldb) p (myenums) 0 (myenums) = a (lldb)

If you look at the debug info for this binary (dwarfdump a.out.dSYM) you'll see that the variable var's type is myenumsand the debug information includes the values of those enumerated types:

如果您查看此二进制文件 ( dwarfdump a.out.dSYM)的调试信息,您将看到变量var的类型为myenums并且调试信息包括这些枚举类型的值:

enum myenums {a = 0, b, c};
enum otherenums {d = 0, e, f}; // unused in this CU
int main ()
{
 enum myenums var = b;
 return (int) var;  // break here
}

If I add another enum to my sample file which isn't used anywhere,

如果我将另一个枚举添加到我的示例文件中,该文件在任何地方都没有使用,

int arr[] = { [1] = 2, [3] = 8, [7] = 12 };

re-compile and look at the DWARF again via dwarfdump, I won't find any debug info describing otherenums- it is unused (in this compilation unit) and so it is elided.

重新编译并通过 再次查看 DWARF dwarfdump,我找不到任何描述的调试信息otherenums- 它未使用(在此编译单元中),因此被省略。

回答by jscs

As has been noted, the constant names are not accessible after compilation except in the debugger. H2CO3's suggestion of the TO_STR()macro should get you through most uses, I imagine.

如前所述,除了在调试器中之外,编译后无法访问常量名称。TO_STR()我想,H2CO3 对宏的建议应该可以帮助您完成大多数用途。

I was looking into libclang recently and decided to exercise it by addressing your complaint about the drudgery of translating from enum value to string "(you have to write it all out by hand / create a static array / put all the values into a dictionary and read them back ... etc)".

我最近正在研究 libclang 并决定通过解决您对从枚举值转换为字符串的苦差事的抱怨来练习它“(您必须手动将其全部写出来/创建一个静态数组/将所有值放入字典和读回它们......等)”。

Here's a Python script that will parse a Cocoa file, find the enums (including those defined with NS_OPTIONSand NS_ENUM), and spit out either an array or a function (using a switch) to do the mapping. The array option takes advantage of a C feature called "designated (array) initializers", whereby array members can be explicitly paired with a particular index:

这是一个 Python 脚本,它将解析 Cocoa 文件,找到枚举(包括用NS_OPTIONS和定义的枚举NS_ENUM),然后输出数组或函数(使用 a switch)来进行映射。数组选项利用称为“指定(数组)初始值设定项”的 C 功能,由此数组成员可以与特定索引显式配对:

#!/usr/bin/env python

"""
CocoaEnumToString

Parse a specified Cocoa header file and emit ObjC code to translate the values
of any enums found within into their names as NSStrings.
"""
# Copyright 2013 Joshua Caswell.
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import itertools
import argparse
import sys
import re
from os import path

from clang import cindex
from clang.cindex import CursorKind

def all_children(node):
    return itertools.chain(iter([node]), *map(all_children, 
                                              node.get_children()))

def all_constant_decls(enum):
    return iter(child for child in all_children(enum) if 
                    child.kind == CursorKind.ENUM_CONSTANT_DECL)

def indent_all_lines(s, indent):
    return '\n'.join(indent + line for line in s.split('\n'))

def format_anonymous(enum, title):
    const_str = 'NSString * const {} = @"{}";\n'
    constants = [const_str.format(title.replace('%e', constant.spelling),
                                  constant.spelling)
                            for constant in all_constant_decls(enum)]
    return "".join(constants)


def format_as_array(enum, title, indent):
    all_members = ['[{0}] = @"{0}"'.format(constant.spelling) 
                        for constant in all_constant_decls(enum)]
    all_members = ",\n".join(all_members)

    title = title.replace('%e', enum.spelling)
    array_str = "NSString * const {}[] = {{\n{}\n}};"
    return array_str.format(title, indent_all_lines(all_members, indent))

def format_as_func(enum, title, indent):
    case_str = 'case {0}:\n{1}return @"{0}";'
    all_cases = [case_str.format(constant.spelling, indent)
                        for constant in all_constant_decls(enum)]
    all_cases.append('default:\n{}@"";'.format(indent))
    all_cases = "\n".join(all_cases)

    switch = "switch( val ){{\n{}\n}}".format(indent_all_lines(all_cases, 
                                                               indent))
    title = title.replace('%e', enum.spelling)
    func_str = "NSString * {}({} val){{\n{}\n}}"
    return func_str.format(title, enum.spelling, 
                           indent_all_lines(switch, indent))


parser = argparse.ArgumentParser(description="Use libclang to find enums in "
                                  "the specified Objective-C file and emit a "
                                  "construct (array or function) that "
                                  "maps between the constant values and "
                                  "their names.")
# This argument must be added to the parser first for its default to override
# that of --arr and --fun
parser.add_argument("-c", "--construct", default="array",
                    help="Specify 'function' or any prefix ('f', 'fun', etc.) "
                         "to emit a function that uses a switch statement for "
                         "the mapping; specify 'array' or any prefix for "
                         "an array (this is the default). Whichever of -c, "
                         "--arr, or --fun occurs last in the argument list "
                         "will dictate the output.")
parser.add_argument("--arr", "--array", action="store_const", const="array",
                    dest="construct", help="Emit an array for the mapping.")
parser.add_argument("-e", "--enums", action="append",
                    help="Specify particular enums to capture; by default "
                    "all enums in the given file are used. This argument may "
                    "be present multiple times. Names which are not found in "
                    "the input file are ignored.")
parser.add_argument("--fun", "--func", "--function", action="store_const", 
                    const="function", dest="construct", 
                    help="Emit a function for the mapping.")
parser.add_argument("-i", "--indent", default="4s",
                    help="Number and type of character to use for indentation."
                    " Digits plus either 't' (for tabs) or 's' (for spaces), "
                    "e.g., '4s', which is the default.")
parser.add_argument("-n", "--name", default="StringFor%e",
                    help="Name for the construct; the prefix will "
# Escape percent sign because argparse is doing some formatting of its own.
                    "be added. Any appearances of '%%e' in this argument will "
                    "be replaced with each enum name. The default is "
                    "'StringFor%%e'.")
parser.add_argument("-o", "--output",
                    help="If this argument is present, output should go to a "
                    "file which will be created at the specified path. An "
                    "error will be raised if the file already exists.")
parser.add_argument("-p", "--prefix", default="",
                    help="Cocoa-style prefix to add to the name of emitted "
                    "construct, e.g. 'NS'")
parser.add_argument("file", help="Path to the file which should be parsed.")


arguments = parser.parse_args()

if "array".startswith(arguments.construct):
    format_enum = format_as_array 
elif "function".startswith(arguments.construct):
    format_enum = format_as_func
else:
   parser.error("Neither 'function' nor 'array' specified for construct.")

match = re.match(r"(\d*)([st])", arguments.indent)
if not match.group(2):
    parser.error("Neither tabs nor spaces specified for indentation.")
else:
    indent_char = '\t' if match.group(2) == 't' else ' '
    indent = indent_char * int(match.group(1) or 1)

if arguments.output:
    if path.exists(arguments.output):
        sys.stderr.write("Error: Requested output file exists: "
                         "{}\n".format(arguments.output))
        sys.exit(1)
    else:
        out_f = open(arguments.output, 'w')
else:
    out_f = sys.stdout

target_file_name = arguments.file


# Ignore the fact that system libclang probably doesn't match the version
# of the Python bindings.
cindex.Config.set_compatibility_check(False)
# Use what's likely to be a newer version than that found in /usr/lib
cindex.Config.set_library_file("/Applications/Xcode.app/Contents/Developer/"
                               "Toolchains/XcodeDefault.xctoolchain/usr/lib/"
                               "libclang.dylib")

# Preprocessor macros that resolve into enums; these are defined in
# NSObjCRuntime.h, but including that directly causes redefinition errors due
# to it being also imported.
ns_options_def = ("NS_OPTIONS(_type, _name)=enum _name : "
                 "_type _name; enum _name : _type")
ns_enum_def = ("NS_ENUM(_type, _name)=enum _name : _type _name; "
              "enum _name : _type")

tu = cindex.TranslationUnit.from_source(target_file_name, 
                                        args=["-ObjC", "-D", ns_enum_def,
                                              "-D", ns_options_def, "-D",
                                              "NS_ENUM_AVAILABLE="])


enums = [node for node in all_children(tu.cursor) if 
                node.kind == CursorKind.ENUM_DECL and
                node.location.file.name.find(target_file_name) != -1]
if arguments.enums:
    enums = filter(lambda enum: enum.spelling in arguments.enums, enums)

title = arguments.prefix + arguments.name

for enum in enums:
    if not enum.spelling:
        out_f.write(format_anonymous(enum, title))
    else:
        out_f.write(format_enum(enum, title, indent))
    out_f.write("\n\n")

Note that this is nota sparse array -- any non-specified indexes before the last are still created, and initialized with 0. The implication is that enums intended to be used as bitmasks, whose values are 1 << 2, 1 << 3, 1 << 4, and so on, will create fairly large arrays for relatively few used values. A function is probably the better choice in those cases.

请注意,这不是一个稀疏数组——仍会创建最后一个之前的任何未指定索引,并使用 0 进行初始化。这意味着打算用作位掩码的枚举,其值为1 << 21 << 31 << 4等,将为相对较少的使用值创建相当大的数组。在这些情况下,函数可能是更好的选择。

Anonymous enums (which I think are all single-member, in Foundation at least) are converted directly to a single NSStringusing the name of the constant. I've punted on the automatic handling of constants that are negative-valued, like NSOrderedAscending-- a negative index in the array initializer won't compile, but the function/switchalternative will work fine; you just have to choose that manually for those few cases.

Anonymous enum(我认为它们都是单一成员,至少在 Foundation 中)NSString使用常量的名称直接转换为单一成员。我已经对负值常量的自动处理进行了下注,例如NSOrderedAscending-- 数组初始值设定项中的负索引不会编译,但函数/switch替代方法可以正常工作;您只需要为这几种情况手动选择。

The script is up on GitHub, and here it is in its entirety. MIT license, so do with it what you like. I'll be happy to hear about any modifications.

该脚本在 GitHub 上,这里是它的全部内容。MIT 许可证,所以随心所欲。我很高兴听到任何修改。

##代码##