[Swift 공식문서 읽기]Strings and Characters

llim🧚🏻‍♀️·2021년 8월 10일
1

Swift

목록 보기
3/26
post-thumbnail

안녕하세요. 엘림입니다🙇🏻‍♀️

Swift 공식 문서 정독하기 3편입니다!

제 스타일대로 정리했으니 추가적으로 더 필요한 정보는
공식문서 링크를 눌러 확인해주세용!

사실 거의 한국어로 번역된 사이트를 확인했습니다!ㅎㅎ

자, 그럼 시작해볼까요

이 글은 공부하면서 작성한 글이기 때문에 잘못된 정보가 있을 수 있습니다.🥺
금방 잊어버릴... 미래의 저에게 다시 알려주기 위한 글이다보니
혹시라도 틀린 부분이 있다면, 댓글로 친절하게 알려주시길 부탁드립니다.🙏


Strings and Characters

Swift의 String 및 Character 타입은 코드의 텍스트 작업을 타입은 유니코드 호환 방식으로 빠르게 제공합니다.
신택스는 단순하지만, 빠르고 현대적인 구현입니다. 모든 문자열은 인코딩에 독립적인 유니코드 문자로 구성되며, 다양한 유니코드 표현으로 해당 문자에 접근할 수 있도록 지원합니다.
또한, Swift의 String은 Foundation 프레임워크의 NSString이 bridge된 타입이기 때문에 NSString의 메소드를 String에서 캐스팅 없이 사용 가능합니다.

문자열 리터럴

문자열은 큰 따옴표(“)로 묶어 표현 합니다.
let something = "Some string literal value"

여러 줄 문자열 리터럴

let singleLineString = "These are the same."
let multilineString = """These are the same.""" // 에러!
// """을 사용할때는 아래처럼 반드시 줄을 바꿔줘야함!

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."
"""
/* 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."*/

// 소스코드를 읽기 편하게 하고싶어서 줄바꿈을 했지만, 문자열에는 줄바꿈이 저장되지 않았으면 할때 \ 를 사용한다.

이 부분에서 한국어 번역본에 오류가 있습니다. 꼭 공식문서를 함께 확인해주세요.
1. let multilineString = """These are the same."""으로 표현되어 있지만, 실제로 """는 줄바꿈을 반드시 해야합니다. (한줄로 표현할 수 없음)
2. 여러줄 문자열을 사용하며 줄바꿈을 하고 싶으면 백슬래쉬(\)를 사용합니다.라고 되어있는데 표현이 애매한 것 같습니다. 이는 소스코드에서는 줄바꿈을 하는 것이지만, 실제 문자열 자체는 줄바꿈이 없이 저장하기 위한 기호입니다.

들여쓰기*
닫는 따옴표(""") 앞에 공백은, 모든 줄 앞에서 무시할 공백이 됩니다.
그러므로 닫는 따옴표보다 앞으로 문자를 적을 수는 없고, 뒤로 띄운 경우에는 문자열에 들여쓰기로 포함됩니다.

특수 문자

문자열 리터럴은 다음과 같은 특수 문자를 포함할 수 있습니다.

  • \0(널 문자), \(백슬래시), \t(가로 탭), \n(줄 바꿈), \r(캐리지 리턴), \"(큰따옴표) 및 \'(작은따옴표)
  • u{n}(n은 1-8자리 십진수 형태로 구성된 유니코드)
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowlege" - Einstein
let dollaSign = "\u{24}"            // $, 유니코트 U+0024
let blackHeart = "\u{2665}"         // ♥, 유니코드 U+2665
let sparklingHeart = "\u{1F496}" // 💖,유니코드 U+1F496

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
No escaping a quotation marks " " "
' ' ' ' ' ' ' \u{1F444}
"""
// Escaping the first quotation mark """
// Escaping all three quotation marks """
// No escaping a quotation marks " " "
// ' ' ' ' ' ' ' 👄

여러 줄 일때("""), 내부에 큰 따옴표와 작은 따옴표는 백슬래시 없이 표현이 가능합니다.
백슬래시 뒤에 문자를 적는 다른 특수문자들은 동일하게 표현됩니다.

확장 문자열 구분 기호

#을 사용하여, 특수 문자가 되지 않게 할 수도 있고
그 내부에서 특수 문자가 될 수 있게도 할 수 있습니다.

let extendedString = #"Line 1\nLine 2"#
// Line 1\nLine 2

let extendedString2 = #"Line 1\#nLine 2"#
// Line 1
// Line 2

let extendedString3 = ###"Line1\###nLine2"###
// Line1
// Line2

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
\n \r \t \0 ''
"""#
// Here are three more double quotes: """
// \n \r \t \0 ''

빈 문자열 초기화 및 수정

"" 또는 String()로 초기화할 수 있습니다.

var emptyString = ""               // empty string literal
var anotherEmptyString = String()  // initializer syntax
// these two strings are both empty, and are equivalent to each other

if anotherEmptyString == emptyString {
    print("is same")
}
// Prints "is same"

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

문자열 수정

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

문자열은 값 타입입니다.

Swift의 String은 값 타입(value type)입니다. 그래서 String이 다른 함수 혹은 메소드로 부터 생성되면 String값이 할당 될때, 이전 String의 레퍼런스를 할당하는 것이 아니라 값을 복사해서 생성합니다. 반대로 이야기 하면 다른 메소드에서 할당 받은 문자열은 그 문자열을 수정해도 원본 문자열이 변하지 않기 때문에 편하게 사용하셔도 됩니다.

Swift의 컴파일러는 실제 복사가 절대적으로 필요할 때만 발생하도록 문자열 사용을 최적화합니다. 이는 문자열을 값 유형으로 사용할 때 항상 뛰어난 성능을 얻을 수 있음을 의미합니다.

💡 값 타입의 copy-on-write

Character

문자열의 개별 문자를 for-in loop을 사용해 접근할 수 있습니다.

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

let dog = "Dog!🐶"

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!🐱"

문자열과 문자의 결합

let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome : "hello there"

var instruction = "look over"
instruction += string2
// instruction : "look over there"

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

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three

문자열 보간

백슬래쉬 괄호를 이용해 상수, 변수, 리터럴 값을 문자열에 추가할 수 있습니다.

let mutiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message : "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."

💡 보간법과 +의 차이관한 스택오버플로우

유니코드

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

유니코드 스칼라 값

Swift의 네이티브 문자열 타입은 유니코드 스칼라 값으로 만들어 졌습니다. 유니코드 스칼라 값은 유니코드 "문자"에 배당된 숫자로, 통상 "U+" 뒤에 4~6자리 16진수로 표기하며, 하나의 유니코드는 21비트의 숫자로 구성돼 있습니다. (모든 21비트 유니코드 스칼라 값이 문자에 할당되는 것은 아닙니다. 일부 스칼라는 나중에 할당하거나 UTF-16 인코딩에서 사용하기 위해 예약되어 있습니다.)
U+0061는 라틴어의 소문자 a를 나타내고 U+1F425는 정면의 병아리 🐥 를 나타냅니다.

자모 그룹의 확장

Swift Character유형 의 모든 인스턴스는 단일 확장 자소 클러스터를 나타냅니다. 확장 자소 클러스터는 (결합 시) 사람이 읽을 수 있는 단일 문자를 생성하는 하나 이상의 유니코드 스칼라 시퀀스입니다.

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

  let precomposed: Character = "\u{D55C}"                        // 한
  let decomposed: Character = "\u{1112}\u{u1161}\u{11AB}"    // ㅎ, ㅏ,ㄴ
  // precomposed : 한, decomposed 한
  
    let enclosedEAcute: Character = "\u{E9}\u{20DD}"
  // enclosedEAcute : é⃝
  
    let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
  // regionalIndicatorForUS : 🇺🇸

문자 세기

문자열의 문자 숫자를 세기 위해서는 문자열의 count 프로퍼티를 이용합니다.

import Foundation

var word = "cafe"
let nsStringLength1 = NSString(string: word).length

print("the number of characters in \(word) is \(word.count) and \(nsStringLength1)")
// Prints "the number of characters in cafe is 4 and 4"

word += "\u{301}"    // "café"
let nsStringLength2 = NSString(string: word).length

print("the number of characters in \(word) is \(word.count) and \(nsStringLength2)")
// Prints "the number of characters in café is 4 and 5"

확장된 자소 클러스터 사용은 문자열 연결 및 수정이 문자열의 문자 수에 항상 영향을 미치는 것은 아니라는 것도 기억해야합니다.

확장된 자소 클러스터는 여러 유니코드 스칼라로 구성될 수 있습니다. 즉, 다른 문자 및 동일한 문자의 다른 표현이 저장하는 데 필요한 메모리 양이 다를 수 있습니다. 이 때문에 Swift의 문자는 문자열 표현 내에서 동일한 양의 메모리를 각각 차지하지 않습니다. 결과적으로 문자열의 문자 수는 확장된 자소 클러스터 경계를 결정하기 위해 문자열을 반복하지 않고 계산할 수 없습니다. 특히 긴 문자열 값으로 작업하는 경우 count해당 문자열의 문자를 결정하기 위해 속성이 전체 문자열의 유니코드 스칼라를 반복해야 한다는 점에 유의하십시오 .

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

💡String.count VS. NSString.length <- 요 문서는 다시 읽기..!
isEmpty vs count == 0

문자열 접근과 수정

문자열 인덱스

Swift 문자열은 정수 값으로 인덱싱 할 수 없습니다.
위에서 언급했던 것 처럼, 각 문자 별로 메모리의 양이 다르기 때문에 어떤 문자가 특정 위치에 있는것을 확인하려면 해당 문자열의 시작 또는 끝에서 각 유니코드 스칼라를 반복해야 하기 때문입니다.

대신 startIndex, endIndex, index(before:), index(after:), index(_: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] // 에러!
greeting.index(after: greeting.endIndex) // 에러!

endIndex는 마치 C언어의 문자열 마지막 \0을 의미한다고 보면 됩니다. 즉, 실제로 유요한 인수가 아니며, 마지막 문자의 다음 위치라고 볼 수 있습니다. (그러므로 빈 문자열의 startIndex와 endIndex는 같습니다.)
그렇기 때문에 위와 같이 문자열 범위 밖의 인덱스에 접근하려고 하면 에러가 나게 됩니다.

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

indices를 사용하여 문자열에 있는 개별 문자의 모든 인덱스에 접근할 수 있습니다.

문자 삽입 및 제거

insert(:at:), insert(contentsOf:at:), remove(at:), removeSubrange(:) 메소드를 사용할 수 있습니다.

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

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

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

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

위 메소드들은 RangeReplaceableCollection 프로토콜을 따르는 Array, Dictionary, Set 등에서도 동일하게 사용할 수 있습니다.

부분 문자열

문자열에서 부분 문자열을 얻기 위해 prefix(_:)와 같은 서브스크립트 메소드를 이용할 수 있는데, 그렇게 얻은 부분 문자열은 문자열 인스턴스가 아니라 부분문자열(SubString) 인스턴스 입니다. String과 Substring 모두 StringProtocol을 따릅니다. 그래서 문자 조작에 필요한 편리한 매소스들을 공통으로 사용할 수 있습니다.

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

// SubString인 beginning을 String으로 변환
let newString = String(beginning)

만약 부분 문자열을 단기간에 사용하는게 아니라 오랜기간 사용한다면 문자열 인스턴스로 바꿔서 사용하는게 좋습니다.


그 이유는 메모리 관리 때문입니다. SubString은 해당 문자를 직접 갖고 있는 것이 아니라 원본 String의 메모리를 참조해 사용합니다.(성능 최적화로서 하위 문자열은 원래 문자열을 저장하는 데 사용된 메모리의 일부, 또는 다른 하위 문자열을 저장하는데 사용된 메모리의 일부를 재사용 합니다.)
그래서 SubString을 계속 이용하는 이상은 원본 String이 계속 메모리에 남아 있게 됩니다. 사용하지 않는 문자열까지도 남게 되는 것이죠. 그렇게 때문에 SubString을 오래 사용하고자 한다면 위 예제처럼 String에서 인스턴스로 만들어 사용하고자 하는 문자만 메모리에 올려놓고 사용하는 것이 관리 효율면에서 좋다고 할 수 있습니다.

문자열 비교

문자열과 문자 비교

// "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}" // A
let cyrillicCapitalLetterA: Character = "\u{0410}" // A

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters aren't equivalent.")
}
// Prints "These two characters aren't equivalent."

확장된 자소 클러스터가 표준적으로 동일한 경우, 같은 것으로 판단합니다.
같은 맥락으로 유니코드가 다르면 같아 보여도 다른 문자로 판별합니다.
(Swift에서 문자열과 문자의 비교는 언어를 고려하지 않습니다. 다시말해, 언어와 상관없이 같은 문자면 같은 문자로 취급합니다.)

접두사와 접미사 비교

hasPrefix(:), hasSuffix(:) 메소드를 사용할 수 있습니다. 둘 다 문자열 타입의 단일 인수를 사용하며, Bool값을 반환합니다.

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 remeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// There are 5 scenes in Act 1

var act1SceneCount = 0
for scene in remeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("There are \(act1SceneCount) scenes in Act 1")
// There are 5 scenes in Act 1

문자열의 유니코드 표현

유니코드 문자가 텍스트 파일이나 다른 저장소에 쓰여질 때 유니코드 스칼라는 UTF-8, UTF-16, UTF-32 등 다양한 유니코드 인코딩 방식이 사용됩니다.
(??????????)

let dogString = "Dog!!🐶"

UTF-8

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

UTF-16

for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
// 68 111 103 8252 55357 56374

유니스코드 스칼라

for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
// 68 111 103 8252 128054

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

오늘은 스위프트 공식문서에서 세번째 챕터인 Strings and Characters를 읽어보았습니다~
다음에는 Collection Types를 읽어보도록 하겠습니다!

감사합니다🙇🏻‍♀️

profile
한달 차 iOS 개발자입니다🐥

0개의 댓글