Search code examples
swiftstringswift3string-concatenationstring-interpolation

Difference between String interpolation and String concatenation


When specifically dealing with non-optional String values, what could be the difference between String interpolation and String concatenation?

struct MyModel {
    let value1: String
    let value2: String
    var displayNameByConcatenation: String {
        return value1 + "-" + value2
    }
    var displayNameByInterpolation: String {
        return "\(value1)-\(value2)"
    }
}
  • Is there going to be any case where displayNameByConcatenation and displayNameByInterpolation are different? Like on long unicode strings?
  • Is it possible to somehow override the behavior of operator + or the behavior of interpolation to make them different in above example?
  • Is one faster/slower than the other?

Note that from this question we learn that string interpolation will use the description of a CustomStringConvertible. But does String concatenation (operator +) also calls the description?


Solution

  • From a speed point of view, to differentiate concatenation (value1 + "-" + value2) and interpolation ("\(value1)-\(value2)"), results may depend on the number of operations done to obtain the final string.

    My results on an iPhone 8 show that:

    • if there is roughly < 30 substrings to attach together, then concatenation is faster
    • if there is roughly > 30 substrings to attach together, then interpolation is faster

    Thank you Sirens for figuring out that one wasn't always faster than the other!

    Try it yourself (and don't forget to adapt the tested character set and iterations for your needs):

    import UIKit
    
    class ViewController: UIViewController {
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            DispatchQueue.global(qos: .default).async {
                ViewController.buildDataAndTest()
            }
        }
    
        private static func buildDataAndTest(times: Int = 1_000) {
            let characterSet = CharacterSet.alphanumerics
            characterSet.cacheAllCharacters()
            let data: [(String, String)] = (0 ..< times).map { _ in
                 (characterSet.randomString(length: 50), characterSet.randomString(length: 20))
            }
            _ = testCIA(data)
            _ = testInterpol(data)
            print("concatenation: " + String(resultConcatenation))
            print("interpolation: \(resultInterpolation)")
        }
    
        /// concatenation in array
        static var resultConcatenation: CFTimeInterval = 0
        private static func testCIA(_ array: [(String, String)]) -> String {
            var foo = ""
            let start = CACurrentMediaTime()
            for (a, b) in array {
                foo = foo + " " + a + "+" + b
            }
            resultConcatenation = CACurrentMediaTime() - start
            return foo
        }
    
        /// interpolation
        static var resultInterpolation: CFTimeInterval = 0
        private static func testInterpol(_ array: [(String, String)]) -> String {
            var foo = ""
            let start = CACurrentMediaTime()
            for (a, b) in array {
                foo = "\(foo) \(a)+\(b)"
            }
            resultInterpolation = CACurrentMediaTime() - start
            return foo
        }
    }
    
    extension CharacterSet {
        static var cachedCharacters: [Character] = []
    
        public func cacheAllCharacters() {
            CharacterSet.cachedCharacters = characters()
        }
    
        /// extracting characters
        /// https://stackoverflow.com/a/52133647/1033581
        public func characters() -> [Character] {
            return codePoints().compactMap { UnicodeScalar($0) }.map { Character($0) }
        }
        public func codePoints() -> [Int] {
            var result: [Int] = []
            var plane = 0
            for (i, w) in bitmapRepresentation.enumerated() {
                let k = i % 8193
                if k == 8192 {
                    plane = Int(w) << 13
                    continue
                }
                let base = (plane + k) << 3
                for j in 0 ..< 8 where w & 1 << j != 0 {
                    result.append(base + j)
                }
            }
            return result
        }
    
        // http://stackoverflow.com/a/42895178/1033581
        public func randomString(length: Int) -> String {
            let charArray = CharacterSet.cachedCharacters
            let charArrayCount = UInt32(charArray.count)
            var randomString = ""
            for _ in 0 ..< length {
                randomString += String(charArray[Int(arc4random_uniform(charArrayCount))])
            }
            return randomString
        }
    }