objective-c - 使用 Objective-C @encode 检查任意 C 类型的相等性——已经存在了吗?

标签 objective-c

我正在寻找一些代码来对 Objective-C 的 @encode() 指令支持的任意 C 类型进行通用相等性比较。我本质上是在寻找类似以下的功能:

BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char* bEnc)

你可以这样调用它:

struct { ... } foo = { yadda, yadda, yadda };
struct { ... } bar = { yadda, yadda, yadda };

BOOL isEqual = CTypesEqual(&foo, @encode(typeof(foo)), &bar, @encode(typeof(bar)));

这是我到目前为止的发现:

启示录 #1

你不能这样做:

BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
    if (0 != strcmp(aEnc, bEnc)) // different types
        return NO;

    NSUInteger size = 0, align = 0;
    NSGetSizeAndAlignment(aEnc, &size, &align);
    if (0 != memcmp(a, b, size))
        return NO;

    return YES;
}

...因为对齐限制在成员之间的空间中产生了垃圾。例如,以下将无法通过基于 memcmp 的相等性检查,尽管这两个结构对我来说是相等的:

typedef struct {
    char first;
    NSUInteger second;
} FooType;

FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));

a.first = 'a';
a.second = ~0;

b.first = 'a';
b.second = ~0;

启示录 #2

你可以滥用 NSCoder 来做到这一点,像这样:

BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
    if (0 != strcmp(aEnc, bEnc)) // different types
        return NO;

    NSMutableData* aData = [[NSMutableData alloc] init];
    NSArchiver* aArchiver = [[NSArchiver alloc] initForWritingWithMutableData: aData];
    [aArchiver encodeValueOfObjCType: aEnc at: a];

    NSMutableData* bData = [[NSMutableData alloc] init];
    NSArchiver* bArchiver = [[NSArchiver alloc] initForWritingWithMutableData: bData];
    [bArchiver encodeValueOfObjCType: bEnc at: b];

    return [aData isEqual: bData];
}

一切都很好,并提供了预期的结果,但结果谁知道有多少堆分配(至少 6 个)并使操作应该相对便宜,非常昂贵。

启示录#3

你不能为此使用 NSValue。如下所示,以下内容不起作用:

typedef struct {
    char first;
    NSUInteger second;
} FooType;

FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));

a.first = 'a';
a.second = 0xFFFFFFFFFFFFFFFF;

b.first = 'a';
b.second = 0xFFFFFFFFFFFFFFFF;

NSValue* aVal = [NSValue valueWithBytes: &a objCType: @encode(typeof(a))];
NSValue* bVal = [NSValue valueWithBytes: &b objCType: @encode(typeof(b))];

BOOL isEqual = [aVal isEqual: bVal];

启示录 #4

Cocotron's NSCoder implementation对所有困难的东西(任意 structunion 等)下注,因此没有进一步灵感的来源。

到目前为止我的尝试

所以我开始研究这个,手头有文档,我大致做到了这一点:

BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
    if (0 != strcmp(aEnc, bEnc)) // different types
        return NO;

    return SameEncCTypesEqual(a, b, aEnc);
}

static BOOL SameEncCTypesEqual(void* a, void* b, const char* enc)
{
    switch (enc[0])
    {
        case 'v':
        {
            // Not sure this can happen, but...
            return YES;
        }
        case 'B':
        case 'c':
        case 'C':
        case 's':
        case 'S':
        case 'i':
        case 'I':
        case 'l':
        case 'L':
        case 'q':
        case 'Q':
        case 'f':
        case 'd':
        case '@':
        case '#':
        {
            NSUInteger size = 0, align = 0;
            NSGetSizeAndAlignment(enc, &size, &align);
            const int result = memcmp(a, b, size);
            if (result)
                return NO;
            break;
        }
        case ':':
        {
            if (!sel_isEqual(*(SEL*)a, *(SEL*)b))
                return NO;
        }

        case '*':
        {
            if (strcmp((const char *)a, (const char *)b))
                return NO;
        }
        case '{':
        {
            // Get past the name
            for (const char *prev = enc - 1, *orig = enc; prev < orig || (prev[0] != '=' && prev[0] != '\0' && enc[0] != '}'); prev++, enc++);

            // Chew through it
            for (NSUInteger pos = 0, size = 0, align = 0; enc[0] != '}' && enc[0] != '\0'; enc++, pos += size, size = 0, align = 0)
            {
                NSGetSizeAndAlignment(enc, &size, &align);

                // figure out where we should be w/r/t alignment
                pos = align * (pos + align - 1) / align;

                // Descend
                BOOL sub = SameEncCTypesEqual(((uint8_t*)a) + pos, ((uint8_t*)b) + pos, enc);
                if (!sub)
                    return NO;
            }
            break;
        }
        case '[':
        {
            // Skip the '['
            enc++;

            // Get numElements
            int numElements = 0;
            sscanf(enc, "%d", &numElements);

            // Advance past the number
            for (; enc[0] <= '9' && enc[0] >= '0'; enc++);

            // Get the size
            NSUInteger size = 0, align = 0;
            const char * const elementType = enc;
            NSGetSizeAndAlignment(elementType, &size, &align);

            for (NSUInteger i = 0; i < numElements; i++)
            {
                BOOL elementEqual = SameEncCTypesEqual(((uint8_t*)a) + i * size, ((uint8_t*)b) + i * size, elementType);
                if (!elementEqual)
                    return NO;
            }
            break;
        }
        case '(':
        {
            NSLog(@"unions?! seriously, bro?");
            return NO;
            break;
        }
        default:
        {
            NSLog(@"Unknown type: %s", enc);
            return NO;
            break;
        }
    }
    return YES;
}

...当我开始使用 union 时,我对自己说,“Self,你为什么要这样做?这正是那种有上百万个小角落案例要遗漏的代码,而且真的,它看起来像一些应该已经被其他更有耐心的人写过很多次的东西。”所以我在这里。任何人都知道在(公共(public))框架或“野外”中经过实践检验的这个实现没有使用 NSCoder 的所有额外重量吗?

最佳答案

为什么不直接创建一个跳过填充的函数呢?

首先,您需要猜测填充策略。这是简单的部分。

然后你使用@encode提供的编码信息将数据类型映射到掩码,并使用这些掩码进行比较。

一个简单的例子:

struct m { char a; int b; };
struct n { char c; struct m d; int e; };

可以转换为(假设 sizeof(int) 为 4):

struct_m_mask = { 1, 4, 0 };
struct_n_mask = { 1, 1, 4, 4, 0 };

在对齐允许的情况下,表示的优化当然是可能的,例如:

struct_b_mask = { 2, 4, 4, 0 };

然后,您可以遍历这个数组来进行比较。 A[n+1] - A[n] 给出孔的大小,如果前面没有孔,比如 (b, e),那么你可以合并它们。

这是我能想到的最简单的方法。也许您可以实现更复杂的技巧。

顺便说一句,我的猜测是假设这些东西是常量,编译器可以在编译时计算掩码,但也许这要求太高了......

关于objective-c - 使用 Objective-C @encode 检查任意 C 类型的相等性——已经存在了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20169425/

相关文章:

ios - 如何在 iOS 中创建自定义控件?

iphone - 动画导航栏标题

html - 如何获得 HTML 属性文本的真实高度

ios - 为什么自定义字体会移动文本(更改垂直对齐方式)?

ios - Swift 中的 CorePlot 无法调用 “numberForPlot”

objective-c - 使用多个分隔符拆分 NSString?

ios - NSDictionary 被视为 bool 值 - 无法调试

ios - ObjC : +[NSObject isSubclassOfClass:] gives incorrect failure

ios - 如何使 UIView 动画序列重复和自动反转

objective-c - XCode 4 存档/IPA 错误 : "The operation couldn’ t be completed. 没有这样的文件或目录”