[Swift] String 나누는법! split vs components

별똥별·2025년 3월 12일

(침착맨의 3분할)

🚀 Swift에서 문자열을 나누는 두가지 방법(split vs components)

안녕하세요, 별똥별🌠입니다!
최근 프로그래머스 에서 알고리즘 문제를 풀어보고 있는데요. 대부분 문제의 파라미터가 string으로 주어지고 해당 string을 이리저리 변형시켜서 풀어야 하는 경우가 많았습니다.

그래서 해당 string을 변형시키려고 구글링을 했더니 swift에서 string을 나누는 두 가지 방법이 있다는걸 알았습니다.
바로 split(separator:maxSplits:omittingEmptySubsequences:)components인데요. 두가지가 어떻게 다른지 비교해보는 시간을 가져보도록 하겠습니다.


1.   components(separatedBy:) 란?

먼저, components(separatedBy:)는 Swift에서 문자열을 구분자(separator)로 나누는 방법이에요. 예를 들어, ","로 나누고 싶으면 이렇게 쓰면 됩니다.

let text = "apple,banana,cherry"  
let components = text.components(separatedBy: ",")  
print(components) // ["apple", "banana", "cherry"]  
  • 반환 타입 : [String]
  • 특징 : 나눠진 각 부분은 새로운 String 객체로 만들어집니다. 즉, 원본 문자열을 복사해서 새로운 문자열을 생성하므로 메모리 사용이 큽니다.
  • 왜 메모리 사용이 크나요? : 각 부분을 새로 만들기 때문에, 특히 큰 문자열을 다룰 때 메모리 부담이 커집니다. 예를 들어, 1MB짜리 문자열을 100개로 나누면, 각 부분을 복사하므로 메모리 사용량이 많아집니다.
  • 장점 : 결과가 바로 [String] 이라 추가 작업 없이 사용 가능합니다.
  • 단점 : 메모리 효율성이 낮습니다.

2.   split(separator:)란?

split은 StringProtocol에 있는 메서드로, 문자열을 구분자로 나누지만, 반환 타입이 다릅니다. 예를들어,

let text = "apple,banana,cherry"  
let splitComponents = text.split(separator: ",")  
for part in splitComponents {  
    print(part) // "apple", "banana", "cherry"  
}  
  • 반환 타입 : [SubString]
  • 특징 : Substring은 원본 문자열의 일부를 참조하는 경량 타입입니다. 복사를 하지 않기 때문에 보다 메모리 효율적입니다.
  • 장점 : 메모리 사용이 적어서 큰 문자열을 다룰 때 유리합니다.
  • 단점 : Substring은 원본 문자열에 의존적이라, 원본이 사라지면 유효하지 않을 수 있습니다. 또한, 필요하면 String으로 변환해야합니다.

3.   시간 복잡도 비교

  • 두 메서드 모두 문자열을 한 번 순회하며 구분자를 찾으므로, 시간 복잡도는 O(n)입니다. 여기서 n은 문자열 길이입니다.
  • 그러나 components는 각 부분을 새 String으로 만들기 때문에, 메모리 할당과 복사로 인해 실제 실행 시간은 split보다 약간 느릴 수 있습니다.

4.   Swift에서 String 자르기: str[0..<5]가 에러 나는 이유

Swift에서 다른 언어들 처럼 str에 substring으로 바로 접근할 수 없습니다. 실제로

let str = "Hello"
print(str[0..<5] // 'subscript(_:)' is unavailable: cannot subscript String with an integer range, use a String.Index range instead' 에러 발생

위처럼 에러가 발생하는데 그 이유는 Swift가 문자열을 '유니코드' 기반으로 처리하기 때문입니다.

  • 왜 Int 범위를 못쓰나요?
    Swift는 문자열을 단순한 문자 배열로 보지 않고, 유니코드 스칼라 값(Unicode scalar values)으로 처리합니다. 예를 들어, "안녕"은 두 글자로 보이지만, 내부적으로는 더 복잡한 유니코드 코드 포인트로 구성됩니다. 즉, Int로 인덱싱하면 문자 경계가 어긋날 수 있어 안전하지 않습니다. 대신 String.Index를 사용해야합니다.

  • 다른 언어와의 차이
    Python이나 JavaScript에서는 str[0]처럼 Int로 바로 접근할 수 있지만, Swift는 문자열의 복잡성을 고려해 더 엄격한 접근 방식을 채택했습니다. 이는 유니코드 지원과 문자열 안정성을 보장하기 위한 설계 철학입니다. 예를 들어, 이모지(😀)나 복합 문자(é)를 다룰 때, Int 인덱싱은 잘못된 결과를 초래할 수 있습니다.

  • 올바른 방법
    String.Index는 문자열의 실제 위치를 나타내는 타입으로, startIndex와 index(_:offsetBy:)를 사용해 접근합니다. 예를 들어,
let str = "hello,world"  
let start = str.startIndex  
let end = str.index(start, offsetBy: 5)  
let substr = str[start..<end] // "hello" as Substring  
let converted = String(substr) // "hello" as String  
print(converted) // "hello"  

이렇게 하면 안전하게 문자열을 자를 수 있습니다.


5.   차이점 한눈에 보기

특성components(separatedBy:)split(separator:)
반환 타입[String] (새로운 문자열)[SubString] (원본 참조)
메모리 사용높음 (복사 발생, 메모리 부담 큼)낮음 (복사 없음, 메모리 효율적)
성능큰 문자열에서 느릴 수 있음큰 문자열에서 효율적
사용 용이성바로 [String] 사용 가능SubString -> String 변환 필요
적합한 경우작은 문자열, 결과 저장 필요큰 문자열, 임시 처리, 메모리 최적화 필요

6.   마무리

Swift의 split과 components는 비슷해 보이지만, 반환 타입과 메모리 사용 방식에서 차이가 있습니다. 특히 String을 자를 때 Int 범위를 못쓰는 이유는 Swift의 유니코드 처리 방식 때문입니다. String.Index를 사용하면 안전하게 문자열을 다룰 수 있습니다. 이 차이를 이해하면 상황에 맞게 더 효율적으로 코드를 작성할 수 있습니다!

profile
밍밍

0개의 댓글