Dies hängt damit zusammen, wie der String
Typ in Swift funktioniert und wie die contains(_:)
Methode funktioniert.
Das '👩👩👧👦' ist eine sogenannte Emoji-Sequenz, die als ein sichtbares Zeichen in einer Zeichenfolge gerendert wird. Die Sequenz besteht aus Character
Objekten und gleichzeitig aus UnicodeScalar
Objekten.
Wenn Sie die Zeichenanzahl der Zeichenfolge überprüfen, sehen Sie, dass sie aus vier Zeichen besteht. Wenn Sie die Anzahl der Unicode-Skalare überprüfen, wird ein anderes Ergebnis angezeigt:
print("👩👩👧👦".characters.count) // 4
print("👩👩👧👦".unicodeScalars.count) // 7
Wenn Sie nun die Zeichen analysieren und ausdrucken, sehen Sie, was wie normale Zeichen aussieht. Tatsächlich enthalten die drei ersten Zeichen jedoch sowohl einen Emoji- als auch einen Joiner mit einer Breite von Null in ihren UnicodeScalarView
:
for char in "👩👩👧👦".characters {
print(char)
let scalars = String(char).unicodeScalars.map({ String($0.value, radix: 16) })
print(scalars)
}
// 👩
// ["1f469", "200d"]
// 👩
// ["1f469", "200d"]
// 👧
// ["1f467", "200d"]
// 👦
// ["1f466"]
Wie Sie sehen können, enthält nur das letzte Zeichen keinen Joiner mit der Breite Null. Wenn Sie die contains(_:)
Methode verwenden, funktioniert sie also wie erwartet. Da Sie nicht mit Emoji vergleichen, die Joiner mit einer Breite von Null enthalten, findet die Methode nur für das letzte Zeichen eine Übereinstimmung.
Wenn Sie String
ein Emoji-Zeichen erstellen, das mit einem Joiner mit der Breite Null endet, und es an die contains(_:)
Methode übergeben, wird es ebenfalls ausgewertet false
. Dies hat damit zu tun, contains(_:)
dass es genau dasselbe ist wie range(of:) != nil
, was versucht, eine genaue Übereinstimmung mit dem gegebenen Argument zu finden. Da Zeichen, die mit einem Joiner mit der Breite Null enden, eine unvollständige Sequenz bilden, versucht die Methode, eine Übereinstimmung für das Argument zu finden, während Zeichen, die mit Joinern mit der Breite Null enden, zu einer vollständigen Sequenz kombiniert werden. Dies bedeutet, dass die Methode niemals eine Übereinstimmung findet, wenn:
- Das Argument endet mit einem Joiner mit der Breite Null und
- Die zu analysierende Zeichenfolge enthält keine unvollständige Sequenz (dh sie endet mit einem Joiner mit der Breite Null und wird nicht von einem kompatiblen Zeichen gefolgt).
Demonstrieren:
let s = "\u{1f469}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}" // 👩👩👧👦
s.range(of: "\u{1f469}\u{200d}") != nil // false
s.range(of: "\u{1f469}\u{200d}\u{1f469}") != nil // false
Da der Vergleich jedoch nur nach vorne schaut, können Sie mehrere andere vollständige Sequenzen innerhalb der Zeichenfolge finden, indem Sie rückwärts arbeiten:
s.range(of: "\u{1f466}") != nil // true
s.range(of: "\u{1f467}\u{200d}\u{1f466}") != nil // true
s.range(of: "\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}") != nil // true
// Same as the above:
s.contains("\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}") // true
Die einfachste Lösung wäre, eine bestimmte Vergleichsoption für die range(of:options:range:locale:)
Methode bereitzustellen . Die Option String.CompareOptions.literal
führt den Vergleich mit einer genauen Zeichen-für-Zeichen-Äquivalenz durch . Nebenbei bemerkt, was hier mit Zeichen gemeint ist, ist nicht der Swift Character
, sondern die UTF-16-Darstellung sowohl der Instanz als auch der Vergleichszeichenfolge. Da String
jedoch keine fehlerhafte UTF-16 zulässig ist, entspricht dies im Wesentlichen dem Vergleich des Unicode-Skalars Darstellung.
Hier habe ich die Foundation
Methode überladen. Wenn Sie also die ursprüngliche Methode benötigen, benennen Sie diese oder etwas anderes um:
extension String {
func contains(_ string: String) -> Bool {
return self.range(of: string, options: String.CompareOptions.literal) != nil
}
}
Jetzt funktioniert die Methode mit jedem Zeichen so, wie es "sollte", auch bei unvollständigen Sequenzen:
s.contains("👩") // true
s.contains("👩\u{200d}") // true
s.contains("\u{200d}") // true