Mic*_*all 3 foundation swift character-set
在与CharacterSet我合作时遇到了一个有趣的问题.从我到目前为止收集的CharacterSet是基于UnicodeScalar; 您可以使用标量初始化它并检查集合中是否包含标量.查询集合以查明它是否包含a Character,谁的字形可能由几个unicode标量值组成,这没有意义.
我的问题在于我使用表情符号测试,这是一个单一的unicode标量值(十进制128518).因为这是一个单一的unicode标量值,我认为它会起作用,结果如下:
"" == UnicodeScalar(128518)! // true
// A few variations to show exactly what is being set up
let supersetA = CharacterSet(charactersIn: "")
let supersetB = CharacterSet(charactersIn: "A")
let supersetC = CharacterSet(charactersIn: UnicodeScalar(128518)!...UnicodeScalar(128518)!)
let supersetD = CharacterSet(charactersIn: UnicodeScalar(65)...UnicodeScalar(65)).union(CharacterSet(charactersIn: UnicodeScalar(128518)!...UnicodeScalar(128518)!))
supersetA.contains(UnicodeScalar(128518)!) // true
supersetB.contains(UnicodeScalar(128518)!) // false
supersetC.contains(UnicodeScalar(128518)!) // true
supersetD.contains(UnicodeScalar(128518)!) // false
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,如果CharacterSet包含单个标量值(可能是由于优化),则检查有效,但在任何其他情况下,它都无法按预期工作.
我找不到任何有关较低级别实现的信息,CharacterSet或者它是否以某种编码工作(即UTF-16之类的NSString),但由于API处理很多,UnicodeScalar我很惊讶它失败了,我不确定为什么会发生这种情况,或者如何进一步调查.
任何人都可以阐明为什么会这样吗?
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)
}
}
Run Code Online (Sandbox Code Playgroud)
所以它基本上只是打电话来CFCharacterSetIsLongCharacterMember.它的源代码也可用,虽然仅适用于Yosemite(El Cap和Sierra的版本都说"即将推出").然而,Yosemite代码似乎与我在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);
}
Run Code Online (Sandbox Code Playgroud)
所以我们可以跟进,弄清楚发生了什么.不幸的是,我们必须破坏我们的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;
};
Run Code Online (Sandbox Code Playgroud)
我们也需要知道它到底CFRuntimeBase是什么:
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
Run Code Online (Sandbox Code Playgroud)
你猜怎么着!我们还需要一些常量.
enum {
__kCFCharSetClassTypeMask = 0x0070,
__kCFCharSetClassBuiltin = 0x0000,
__kCFCharSetClassRange = 0x0010,
__kCFCharSetClassString = 0x0020,
__kCFCharSetClassBitmap = 0x0030,
__kCFCharSetClassSet = 0x0040,
__kCFCharSetClassCompactBitmap = 0x0040,
// irrelevant stuff redacted
};
Run Code Online (Sandbox Code Playgroud)
然后我们可以打破CFCharacterSetIsLongCharacterMember并记录结构:
supersetA.contains(UnicodeScalar(128518)!)
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 90190000 02000000 00000000 00000000 06f60100 00000000 01000000 00000000 00000000 00000000>
Run Code Online (Sandbox Code Playgroud)
根据上面的结构,我们可以弄清楚这个字符集是由什么构成的.在这种情况下,相关部分将是cfinfofrom 的第一个字节CFRuntimeBase,即字节9-12.它的第一个字节0x90包含字符集的类型信息.它需要AND编辑,这是__kCFCharSetClassTypeMask我们的0x10,这是__kCFCharSetClassRange.
对于这一行:
supersetB.contains(UnicodeScalar(128518)!)
Run Code Online (Sandbox Code Playgroud)
结构是:
(lldb) po [NSData dataWithBytes:$rdi length:48]
<21b3d2ad ffff1d00 a0190000 02000000 00000000 00000000 9066f000 01000000 02000000 00000000 00000000 00000000>
Run Code Online (Sandbox Code Playgroud)
此时字节9是0xa0,其AND与掩模ED是0x20,__kCFCharSetClassString.
在这一点上,Monty Python演员正在尖叫着"Get On With It!",所以让我们来CFCharacterSetIsLongCharacterMember看看源代码,看看发生了什么.
跳过所有CF_OBJC_FUNCDISPATCHV废话,我们到达这一行:
if (plane) {
Run Code Online (Sandbox Code Playgroud)
在两种情况下,这显然都是真实的.下一个测试:
if (__CFCSetIsBuiltin(theSet)) {
Run Code Online (Sandbox Code Playgroud)
在两种情况下,这都评估为false,因为两种类型都不是__kCFCharSetClassBuiltin,所以我们跳过该块.
isAnnexInverted = __CFCSetAnnexIsInverted(theSet);
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,_annex指针都为null(请参阅结构末尾的所有零),所以这是false.
此测试将true出于同样的原因:
if ((annexPlane = __CFCSetGetAnnexPlaneCharacterSetNoAlloc(theSet, plane)) == NULL) {
Run Code Online (Sandbox Code Playgroud)
带我们去:
if (!__CFCSetHasNonBMPPlane(theSet) && __CFCSetIsRange(theSet)) {
Run Code Online (Sandbox Code Playgroud)
该__CFCSetHasNonBMPPlane宏检查_annex,所以这是假的.当然,表情符号不在BMP平面中,所以对于这两种情况来说这实际上似乎都是错误的,即使是返回正确结果的情况也是如此.
__CFCSetIsRange检查我们的类型是否__kCFCharSetClassRange是第一次.所以这是我们的分歧点.第二次调用它会产生不正确的结果,在下一行返回:
return (isAnnexInverted ? true : false);
Run Code Online (Sandbox Code Playgroud)
而且,由于该附件是NULL,造成isAnnexInverted是假的,这个返回false.
至于如何解决它......好吧,我不能.但现在我们知道它为什么会发生.据我所知,主要的问题是在_annex创建字符集时字段没有被填充,并且由于附件似乎用于跟踪非BMP平面中的字符,我认为它应该是存在两个字符集.顺便说一句,如果你决定这个信息将可能是一个错误报告有用的文件,一个(我会文件,它针对的CoreFoundation,因为这是在实际的问题是).