반응형

Swift에서 문자열 결합을 위해 joined(separator:) 사용하기

 

문자열을 다수 결합할 때 + 반복보다 joined(separator:) 가 빠르고 명확합니다. 내부적으로 버퍼를 효율적으로 관리해 불필요한 중간 문자열 생성을 줄이기 때문입니다.

 

1. 핵심 요약

  • 여러 문자열을 하나로 합칠 때는 joined(separator:)가 가독성·성능 모두에서 유리합니다.
  • reduce나 + 반복 결합은 반복적인 복사로 느려질 수 있습니다.
  • Substring·String 혼합, 숫자 변환, 지연 평가(lazy)와도 잘 어울립니다.

 

2. 기본 사용

let words = ["Hello", "Swift", "World"]
let sentence = words.joined(separator: " ")     // "Hello Swift World"

let noSep = words.joined()                      // "HelloSwiftWorld" (separator 기본값 "")

 

3. String/Substring 혼용

let text = "  A, B,  C  "
let tokens = text.split(separator: ",")                  // [Substring]
let trimmed = tokens.map { $0.trimmingCharacters(in: .whitespaces) } // [String]
let result = trimmed.joined(separator: " | ")            // "A | B | C"

split은 Substring을 내놓습니다. 바로 joined해도 되지만, 장기 보관할 값이면 String으로 변환을 권장합니다(원본 문자열의 저장 비용에서 분리).

 

4. 숫자·커스텀 타입 결합

문자열이 아닌 요소는 먼저 문자열로 변환하세요.

let nums = [10, 20, 30]
let csv = nums.map(String.init).joined(separator: ",") // "10,20,30"

고급 포맷팅(3자리 구분 등)은 NumberFormatter를 사용한 뒤 joined로 합칩니다.

import Foundation

let f = NumberFormatter()
f.numberStyle = .decimal // 1,234 형식
let out = [1234, 56789].compactMap { f.string(from: $0 as NSNumber) }
.joined(separator: /) // 1,234 / 56,789

 

5. 경로·URL 조합 팁

let components = ["Users", "me", "Documents", "file.txt"]
let path = components.joined(separator: "/")              // "Users/me/Documents/file.txt"

실제 파일 경로/URL은 URL(fileURLWithPath:)나 URL.appendingPathComponent 사용을 권장(플랫폼별 구분자 문제 방지). joined는 단순 문자열 결합일 때만.

 

6. 라인 결합 / CSV 생성

let header = ["id","name","age"].joined(separator: ",")
let rows = [
    ["1","Alice","28"],
    ["2","Bob","31"]
].map { $0.joined(separator: ",") }
let csv = ([header] + rows).joined(separator: "
")
/*
id,name,age
1,Alice,28
2,Bob,31
*/

 

7. reduce/+ 대비 성능 차이

let words = Array(repeating: "swift", count: 50_000)

// 비권장: 누적 복사 비용 큼
let s1 = words.reduce("") { $0 + ($0.isEmpty ? "" : " ") + $1 }

// 권장: 내부 버퍼 최적화
let s2 = words.joined(separator: " ")

joined는 전체 길이를 예측·버퍼를 한번에 확보하는 경향이 있어 대용량에서 유리합니다. (실측은 Release + 최적화에서)

 

8. lazy와 결합해 중간 배열 없애기

let raw = ["1"," two ","3 "]
let normalized = raw.lazy
    .map { $0.trimmingCharacters(in: .whitespaces) }  // 지연 변환
    .map(\.uppercased)
let out = normalized.joined(separator: ",")           // materialize 시점에 결합

 

9. ListFormatter로 지역화된 목록 문자열

사용자에게 보이는 자연어 목록 ListFormatter가 더 적합합니다.

import Foundation

let lf = ListFormatter()
lf.locale = Locale(identifier: ko_KR)
let pretty = lf.string(from: [사과, 배, 포도]) // 사과, 배 및 포도

UI 문맥에서는 joined(" / ") 같은 임의 구분자보다 지역화 규칙을 따르는 API를 고려하세요.

 

10. 줄바꿈·공백 처리 팁

let lines = ["  line1  ", "", "line3  "]
let clean = lines
    .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
    .filter { !$0.isEmpty }
    .joined(separator: "\n")
/*
line1
line3
*/

 

11. 긴 문자열 빌드가 잦다면

  • 잦은 누적/삽입이 필요하면 TextOutputStream 또는 String.reserveCapacity(_:)로 직접 버퍼 관리
  • 하지만 단순 결합이라면 joined가 대부분 최적
var s = ""
s.reserveCapacity(10_000)
for p in payloads { s += p }    // 혹은 s.append(contentsOf: payloads)

 

12. 예외·엣지 케이스

  • 빈 배열의 joined는 ""를 반환합니다.
  • 요소에 이미 구분자가 포함되어 있다면, 이중 구분 방지 로직이 필요할 수 있습니다.
  • 메모리 상한에 가까운 매우 큰 결합은 Data 스트리밍(파일 핸들, OutputStream)로 대체 고려.

 

13. 선택 가이드

상황 권장 방법
단순 다중 문자열 결합 joined(separator:)
숫자/커스텀 객체 결합 map(String.init)  joined
사용자 표시용 자연어 목록 ListFormatter
경로/URL URL API 사용
대용량 파이프라인 lazy + joined

 

14. 간단 벤치마크 스케치

import Foundation

let items = (0..<200_000).map { "v\($0)" }

func measure(_ name: String, _ body: () -> Void) {
    let t1 = CFAbsoluteTimeGetCurrent(); body(); let t2 = CFAbsoluteTimeGetCurrent()
    print(name, ":", t2 - t1, "sec")
}

measure("joined") { _ = items.joined(separator: ",") }
measure("reduce +") {
    _ = items.reduce("") { $0 + ($0.isEmpty ? "" : ",") + $1 }
}

 

15. 참조 링크

 

16. 결론

  • 문자열 다중 결합 = joined(separator:) 가 기본값입니다.
  • 숫자/포맷팅/지역화가 섞이면 변환(map) 또는 전용 API와 결합하세요.
  • 대용량/스트리밍은 lazy·TextOutputStream·URL/OutputStream 등 상황 맞춤형으로 보완하면 됩니다.

 

반응형
Posted by 까칠코더
,