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

I come from a C# background where System.String is immutable and string concatenation is relatively expensive (as it requires reallocating the string) we know to use the StringBuilder type instead as it preallocates a larger buffer where single characters (Char, 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 ([Bool]) 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 NSMutableString is the equivalent of .NET's StringBuilder, 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 UnicodeScalar instead of Character, problem is NSMutableString does not let you append a UnicodeScalar value, you have to use Swift's own mutable String type, so now my code looks like:

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

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

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

3

1 Answer

(This answer was written based on documentation and source code valid for Swift 2 and 3: possibly needs updates and amendments once Swift 4 arrives)

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 ... 
10

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like