Dai Dai - 3 months ago 10
Swift Question

Fastest, leanest way to append characters to form a string in Swift

I come from a C# background where

is immutable and string concatenation is relatively expensive (as it requires reallocating the string) we know to use the
type instead as it preallocates a larger buffer where single characters (
, a 16-bit value-type) and short strings can be concatenated cheaply without extra allocation.

I'm porting some C# code to Swift which reads from a bit-array (
) at sub-octet indexes with character lengths less than 8 bits (it's a very space-conscious file format).

My C# code does something like this:

StringBuilder sb = new StringBuilder( expectedCharacterCount );
int idxInBits = 0;
Boolean[] bits = ...;
for(int i = 0; i < someLength; i++) {
Char c = ReadNextCharacter( ref idxInBits, 6 ); // each character is 6 bits in this example
sb.Append( c );

In Swift, I assume
is the equivalent of .NET's
, and I found this QA about appending individual characters ( How to append a character to string in Swift? ) so in Swift I have this:

var buffer: NSMutableString
for i in 0..<charCount {
let charValue: Character = readNextCharacter( ... )
buffer.AppendWithFormat("%c", charValue)
return String(buffer)

But I don't know why it goes through a format-string first, that seems inefficient (reparsing the format-string on every iteration) and as my code is running on iOS devices I want to be very conservative with my program's CPU and memory usage.

As I was writing this, I learned my code should really be using
instead of
, problem is
does not let you append a
value, you have to use Swift's own mutable
type, so now my code looks like:

var buffer: String
for i in 0..<charCount {
let x: UnicodeScalar = readNextCharacter( ... )
return buffer

I thought that
was immutable, but I noticed its
method returns

I still feel uncomfortable doing this because I don't know how Swift's
type is implemented internally, and I don't see how I can preallocate a large buffer to avoid reallocations (assuming Swift's
uses a growing algorithm).


Since Swift is now open-source, we can actually have a look at the source code for Swift:s native String

From the source above, we have following comment

/// Growth and Capacity
/// ===================
/// When a string's contiguous storage fills up, new storage must be
/// allocated and characters must be moved to the new storage.
/// `String` uses an exponential growth strategy that makes `append` a
/// constant time operation *when amortized over many invocations*.

Given the above, you shouldn't need to worry about the performance of appending characters in Swift (be it via append(_: Character), append(_: UniodeScalar) or appendContentsOf(_: String)), as reallocation of the contiguous storage for a certain String instance should not be very frequent w.r.t. number of single characters needed to be appended for this re-allocation to occur.

Also note that NSMutableString is not "purely native" Swift, but belong to the family of bridged Obj-C classes (accessible via Foundation).

A note to your comment

"I thought that String was immutable, but I noticed its append method returns Void."

String is just a (value) type, that may be used by mutable as well as immutable properties

var foo = "foo" // mutable 
let bar = "bar" // immutable
    /* (both the above inferred to be of type 'String') */

The mutating void-return instance methods append(_: Character) and append(_: UniodeScalar) are accessible to mutable as well as immutable String instances, but naturally using them with the latter will yield a compile time error

let chars : [Character]  = ["b","a","r"]
foo.append(chars[0]) // "foob"
bar.append(chars[0]) // error: cannot use mutating member on immutable value ...