Skip to content

문자열과 문자 #38

@simoniful

Description

@simoniful

String은 Character 타입의 Collection이며, 다양한 방법으로 값에 접근 가능
String 및 Character 유형은 코드에서 텍스트 기반의 작업에 있어서 빠른 유니코드 호환 방법을 제공
문자열 생성 및 조작을 위한 구문은 C와 유사한 문자열 리터럴 구문을 사용 - 가독성이 좋고 가볍게 처리
ex. 문자열 연결 - (+) 연산자 사용, 문자열 가변성 - 상수와 변수 중 선택하여 활용

swift에서 문자열 보간을 통해서 문자열 내부에 상수, 변수, 리터럴 및 표현식을 삽입 가능
따라서, 커스텀이 자유롭기에 뷰에 그리기, 내부에 저장, 콘솔 프린팅에 용이
모든 문자열은 인코딩에 독립적인 유니코드 문자로 구성되며 다양한 유니코드 표현으로 해당 문자에 액세스할 수 있도록 지원

Swift의 문자열 타입은 Foundation의 NSString 클래스와 브리지
또한 Foundation은 NSSstring에 의해 정의된 메서드를 표시하도록 String을 확장
즉, Foundation을 가져오는 경우 String에서 해당 NSString 메서드에 캐스팅 없이 액세스 가능
Bridging Between String and NSString

문자열 리터럴(String Literals)

문자열 리터럴은 상수나 변수의 초기 값을 사용 가능, 초기값에 기반하여 타입 유추

let someString = "Some string literal value"

1) 여러줄 문자열 리터럴(Multiline String Literals)

여러 줄로 구성된 문자열을 구성하고 싶을 시 (""")을 활용하여 구성 가능
여는 따옴표 뒤의 첫 번째 줄에서 시작하고 닫는 따옴표 앞의 줄에서 끝
코드를 읽기 쉽게 하기 위해 줄 바꿈을 사용하고 싶지만 줄 바꿈이 문자열 값의 일부가 되지 않도록 하려면 \해당 줄 끝에 백슬래시( )를 작성

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

let softWrappedQuotation = """
The White Rabbit put on his spectacles.  "Where shall I begin, \
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""

닫는 따옴표 앞의 공백은 Swift에 다른 모든 줄 앞에서 무시할 공백을 설정
그러나 닫는 따옴표 앞에 추가하여 줄 시작 부분에 공백을 쓰면 해당 공백이 포함

2) 문자열 리터럴에서의 특수 문자(Special Characters in String Literals)

기존에 약속된 리터럴에 의해서 특수문자 사용이 불가능한 경우를 피하기 위하여 이스케이프된 특수 문자()를 사용하도록 여러 편의 제공

  • \0 : null character
  • \ : backslash
  • \t : horizontal tab
  • \n : line feed(LF)
  • \r : carriage return(CR)
  • \" : double quotation mark
  • \' : single quotation mark
  • \u{n} : 임의의 유니코드 스칼라 값. n은 1~8자리 16진수 숫자(아래 유니코드에서 다시 설명)
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\u{24}"        // $,  Unicode scalar U+0024
let blackHeart = "\u{2665}"      // ♥,  Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // 💖, Unicode scalar U+1F496

3) 확장 문자열 구분 기호(Extended String Delimiters)

문자열 리터럴 혹은 여러 줄 문자열 리터럴을 확장 구분자(#) 안에 배치
문자열에 특수 문자를 포함하려면 이스케이프 문자() 뒤에 #을 붙임

let extendedStringDelimiters = #"""
Here are three more double quotes: """
"""#
// Prints "Here are three more double quotes: """"

let extendedSingleLine = #"Line 1\nLine 2"#
// Prints "Line 1\nLine 2"

let extendedUseSpecial = #"Line 1\#nLine 2"#
// Prints Line 1
//            Line 2

빈 문자열 초기화(Initializing an Empty String)

빈 문자열 리터럴("")을 변수에 할당하거나, initializer를 통해 새로운 String 인스턴스로 초기화 가능
isEmpty 프로퍼티를 사용해 빈 문자열인지 확인 가능

var emptyString = ""               
// empty string literal
var anotherEmptyString = String()  
// initializer syntax

if emptyString.isEmpty {
    print("Nothing to see here")
    // Prints "Nothing to see here"
}

문자열 수정(String Mutability)

문자열을 변수로 선언하면 수정하거나 변경할 수 있지만, 상수로 선언하면 할 수 없음

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified

이러한 접근법은 NSString과 NSMutableString으로 수정 여부를 구분하는 Objective-C, Cocoa와 다름

값 타입 문자열(Strings Are Value Types)

Swift의 String은 값 타입(value type)
String이 다른 함수 혹은 메소드로 부터 생성되거나 상수/변수로 String 값이 할당 될 때
이전 String의 레퍼런스를 할당하는 것이 아니라 값을 복사해서 생성

Swift의 copy-by-default String behavior는 다른 메소드, 함수에서 반환 받은 문자열이 그 자신의 값을 가지고 있는 것을 보장
그 문자열을 수정해도 원본 문자열이 변하지 않기 때문에 편하게 사용 가능
백그라운드에서 Swift의 컴파일러는 복사본이 실질적으로 필요한 만큼만 메모리 공간을 갖게 하여 String의 사용을 최적화

문자로 작업(Working with Characters)

for-in 반복문에서 String을 순회할 때 각각의 Character에 접근 가능
Character 타입을 지정하여 단일 문자 상수/변수 할당 가능
Character의 배열을 String의 init에 인자로 넣어서 문자열을 생성 가능

for character in "Dog!🐶" {
    print(character)
}
// D
// o
// g
// !
// 🐶

let exclamationMark: Character = "!"

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cat!🐱"

문자열과 문자의 결합(Concatenating Strings and Characters)

(+) 더하기 연산자를 사용하여 문자열을 결합 가능
(+=) 더하기 할당 연산자를 사용하여 문자열을 결합 가능
append() 메소드를 사용하여 문자열을 결합 가능

반대로 Character에 String이나 Character를 더하는 것은 불가능. Character는 반드시 하나의 단일 문자만을 가져야 하기 때문

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

만일 여러 줄 문자열 리터럴일 경우, 마지막 줄을 포함하여 문자열의 모든 줄이 줄 바꿈(LF)으로 끝나도록 구성할 경우 원활

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree
// badStart의 마지막 줄은 줄 바꿈으로 끝나지 않기 때문에 해당 줄은 첫 번째 줄 끝과 결합

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three
// goodStart의 마지막 줄은 모두 줄 바꿈으로 끝나기 때문에 의도한대로 3줄로 결합 

문자열 보간(String Interpolation)

문자열 보간은 상수, 변수, 리터럴, 표현식의 값을 문자열 리터럴에 포함시킴으로써 새로운 문자열을 만드는 방법
단일 혹은 여러 줄 문자열 리터럴 모두 사용 가능
\()의 괄호 안에 각각의 아이템을 연결
확장된 문자열 구분 문자에서 문자열 보간을 사용하려면 백슬래시와 괄호 사이에 #을 삽입

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

print(#"Write an interpolated string in Swift using \(multiplier)."#)
// Prints "Write an interpolated string in Swift using \(multiplier)."

print(#"6 times 7 is \#(6 * 7)."#)
// Prints "6 times 7 is 42."

\() 괄호 안에 들어가는 문자열은 단일 백슬래시(\), CR, LF를 포함할 수 없다. 다른 문자열 리터럴은 가능

유니코드(Unicode)

유니코드는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 국제 표준
Swift의 문자열과 문자 타입은 유니코드에 순응(compliant)

1) 유니코드 스칼라 (Unicode Scalar)

Swift의 네이티브 문자열 타입은 유니코드 스칼라 값으로 만들어졌다.
하나의 유니코드는 고유한 21비트 숫자로 구성
ex. U+0061는 라틴어의 소문자 a / U+1F425는 정면의 병아리 🐥

모든 21비트 유니코드 스칼라가 문자로 할당되는 것은 아니다
미래에 UTF-16 인코딩으로 사용하기 위해 예약되는 경우 존재
문자에 할당된 스칼라 값은 일반적으로 이름을 가짐

2) 확장된 문자소 클러스터 (Extended Grapheme Clusters)

스위프트 문자 타입의 모든 인스턴스는 하나의 확장된 문자소 클러스터를 대표
확장된 문자소 클러스터는 읽을 수 있는 단일 문자를 제공하는 하나 또는 그 이상의 유니코드 스칼라의 시퀀스
확장된 문자소 클러스터는 복잡하게 구성되어 있는 단일 문자 값을 유연하게 표현 가능하도록 환경 제공
확장된 문자소 클러스터는 enclosing marks를 단일 문자의 일부분으로써 다른 유니코드 스칼라에 덧붙일 수 있게 한다
단일 문자 값을 만들기 위해 지역 구분 심볼 유니코드 스칼라를 결합 가능

let eAcute: Character = "\u{E9}"                         
// é
let combinedEAcute: Character = "\u{65}\u{301}"          
// e followed by ́

// eAcute is é, combinedEAcute is é
// 단일 유니코드 스칼라 é와 e와 COMBINING ACUTE ACCENT가 병합된 스칼라 집합

let precomposed: Character = "\u{D55C}"                  
// 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   
// ᄒ, ᅡ, ᆫ
// precomposed is 한, decomposed is 한
// 복잡하게 구성되어 있는 단일 문자 값을 유연하게 표현

let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝
// é 일부분으로써 원으로 둘러싸인 유니코드 스칼라에 덧붙임

let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸
// 🇺 + 🇸= 🇺🇸
// 단일 문자 값을 만들기 위해 지역 구분 심볼 유니코드 스칼라를 결합

문자 갯수 세기(Counting Characters)

문자열 안에 있는 문자의 개수를 세기 위해, count 프로퍼티를 사용
Swift는 확장된 문자소 클러스터를 사용하기 때문에, 문자열의 연결이나 수정이 항상 문자열의 count에 영향을 미칠 수 있는 것은 아님
ex. "cafe"라는 문자열에 COMBINING ACUTE ACCENT를 더할 경우, 네 번째 문자 e가 é로 바뀌어도 전체 문자열 길이는 변하지 않음.

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"

var word = "cafe"
print<("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe is 4"
word += "\u{301}"    // COMBINING ACUTE ACCENT, U+0301
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in café is 4"

확장된 문자소 클러스터는 여러 유니코드 스칼라로 구성 가능
즉, 서로 다른 문자와 동일한 문자의 다른 표현은 저장하기 위해 서로 다른 양의 메모리를 필요로 할 수 있다는 의미
때문에 Swift의 문자는 문자열 표현 내에서 각각 동일한 양의 메모리를 차지하지 않음

확장된 문자소 클러스터의 경계를 결정하기 위해 문자열을 반복하지 않고는 문자열의 문자 수를 계산할 수 없음
특히, 긴 문자열 값을 사용하는 경우 문자열의 문자를 결정하려면 전체 문자열의 유니코드 스칼라에서 count 속성을 반복해야 확인가능

count 속성에서 반환되는 문자 수가 동일한 문자를 포함하는 NSString의 길이 속성과 항상 동일하지는 않음
NSString의 길이는 문자열의 UTF-16 표현에 포함된 16비트 코드 단위의 수를 기준
문자열 내의 유니코드 확장된 문자소 클러스터의 수를 기준으로 하지 않음

문자열 접근 및 수정(Accessing and Modifying a String)

메서드 및 프로퍼티를 통해 또는 subscript syntax을 사용하여 문자열에 액세스하고 수정 가능

3) 문자열 인덱스(String Indices)

각각의 문자 값은 관련된 인덱스 타입인 String.Index를 가지고 있음
서로 다른 문자는 다른 양의 메모리 공간을 요구
각 Character의 특정한 위치를 결정하기 위해선 문자열의 처음부터 끝까지 각 유니코드 스칼라를 순회해야 알 수 있음
이러한 이유로 Swift의 문자열은 정수 값의 인덱스를 가질 수 없음

따라서, 특정 인덱스 프로퍼티를 통해서 해당 문자열에 접근 - Array, Dictionary, Set과 같은 컬렉션 유형의 접근과 유사
문자열 범위를 벗어난 인덱스에 접근하려 할 경우 런타임 에러가 발생
indices 프로퍼티를 사용해 문자열 내 모든 문자의 인덱스에 접근 가능

  • startIndex : 첫 번째 문자의 인덱스
  • endIndex : 마지막 문자의 다음 위치. 따라서 endIndex는 문자열 범위에 포함되지 않음
  • index(before:) : 특정 인덱스 이 전
  • index(after:) : 특정 인덱스 이 후
  • index(_:offsetBy:) : 특정 인덱스에서 offsetBy만큼 떨어진 인덱스
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

greeting[greeting.endIndex] 
// Error
greeting.index(after: greeting.endIndex) 
// Error

for index in greeting.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n   T a g ! "

4) 삽입과 삭제(Inserting and Removing)

단일 문자(character)를 문자열의 특정 인덱스에 삽입하고자 할 때 insert(_:at:) 메서드를 사용
문자열(string)을 삽입하려면 insert(contentsOf:at:) 메서드를 사용

특정 인덱스의 단일 문자를 삭제하려면 remove(at:) 메서드 사용
특정 범위의 부분 문자열을 삭제하려면 removeSubrange(_:) 메서드를 사용

var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"

RangeReplaceableCollection 프로토콜을 준수하는 모든 유형인 Array, Dictionary, Set에도 사용 가능

부분 문자열 (Substrings)

문자열에서 부분 문자열을 얻을 때 String이 아닌 Substring의 인스턴스를 결과로 반환
Swift의 Substring은 문자열에서 쓸 수 있는 대부분의 메소드를 사용 가능
String과 달리 Substring은 짧은 기간에만 쓰는 것을 권장
긴 시간 동안 Substring을 저장해야 한다면 Substring을 String의 인스턴스로 전환

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"

// Convert the result to a String for long-term storage.
let newString = String(beginning)

부분 문자열도 문자열처럼 메모리 공간을 갖고 있음
성능 최적화 관점에서 둘에 차이점이 존재하는데, 부분 문자열은 원본 문자열 혹은 다른 부분 문자열 메모리 일부분을 참조해 재사용

SubString을 계속 이용하는 이상은 원본 String이 계속 메모리에 남아 있게 된다
사용하지 않는 문자열까지도 남게 되기 때문에 SubString을 오래 사용하고자 한다면
String 인스턴스로 만들어 사용하고자 하는 문자만 메모리에 올려놓고 사용하는게 관리 효율면에서 좋음

String과 Substring은 StringProtocol 프로토콜을 따른다
따라서, 문자 조작에 필요한 편리한 메소스들을 공통으로 사용할 수 있다

문자열 비교(Comparing Strings)

Swift는 세 가지 문자열 비교 방법을 제공

  • 문자열과 문자 동등
  • 접두사 동등
  • 접미사 동등

1) 문자열과 문자 동등 (String and Character Equality)

(==) 연산자와 (!=) 연산자를 사용하여 문자열과 문자가 같은지 비교
두 문자열(또는 두 문자) 값의 확장된 문자소 클러스터가 동일하다면, 같은 것으로 취급
반면, 같은 유니코드 문자여도 유니코드가 다르면 다른 문자로 판별
ex. 영어권에서 사용되는 라틴 문자 "A"와 러시아권에서 사용하는 카톨릭 문자 "A"는 다르게 취급된다. 언어적 의미가 다르기 때문

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"

// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

let latinCapitalLetterA: Character = "\u{41}"

let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent.")
}
// Prints "These two characters are not equivalent."

Swift에서 문자열과 문자의 비교는 언어를 고려하지 않는다. 언어와 상관없이 같은 문자면 같은 것으로 여긴다.

2) 접두사와 접미사 비교(Prefix and Suffix Equality)

hasPrefix(:), hasSuffix(:) 메서드를 사용하여 접두사와 접미사를 비교
접두사, 접미사 비교 역시 문자열과 문자의 비교와 동일한 방식으로 수행

let romeoAndJuliet = [
    "Act 1 Scene 1: Verona, A public place",
    "Act 1 Scene 2: Capulet's mansion",
    "Act 1 Scene 3: A room in Capulet's mansion",
    "Act 1 Scene 4: A street outside Capulet's mansion",
    "Act 1 Scene 5: The Great Hall in Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's mansion",
    "Act 2 Scene 2: Capulet's orchard",
    "Act 2 Scene 3: Outside Friar Lawrence's cell",
    "Act 2 Scene 4: A street in Verona",
    "Act 2 Scene 5: Capulet's mansion",
    "Act 2 Scene 6: Friar Lawrence's cell"
]

var act1SceneCount = 0

for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}

print("There are \(act1SceneCount) scenes in Act 1")
// Prints "There are 5 scenes in Act 1"

var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence's cell") {
        cellCount += 1
    }
}

print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// Prints "6 mansion scenes; 2 cell scenes"

문자열의 유니코드 표현(Unicode Representations of Strings)

유니코드 문자열이 텍스트 파일 또는 저장소의 다른 파일로 쓰여졌을 때
그 문자열의 유니코드 스칼라는 다양한 유니코드 인코딩 형태 중 하나로 인코딩
각각의 형태는 문자열을 코드 유닛으로 알려진 작은 청크로 인코딩

Swift는 문자열의 유니코드 표현에 몇 가지 다른 접근 방식을 제공

  • UTF-8 코드 유닛의 컬렉션 (문자열의 utf8 프로퍼티로 접근한다.)
  • UTF-16 코드 유닛의 컬렉션 (문자열의 utf16 프로퍼티로 접근한다.)
  • 21비트 유니코드 스칼라 값의 컬렉션 (이는 UTF-32와 동일하게 취급되며, 문자열의 unicodeScalars 프로퍼티로 접근한다.)
let dogString = "Dog‼🐶"

// UTF-8 표현 
// utf8 프로퍼티(String.UTF8View 타입)을 순회하여 문자열의 UTF-8 표현에 액세스 가능
// 해당 타입은 문자열의 부호 없는 8비트(UInt8) 값의 집합

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "

let dogString = "Dog‼🐶"

// UTF-16 표현
// utf16 프로퍼티(String.UTF16View 타입)을 순회하여 문자열의 UTF-16 표현에 액세스 가능
// 해당 타입은 문자열의 부호 없는 16비트(UInt16) 값의 집합
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "

let dogString = "Dog‼🐶"

// UTF-32, 유니코드 스칼라 표현
// unicodeScalars 프로퍼티(UnicodeScalarView 타입)을 순회하여 문자열의 scalar 표현에 액세스 가능
// 해당 타입은 문자열의 UnicodeScalar 값의 집합
// 각각의 UnicodeScalar는 Uint32 값으로 표현되는 21비트 scalar 프로퍼티를 가짐
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions