슈프림 블로그
[Swift] String 관련 함수들 본문
▼▼▼Apple 공식 문서 | String ▼▼▼
developer.apple.com/documentation/swift/string
Swift는 문자열 다루기가 까다롭기로 악명높다..
실제로 나도 Swift로 코딩테스트를 여러번 치뤄오면서 문자열 문제 때문에 다른 언어로 갈아탈까 수백번은 고민한 것 같다.
하지만 이번 기회에 잘 정리해서 능숙하게 사용할 수 있도록 공부해 두면 해결될 문제라고 생각한다!
기본 속성들
- 큰따옴표 3개로 묶으면 여러 줄의 문자열을 그대로 사용할 수 있다. (큰따옴표 3개는 문자열 한 줄을 내려써야 한다.)
- 문자열 길이 확인 count (Int)
- 빈 문자열 확인 isEmpty (Bool)
- 반복되는 문자열로 초기화 init(repeating: String, count: Int)
- 포맷 활용하기 init(format: String, CVarArg...)
문자열 요소 : Character
String은 Character 요소로 구성되어있다. for loop를 사용하여 확인할 수 있다.
let str = "Hello"
for c in str {
print("element : \(c), type: \(type(of: c)")
}
// element : H, type: Character
// element : e, type: Character
// element : l, type: Character
// element : l, type: Character
// element : o, type: Character
문자열 보간 (문자열 삽입) : String Interpolations
이건 가장 편리했던 Swift 문자열의 특징이다.
문자열은 큰따옴표로 묶어서 표시하는데, 큰따옴표 중간에 \( )
를 사용하여 아무 표현식이나 변수를 삽입하여 문자열로 바꿀 수 있다.
\( )
사이에는 문자열은 물론, 어떤 값도 들어갈 수 있다.
객체가 들어간다면, 해당 객체의 description 값이 삽입될 것이고, 계산식이 들어가면 계산된 결과값이 삽입될 것이다.
let name = "Rosa"
let personalizedGreeting = "Welcome, \(name)!"
let price = 2
let number = 3
let cookiePrice = "\(number) cookies: $\(price * number)."
// cookiePrice == "3 cookies: $6."
문자열 결합 : Combining Strings
append() 또는 +
연산자를 사용하여 문자열을 결합할 수 있다.
let greeting = "Hello"
let language = "Swift"
print(greeting + language) // "HelloSwift"
문자열 비교 : Comparing Strings
==
, <
, >
비교 연산자를 사용하여 문자열을 비교할 수 있다.
문자열의 표현식이 달라도, 결과값이 동일하다면 같은 문자열로 인정한다.
let cafe1 = "Cafe\u{301}"
let cafe2 = "Café"
print(cafe1 == cafe2) // true
문자열 요소의 접근 : Subscript
문자열 요소에 접근하기 위해서는 subscript를 사용한다.
서브스크립트는 시퀀스나 딕셔너리 등 대괄호 [] 안에 특정 값을 넣어 원하는 값을 바로 찾아오는 기능을 말한다.
문자열의 경우, 인덱스값을 통해 해당 위치의 Character 값을 접근할 수 있다.
String클래스의 subscript는 다음과 같이 정의되어 있다.
// define
subscript(String.Index) -> Character
일반적으로 Int형 index값으로 문자 요소에 접근할 수 있다고 생각한다. 하지만 Swift는 안된다ㅠㅠ 다음과 같은 에러가 발생한다.
'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.
let str = "Hello"
print(str[0]) //[!] Error!
Int형은 사용할 수 없으니 String.Index
를 사용하라고 나온다. 이 부분이 문자열에서 가장 까다롭다고 느낀 부분이다.
String.Index
형은 여러가지 방법으로 구할 수 있다.
인덱스 구하기
- startIndex : 문자열의 시작 요소 인덱스를 가리킨다.
- endIndex : 문자열의 마지막 요소 인덱스 다음을 가리킨다.
- index(before: String.Index) : 인자로 들어온 인덱스 1칸 앞을 가리킨다.
- index(after: String.Index) : 인자로 들어온 인덱스 1칸 뒤를 가리킨다.
- index(String.Index, offsetBy: String.IndexDistance) : 인자로 들어온 인덱스와 offsetBy 차이만큼 떨어진 곳을 가리킨다.
- firstIndex(of: Character), lastIndex(of: Character) : 인자로 들어온 문자가 몇번째 인덱스에 있는지 (Optional)
let str = "ABCDE"
str[str.startIndex] // A
str[str.index(after: str.startIndex)] // B
str[str.index(before: str.endIndex)] // E
str[str.index(str.startIndex, offsetBy: 2] // C
str[str.index(str.endIndex, offsetBy: -2] // D
이렇게 접근한 요소는 모두 Character
형이다.
Extension 활용
나는 이 방식이 너무 불편해서 인덱스만 알면 해당 위치의 문자를 구할 수 있도록 extension으로 함수를 빼놓는 것을 선호한다.
extension String {
func getChar(at index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)]
}
}
let str = "aBcDeF"
print(str.getChar(at: 3)) // D
정리하다가 subscript도 오버로딩이 가능하다는 사실을 깨달았다.
이렇게 해두면 Int형으로도 원하는 위치의 문자열 요소에 쉽게 접근할 수 있다.
extension String {
subscript(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)]
}
}
let str = "aBcDeF"
print(str[3]) // D
부분 문자열 Substring
부분 문자열도 subscript를 통해 접근할 수 있으며, Int가 아니라 String.Index 타입 Range로 변환하여 구해야 한다.
let name = "Marie Curie"
let firstSpace = name.firstIndex(of: " ") ?? name.endIndex
let firstName = name[..<firstSpace]
// firstName == "Marie"
이렇게 구해진 firstName은 String 타입이 아니라 Substring 타입이다.
Substring은 자신의 원본 문자열을 저장하는 메모리를 그대로 사용한다. 즉, 원본 메모리의 인스턴스를 참조한다.
따라서 단순 참조만 이루어 질 경우에는 성능 최적화를 위해 Substring 그대로 사용하는 것이 좋고,
Substring을 변형하는 등 기타 활용해야 할 경우, String으로 형변환 하여 사용하는 것이 좋다.
Extension 활용
마찬가지로 String.Index의 Range 대신 Int 타입 Range로 바꿔서 String을 반환하도록 extension해두면 편리하다.
extension String {
subscript(_ range: Range<Int>) -> String {
let fromIndex = self.index(self.startIndex, offsetBy: range.startIndex)
let toIndex = self.index(self.startIndex,offsetBy: range.endIndex)
return String(self[fromIndex..<toIndex])
}
}
문자 포함
특정 문자의 인덱스를 알고 싶다면
firstIndex(of:), lastIndex(of:)를 사용하면 원하는 문자가 있는 인덱스를 구할 수 있다.
반환형은 String.Index? (옵셔널)이므로 Unwrapping 후 사용하면 된다.
let str = "Hello World"
if let index = str.firstIndex(of: "o") {
print(index) // 4
}
if let index = str.lastIndex(of: "o") {
print(index) // 7
}
특정 문자를 포함하고 있는 여부만 알고 싶다면
contains(_:)를 사용한다. 인자로는 Character 형 문자를 넘겨준다. 결과값은 Bool 형이다.
let str = "Apple"
print(str.contains("A")) // true
print(str.contains("a")) // false
문자열 포함
특정 문자열을 포함하고 있는지는 contains(_:) 에 원하는 문자열을 넣어서 판단할 수 있다.
반환형은 Bool 이다. (문자 Character, 문자열 String 모두 사용 가능)
let str = "Hello World"
print(str.contains("Hello")) // true
print(str.contains("Swift")) // false
하지만 특정 문자열의 인덱스 범위를 알 수 있는 함수는 없다.
따라서 Extension으로 함수를 만들어두고 사용하면 편리하다.
문자열을 배열로 만들기 : String to Array
map()
고차함수 map을 사용하여 문자열을 문자 하나하나로 쪼개서 배열로 만드는 방법이다.
let str = "aBcDeF"
// Character형을 요소로 갖는 배열
let charArr = str.map {$0}
print("\(charArr) : \(type(of: charArr))") // ["a", "B", "c", "D", "e", "F"] : Array<Character>
// String형을 요소로 갖는 배열
let strArr = str.map {String($0)}
print("\(strArr) : \(type(of: strArr))") // ["a", "B", "c", "D", "e", "F"] : Array<String>
배열을 문자열로 만들기 : Array to String
배열의 joined()나 reduce()를 사용한다.
let array = ["A", "B", "C"]
let str1 = String(Array)
let str2 = array.joined(separator: "")
let str3 = array.reduce("", +)
접두어, 접미어 확인
prefix(_:), suffix(_:)
맨 앞 또는 뒤에서 몇번째 글자까지 오는 접두어나 접미어를 확인할 수 있다. 반환형은 Substring이다.
let str = "aBcDeF"
// 접두어 (앞에서부터 몇 글자)
print(str.prefix(3)) // aBc
print(str.prefix(4)) // aBcD
// 접미어 (뒤에서부터 몇 글자)
print(str.suffix(1)) // F
print(str.suffix(2)) // eF
hasPrefix(_:), hasSuffix(_:)
접두어나 접미어가 주어진 문자열과 일치하는지 확인할 수 있다. 반환형은 Bool 이다.
let str = "aBcDeF"
// 접두어 (앞에서부터 몇 글자)
print(str.hasPrefix("a")) // true
print(str.hasPrefix("aB")) // true
print(str.hasPrefix("ab")) // false
// 접미어 (뒤에서부터 몇 글자)
print(str.hasSuffix("F")) // true
print(str.hasSuffix("DeF")) // true
print(str.hasSuffix("FeD")) // false
대소문자 변환
uppercased(), lowercased() 를 사용하여 영문을 모두 대문자로, 모두 소문자로 변경할 수 있다.
기존 문자열을 변경하는 것이 아니라 새로운 문자열을 생성하여 리턴한다.
문자열에 영문이 포함된 경우에만 대문자 또는 소문자로 변환되고, 영문이 아니더라도 에러는 발생하지 않는다.
! upperCased, lowerCased 아님 주의 !
let str = "Hello Swift"
print(str.uppercased()) // "HELLO SWIFT"
print(str.lowercased()) // "hello swift"
let str2 = "안녕123!"
print(str2.uppercased()) // "안녕123!"
print(str2.lowercased()) // "안녕123!"
아스키코드
String 리터럴을 구성하고 있는 Character는 asciiValue라는 속성을 가지고 있다.
.asciiValue는 UInt8 옵셔널을 반환하므로 언랩핑 후 사용해야한다.
주의할 점은 아스키코드는 영문, 숫자, 일부 특수기호 128개의 고유값만 표현할 수 있다. (한글은 아스키코드로 변환할 수 없다.)
let str = "Hello안녕"
for c in str {
guard let ascii = c.asciiValue else {
print("\(c) cannot convert to Ascii")
continue
}
print("\(c) => \(ascii)")
}
// H => 72
// e => 101
// l => 108
// l => 108
// o => 111
// 안 cannot convert to Ascii
// 녕 cannot convert to Ascii
유니코드
유니코드는 다른 시스템에서도 사용 가능하며, 거의 모든 문자와 언어를 표현할 수 있다.
유니코드를 다룰 때는 Unicode.Scalar라는 구조체를 사용한다.
Character to Unicode Scalar
생성자에 Character를 넣어서 Scalar 인스턴스를 생성할 수 있다.
Scalar를 출력하면 해당하는 문자가 출력되고, value를 출력하면 숫자 코드 (UInt32)가 출력된다.
알파벳은 물론 한글까지 변환 가능하다.
let alphabetToScalar = Unicode.Scalar("A")
print(alphabetToScalar) // A
print(alphabetToScalar.value) // 65
let hanguelToScalar = Unicode.Scalar("가")
print(hanguelToScalar) // 가
print(hanguelToScalar.value) // 44032
Int to Unicode Scalar
숫자를 사용하여 Scalar 인스턴스를 생성할 수도 있다.
여기서 주의해야 할 점은, 숫자를 인자로 넣었을 경우 옵셔널을 반환한다는 점이다. 언랩핑 후 사용하도록 하자.
if let intToScalar = Unicode.Scalar(65) {
print(intToScalar) // A
print(intToScalar.value) // 65
}
총정리
기존 문자열을 변경하는 것들
- 삽입
- 삭제
- 치환
새로운 문자열을 생성하여 반환하는 것들
- appending(_:), +연산자
- uppercased(), lowercased()
Substring을 반환하는 것들
- prefix(), suffix()
- split()
extension으로 빼 두면 좋을 것들
extension String {
subscript(_ index: Int) -> Character {
return self[self.index(self.startIndex, offsetBy: index)]
}
subscript(_ range: Range<Int>) -> String {
let fromIndex = self.index(self.startIndex, offsetBy: range.startIndex)
let toIndex = self.index(self.startIndex,offsetBy: range.endIndex)
return String(self[fromIndex..<toIndex])
}
}
정리하고 보니 String도 시퀀스 중 하나이기 때문에 Array랑 비슷하고 생각보다 많은 기능을 제공하고 있는 듯 하다.
지금까지 거부감이 들었던 것은 선입견이었던 걸까.
Swift는 Extension을 활용해서 클래스 자체에 원하는 기능을 구현해놓을 수 있으니, 이번 기회에 정리해 두고 꾸준히 사용해야겠다.
'iOS_Swift' 카테고리의 다른 글
[iOS] 동시성 프로그래밍 - 동기/비동기 , 직렬/동시 (0) | 2020.12.05 |
---|---|
[iOS] 동시성 프로그래밍 - GCD, Operation 개요 (0) | 2020.12.05 |
[Swift] Array 관련 함수들 2 (1) | 2020.11.01 |
[Swift] Array 관련 함수들 1 (0) | 2020.10.29 |
[iOS/Swift] XCode 프로젝트에서 RealmSwift 사용하기 2 (0) | 2020.09.21 |