实际上,源代码CharacterSet 是可用的。的来源contains是:
fileprivate func contains(_ member: Unicode.Scalar) -> Bool {
    switch _backing {
    case .immutable(let cs):
        return CFCharacterSetIsLongCharacterMember(cs, member.value)
    case .mutable(let cs):
        return CFCharacterSetIsLongCharacterMember(cs, member.value)
    }
}
所以它基本上只是调用 to CFCharacterSetIsLongCharacterMember。源代码也可用,但仅适用于优胜美地(El Cap 和 Sierra 的版本都说“即将推出”)。然而,优胜美地代码似乎与我在 Sierra 的反汇编中看到的一致。无论如何,它的代码如下所示:
Boolean CFCharacterSetIsLongCharacterMember(CFCharacterSetRef theSet, UTF32Char theChar) {
    CFIndex length;
    UInt32 plane = (theChar >> 16);
    Boolean isAnnexInverted = false;
    Boolean isInverted;
    Boolean result = false;
    CF_OBJC_FUNCDISPATCHV(__kCFCharacterSetTypeID, Boolean, (NSCharacterSet *)theSet, longCharacterIsMember:(UTF32Char)theChar);
    __CFGenericValidateType(theSet, __kCFCharacterSetTypeID);
    if (plane) {
        CFCharacterSetRef annexPlane;
        if (__CFCSetIsBuiltin(theSet)) {
            isInverted = __CFCSetIsInverted(theSet);
            return (CFUniCharIsMemberOf(theChar, __CFCSetBuiltinType(theSet)) ? !isInverted : isInverted); 
        }
        isAnnexInverted = __CFCSetAnnexIsInverted(theSet);
        if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) {
            if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) {
                isInverted = __CFCSetIsInverted(theSet);
                length = __CFCSetRangeLength(theSet);
                return (length && __CFCSetRangeFirstChar(theSet) <= theChar && theChar < __CFCSetRangeFirstChar(theSet) + length ? !isInverted : isInverted);
            } else {
                return (isAnnexInverted ? true : false);
            }
        } else {
            theSet = annexPlane;
            theChar &= 0xFFFF;
        }
    }
    isInverted = __CFCSetIsInverted(theSet);
    switch (__CFCSetClassType(theSet)) {
        case __kCFCharSetClassBuiltin:
            result = (CFUniCharIsMemberOf(theChar, __CFCSetBuiltinType(theSet)) ? !isInverted : isInverted);
            break;
        case __kCFCharSetClassRange:
            length = __CFCSetRangeLength(theSet);
            result = (length && __CFCSetRangeFirstChar(theSet) <= theChar && theChar < __CFCSetRangeFirstChar(theSet) + length ? !isInverted : isInverted);
            break;
        case __kCFCharSetClassString:
            result = ((length = __CFCSetStringLength(theSet)) ? (__CFCSetBsearchUniChar(__CFCSetStringBuffer(theSet), length, theChar) ? !isInverted : isInverted) : isInverted);
            break;
        case __kCFCharSetClassBitmap:
            result = (__CFCSetCompactBitmapBits(theSet) ? (__CFCSetIsMemberBitmap(__CFCSetBitmapBits(theSet), theChar) ? true : false) : isInverted);
            break;
        case __kCFCharSetClassCompactBitmap:
            result = (__CFCSetCompactBitmapBits(theSet) ? (__CFCSetIsMemberInCompactBitmap(__CFCSetCompactBitmapBits(theSet), theChar) ? true : false) : isInverted);
            break;
        default:
            CFAssert1(0, __kCFLogAssertion, "%s: Internal inconsistency error: unknown character set type", __PRETTY_FUNCTION__); // We should never come here
            return false; // To make compiler happy
    }
    return (result ? !isAnnexInverted : isAnnexInverted);
}
所以我们可以跟着,弄清楚发生了什么。不幸的是,我们必须发挥我们的 x86_64 组装技能才能做到这一点。但不要害怕,因为我已经为你做了这个,因为显然这是我在星期五晚上做的事情。
一个有用的东西是数据结构:
struct __CFCharacterSet {
    CFRuntimeBase _base;
    CFHashCode _hashValue;
    union {
        struct {
            CFIndex _type;
        } _builtin;
        struct {
            UInt32 _firstChar;
            CFIndex _length;
        } _range;
        struct {
            UniChar *_buffer;
            CFIndex _length;
        } _string;
        struct {
            uint8_t *_bits;
        } _bitmap;
        struct {
            uint8_t *_cBits;
        } _compactBitmap;
   } _variants;
   CFCharSetAnnexStruct *_annex;
};
我们还需要知道到底CFRuntimeBase是什么:
typedef struct __CFRuntimeBase {
    uintptr_t _cfisa;
    uint8_t _cfinfo[4];
#if __LP64__
    uint32_t _rc;
#endif
} CFRuntimeBase;
你猜怎么着!我们还需要一些常量。
enum {
        __kCFCharSetClassTypeMask = 0x0070,
            __kCFCharSetClassBuiltin = 0x0000,
            __kCFCharSetClassRange = 0x0010,
            __kCFCharSetClassString = 0x0020,
            __kCFCharSetClassBitmap = 0x0030,
            __kCFCharSetClassSet = 0x0040,
            __kCFCharSetClassCompactBitmap = 0x0040,
    // irrelevant stuff redacted
};
然后我们可以中断CFCharacterSetIsLongCharacterMember并记录结构:
supersetA.contains(UnicodeScalar(128518)!)
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 90190000 02000000 00000000 00000000 06f60100 00000000 01000000 00000000 00000000 00000000>
根据上面的结构,我们可以弄清楚这个字符集是由什么组成的。在这种情况下,相关部分将是cfinfofrom的第一个字节CFRuntimeBase,即字节 9-12。它的第一个字节0x90包含字符集的类型信息。它需要ANDed with __kCFCharSetClassTypeMask,这得到了我们0x10,也就是__kCFCharSetClassRange。
对于这一行:
supersetB.contains(UnicodeScalar(128518)!)
结构是:
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 a0190000 02000000 00000000 00000000 9066f000 01000000 02000000 00000000 00000000 00000000>
这次第 9 个字节是0xa0,它AND与掩码一起是0x20, __kCFCharSetClassString。
在这一点上,巨蟒剧组正在尖叫“继续前进!”,所以让我们通过源代码CFCharacterSetIsLongCharacterMember看看发生了什么。
跳过所有的CF_OBJC_FUNCDISPATCHV废话,我们来到这一行:
if (plane) {
这显然在这两种情况下都为真。下一个测试:
if (__CFCSetIsBuiltin(theSet)) {
这在两种情况下都为 false,因为这两种类型都不是__kCFCharSetClassBuiltin,所以我们跳过该块。
isAnnexInverted = __CFCSetAnnexIsInverted(theSet);
在这两种情况下,_annex指针都是空的(查看结构末尾的所有零),所以这是false.
该测试将true出于相同的原因:
if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) {
带我们去:
if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) {
__CFCSetHasNonBMPPlane宏检查_annex,所以这是错误的。当然,表情符号不在 BMP 平面中,因此这两种情况实际上似乎都是错误的,即使是返回正确结果的情况也是如此。
__CFCSetIsRange检查我们的类型是否为__kCFCharSetClassRange,这仅在第一次为真。所以这是我们的分歧点。产生不正确结果的第二次调用在下一行返回:
return (isAnnexInverted ? true : false);
并且由于附件是NULL,导致isAnnexInverted为假,因此返回假。
至于如何解决它......好吧,我不能。但现在我们知道为什么会这样了。据我所知,主要问题是_annex创建字符集时没有填充该字段,并且由于附件似乎用于跟踪非 BMP 平面中的字符,我认为应该是存在于两个字符集。顺便说一句,如果您决定提交一份错误报告,这些信息可能会有所帮助(我会向 CoreFoundation 提交,因为这是实际问题所在)。