String Swift
String Swift
ISBN 978-1-949080-06-3
Printed in IBM Plex, designed by Mike Abbink at IBM in collaboration with Bold Monday.
Additional glyphs provided by Google Noto Sans.
Twemoji graphics made by Twitter and other contributors.
Cover type set in Tablet Gothic, designed by José Scaglione & Veronika Burian.
Contents
Note: Swift has struggled with this tension throughout its development. Swift
2 changed String to remove conformance to Collection, instead exposing a
collection of characters through a characters property (similar to the Unicode
views provided by utf8, utf16, and unicodeScalars). However, this change
was later reverted in Swift 3.
In this chapter, we’ll untangle String from each of its adopted and
inherited protocols. By understanding the protocol-oriented nature
of String, you can leverage its functionality more effectively.
1. For clarity, some protocols are omitted from this diagram, including CustomPlaygroundQuick
Lookable, CustomReflectable, MirrorPath, and Codable.
Collection ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByStringLiteral
RangeReplaceableCollection BidirectionalCollection
ExpressibleByStringInterpolation
TextOutputStream Comparable
StringProtocol Equatable
TextOutputStreamable Hashable
String LosslessStringConvertible
CustomStringConvertible
CustomDebugStringConvertible
ExpressibleByExtendedGraphemeClusterLiteral
Comparable
Equatable
Hashable
Unicode.Scalar Character
TextOutputStreamable LosslessStringConvertible
CustomStringConvertible
CustomDebugStringConvertible
Sequence
A sequence is a list of values that provides sequential, iterated access
to elements one at a time.
Functional Programming
Sequence provides essential functional programming methods
like map(_:), compactMap(_:), filter(_:), and reduce(_:_:),
which allow you to apply the same functional paradigm to strings
that you would for any other data processing task.
Note: Don’t use split to tokenize natural language text into words, sentences, or
paragraphs. Instead, use the NLTokenizer class described in Chapter 7.
Finding Characters
Sequence provides the contains(where:) and first(where:)
methods, which you can use to determine whether a string contains
a particular character.
"airport".prefix(3) // "air"
"airport".prefix(while: { $0 != "r"}) // "ai"
Tip: You can use Swift’s trailing closure syntax when passing a closure as the final
argument to a function. In the case of functions like filter(_:) and map(_:)
the meaning of this closure is clear, so this alternative syntax is preferred.
However, when the semantics of the passed closure isn’t as clear, such as in
the case prefix(while:), forego the fancier syntax in favor of keeping the final
parameter label.
Excluding Characters
The dropFirst(_:) and dropLast(_:) methods offer another
approach to accessing substrings by excluding a given number of
characters from the start or end of a string.
Collection
A collection is an indexed sequence that can be traversed multiple
times from any index.
Accessing Indices
The extent of a collection is signified by its startIndex and end
Index properties. Collections also have an indices property that
returns a DefaultIndices collection of each index in order.
Reversing a String
Although Sequence provides an implementation of reversed(_:)
that creates an array populated with each element in reverse
order, the BidirectionalCollection override lets you reverse a
collection without allocating additional memory.
"airport".suffix(4) // "port"
RangeReplaceableCollection
A range-replaceable collection is a collection that can add, remove, and
replace elements.
Adding Characters
Use append(_:), insert(_:at:) to add characters to a string
variable.
Tip: If you’re building up a string value and know what the resulting length will be
ahead of time, you can call the reserveCapacity(_:) method after initializing
your string to reduce the number of additional memory allocations. However, as for
any optimization, it’s important to benchmark before and after to understand the
impact of such a change.
Replacing Characters
Use the replaceSubrange(_:with:) method to replace a range of
characters those from a different string.
StringProtocol
StringProtocol provides a common set of APIs to String and
Substring including initialization from C strings, non-localized
case transformation, and access to Unicode views.
string.withCString { pointer in
strlen(pointer)
} // 1
Transforming Case
You can use the uppercased() and lowercased() methods to
2
obtain the UPPERCASE and lowercase forms of strings.
"hello".uppercased() // "HELLO"
"HELLO".lowercased() // "hello"
extension StringProtocol {
var isPalindrome: Bool {
let normalized = self.lowercased()
.filter { $0.isLetter }
return normalized.elementsEqual(normalized.reversed())
}
}
CustomStringConvertible &
CustomDebugStringConvertible
The Swift standard library provides three distinct functions for
writing textual representations of values:
struct FlightCode {
let airlineCode: String
let flightNumber: Int
}
print(flight)
// AA 1
LosslessStringConvertible
String has more initializers than any other type in the standard
library. In previous versions of Swift, the String initializer that
is used to create textual representations of values took a single,
unlabeled argument that could be difficult to distinguish from other
custom initializers.
self.airlineCode = String(airlineCode)
self.flightNumber = flightNumber
}
}
String(flight)
// "AA 1"
FlightCode(String(flight))
// FlightCode(airlineCode: "AA", flightNumber: 1)
TextOutputStreamable &
TextOutputStream
The standard library print(_:) and dump(_:) functions write
values of any type conforming to the TextOutputStreamable to
standard output.
print(" ")
// Prints: " "
ExpressibleByUnicodeScalarLiteral,
ExpressibleByExtendedGraphemeClusterLiteral,
and ExpressibleByStringLiteral
In Swift, string literals are enclosed double quotes ("). The same
syntax can also express grapheme cluster literals when the enclosed
values comprise a single grapheme cluster or Unicode scalar literals
when the enclosed values comprise a single scalar value.
// ExpressibleByUnicodeScalarLiteral
let unicodeScalar: Unicode.Scalar = "A"
// ExpressibleByExtendedGraphemeClusterLiteral
let character: Character = "A"
// ExpressibleByStringLiteral
let string: String = "A"
ExpressibleByStringLiteral
ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByUnicodeScalarLiteral
Tip: Although each of the defined enumeration cases are expressible by Unicode
scalar literals, it’s generally advisable for custom types to adopt Expressible
ByStringLiteral even when ExpressibleByExtendedGraphemeCluster
Literal or ExpressibleByUnicodeScalarLiteral would be sufficient.
// Before
BookingClass(rawValue: "J")
// After
("J" as BookingClass) // Business Class
ExpressibleByStringInterpolation
One of the most anticipated new features in Swift 5 is the ability to
extend string interpolation to support configurable representations
4
of values.
5
First, define each available style in an enumeration.
enum Style {
case none
case bold
case italic
case boldItalic
case sansSerif(bold: Bool, italic: Bool)
case script(bold: Bool)
case fraktur(bold: Bool)
case doubleStruck
case monospace
}
5. Although the majority of these variants have code points that can be computed by adding a
constant offset, there are sufficiently many exceptions to justify the use of a lookup table.
typealias Variants = (
bold: Character,
italic: Character,
boldItalic: Character,
sansSerif: Character,
sansSerifBold: Character,
sansSerifItalic: Character,
sansSerifBoldItalic: Character,
script: Character,
scriptBold: Character,
fraktur: Character,
frakturBold: Character,
doubleStruck: Character,
monospace: Character
)
struct StyledString {
private var components: [(String, Style)] = []
Swift’s String type has a reputation for being difficult to work with.
Putting all of that aside, you might argue that this negative
reputation is mostly undeserved. Any perceived difficulty to work
6. Abrahams, Dave, and Cohen, Ben. “String Processing For Swift 4”. (2017) https://github.com/
apple/swift/blob/master/docs/StringManifesto.md, accessed October 2018.