반응형
iOS 개발자가 많이 하는 실수 - 문자열 비교 시 lowercased() 반복 사용 문제
1. 흔히 쓰는 패턴
다음과 같은 코드, 한 번쯤은 써 본 적 있을 가능성이 높습니다.
if input.lowercased() == "yes" {
// ...
}
혹은
if input.uppercased() == "OK" {
// ...
}
겉으로 보면 문제 없이 동작하고,
코드도 직관적이라 초반에는 이 방식이 “정답”처럼 느껴집니다.
하지만 이 패턴은 다음과 같은 단점을 갖고 있습니다.
- 매 비교마다 새로운 String 인스턴스 생성
- 문자 수가 많고, 호출이 반복될수록 불필요한 비용 증가
- 로케일/언어에 따라 예상치 못한 결과가 발생할 수 있음
2. 왜 lowercased() 남발이 문제인가?
2-1. String은 값 타입 + Copy-on-Write
Swift에서 String은 구조체(Struct) 기반의 값 타입입니다.
lowercased(), uppercased()는 새로운 문자열을 생성합니다.
let lower = input.lowercased() // 새로운 String 리턴
비교를 위해 매번 이 작업을 하면:
- text 길이에 비례하는 비용 발생
- 루프 내에서 수백·수천 번 호출될 경우 체감 가능한 성능 저하 가능
2-2. 반복 호출 시 비용 누적
예:
for word in hugeWordList {
if word.lowercased() == "target" {
// ...
}
}
- hugeWordList가 크면 클수록
- 매 반복마다 문자열 변환이 일어나서 CPU와 메모리 소비 증가
2-3. 로케일/언어에 따른 미묘한 이슈
영어만 쓰는 앱이라면 크게 체감되지 않을 수 있지만,
다국어를 처리하거나, 터키어 등의 특수 대소문자 규칙이 있는 언어를 지원하면
단순 lowercased/uppercased 변환이 예상과 다르게 동작할 가능성이 있습니다.
3. 올바른 문자열 비교 패턴
Swift는 문자열 비교 시 다양한 옵션을 지원하는 API를 제공합니다.
3-1. caseInsensitiveCompare(_:) 사용
if input.caseInsensitiveCompare("yes") == .orderedSame {
// 대소문자 무시하고 문자열이 같은 경우
}
장점:
- 대소문자만 무시하고 비교
- 문자열 전체를 새로 생성하지 않고 비교 수행
- Apple 공식 코드 스타일에서도 자주 등장하는 패턴
3-2. compare(_:options:) + .caseInsensitive
더 일반적인 API입니다.
if input.compare("yes", options: [.caseInsensitive]) == .orderedSame {
// ...
}
또는 여러 옵션 조합도 가능합니다.
let result = input.compare(
"yes",
options: [.caseInsensitive, .diacriticInsensitive]
)
사용 예:
- 대소문자 무시 + 악센트(é, è 등) 무시 등 복합 조건
3-3. localizedCaseInsensitiveCompare(_:)(사용자 언어 고려)
사용자의 로케일(언어 설정)을 고려한 비교가 필요할 경우:
if input.localizedCaseInsensitiveCompare("예") == .orderedSame {
// 사용자의 로케일에 맞춰 대소문자/문자 비교 수행
}
다국어 앱에서는 이쪽이 더 적절할 수 있습니다.
3-4. contains / hasPrefix / hasSuffix도 마찬가지
다음과 같은 코드도 종종 보입니다.
if input.lowercased().contains("hello") {
// ...
}
더 좋은 접근 방식:
if input.range(of: "hello", options: [.caseInsensitive]) != nil {
// ...
}
또는 prefix/suffix:
if input.hasPrefix("Hello") { } // 대소문자 구분
if input.lowercased().hasPrefix("he") // ❌ 비추천
// 대신
if input.range(of: "he", options: [.caseInsensitive, .anchored]) != nil {
// anchored 옵션은 prefix와 유사하게 동작
}
4. 성능 관점에서의 차이
정리하면:
- lowercased() / uppercased()
- 문자열 전체를 새로 생성
- 다량 호출 시 성능과 메모리 사용량에 영향
- compare(_:options:), caseInsensitiveCompare(_:), range(of:options:)
- 문자열을 변환하지 않고 비교/검색
- 필요할 경우 로케일, 악센트 등까지 제어 가능
- 더 유연하고, 비용도 상대적으로 적음
실무에서 “문자열 비교/검색이 자주 일어나는 코드” (검색, 필터링, 자동완성 등)에서는
반드시 options 기반 API를 쓰는 것이 좋습니다.
5. 정리
- lowercased()를 매번 호출해서 비교하는 방식은
- 직관적이지만 성능/유지보수/다국어 처리 측면에서 비효율적
- 실무에서는 다음 패턴을 우선 고려:
- 완전 일치:
- caseInsensitiveCompare(_:)
- compare(_:options: [.caseInsensitive])
- 부분 검색/포함 여부:
- range(of:options:) + .caseInsensitive
- 다국어/로케일:
- localizedCaseInsensitiveCompare(_:)
- 핵심은 “문자열 전체를 변환하지 말고, 비교 옵션을 사용하라”는 것.
반응형
'Dev Study > iOS' 카테고리의 다른 글
| iOS 개발자가 많이 하는 실수 - switch-case에서 default를 사용하고 enum 케이스 추가 시 실수 (0) | 2025.12.04 |
|---|---|
| iOS 개발자가 많이 하는 실수 - Dictionary에서 key 존재 여부 확인 없이 강제 언래핑(!) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - struct vs class 차이를 오해하여 발생하는 문제 (Value Type / Reference Type 혼동) (1) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - Array append 반복 사용 vs reserveCapacity / Array(repeating:count:) 성능 문제 (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - guard를 잘못 사용(Early Exit Misuse) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - Optional Binding 중첩(if let 지옥) (0) | 2025.12.04 |
| iOS 개발자가 많이 하는 실수 - 옵셔널을 강제 언래핑(!)하기 (0) | 2025.12.04 |
| iOS 색상 토큰(Role-based Color Tokens) 역할 설명 가이드 (0) | 2025.12.04 |

