Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
반응형
Archives
Today
Total
관리 메뉴

슈프림 블로그

[Swift] String 관련 함수들 본문

iOS_Swift

[Swift] String 관련 함수들

_슈프림 2020. 12. 5. 16:22
728x90

▼▼▼Apple 공식 문서 | String ▼▼▼

developer.apple.com/documentation/swift/string

 

Apple Developer Documentation

Structure
String | A Unicode string value that is a collection of characters.

developer.apple.com

 

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"

이렇게 구해진 firstNameString 타입이 아니라 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으로 함수를 만들어두고 사용하면 편리하다.

soooprmx.com/archives/7007


문자열을 배열로 만들기 : 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라는 속성을 가지고 있다.

.asciiValueUInt8 옵셔널을 반환하므로 언랩핑 후 사용해야한다.

주의할 점은 아스키코드는 영문, 숫자, 일부 특수기호 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을 활용해서 클래스 자체에 원하는 기능을 구현해놓을 수 있으니, 이번 기회에 정리해 두고 꾸준히 사용해야겠다.

반응형
Comments