Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

string

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

like image 657
Dai Avatar asked Apr 04 '16 06:04

Dai


People also ask

How do I combine strings in Swift?

In the apple documentation for swift it states that you concatenate 2 strings by using the additions operator e.g. : let string1 = "hello" let string2 = " there" var welcome = string1 + string2 4 // welcome now equals "hello there"

What is string interpolation in Swift?

Swift String Interpolation 2) Means the string is created from a mix of constants, variables, literals or expressions. Example: let length:Float = 3.14 var breadth = 10 var myString = "Area of a rectangle is length*breadth" myString = "\(myString) i.e. = \(length)*\(breadth)"

What are raw strings Swift?

Raw strings (denoted by starting and ending pound symbols ( # ) before and after the quotation marks), allow us to create strings that will print exactly what you see. As you know, there are certain escape sequences that can make our strings print differently.


1 Answers

(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

  • swift/stdlib/public/core/String.swift

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 ... 
like image 186
dfrib Avatar answered Sep 29 '22 23:09

dfrib