애플사이다의 iOS 개발 일지

[Swift Language Guide 정독 시리즈] 3. Strings and Characters 본문

Swift/영문 공식문서 뜯어보기-Swift

[Swift Language Guide 정독 시리즈] 3. Strings and Characters

Applecider 2021. 9. 24. 01:08

안녕하세요. 애플사이다 입니다.

[Swift Language Guide 정독 시리즈]의 세 번째 챕터 Strings and Characters에 대해 정리해보겠습니다.

*Swift Language Guide를 읽어야 하는 이유는 시리즈 0. Language Guide란? 포스팅을 참고해주세요.

 


Strings and Characters (문자열 및 문자)

"hello, world"와 같이 문자열은 여러 개의 문자이다. Swift의 문자열은 String 타입으로 나타낸다.

문자열의 요소 (contents)에 접근하는 방법은 다양하다. 주로 Charater 타입 값의 Collection 형태로 접근한다.

 

Swift의 String 및 Character 타입은 코드의 텍스트를 처리하며, 빠르고 유니코드 호환 (Unicode-compliant)이 가능한 방식이다.

문자열 생성 및 문자열 다루기 (String Manipulation)는 가볍고 (lightweight) 가독성이 좋으며, C와 유사한 String 리터럴 문법을 활용할 수 있다. 

문자열 이어붙이기 (String Concatenation)는 + 연산자를 사용하는 것처럼 간단하고, 상수/변수 중 하나를 선택하여 문자열의 변경 가능 여부 (String Mutability)를 정할 수 있다.

또한 문자열 보간법 (String Interpolation)을 통해 문자열에 상수/변수/리터럴/수식을 삽입하여 보다 긴 문자열을 만들 수 있다. 

 

String 타입은 문법이 간단하며, 빠르고 현대적인 문자열 기능이 구현되어 있다. 모든 문자열은 인코딩에 독립적인 유니코드 문자 (encoding-independent Unicode characters)로 구성되어 있으며, 다양한 유니코드 표현을 통해 문자에 접근할 수 있다.

Swift’s String type is bridged with Foundation’s NSString class. Foundation also extends String to expose methods defined by NSString. This means, if you import Foundation, you can access those NSString methods on String without casting.

 

Swift의 String 타입은 Foundation 프레임워크의 NSString 클래스와 연결 (bridged) 되어 있다. 또한 Foundation 프레임워크는 NSString으로 정의된 메서드를 노출시키기 위해 String 타입을 확장 (extend)한다.

즉, Foundation 프레임워크를 import 하면, NSString의 메서드를 String에서 캐스팅 (Casting) 없이 사용할 수 있다.

*import : 프레임워크, 라이브러리, 애플리케이션 단위의 모듈 (Module)을 불러오는 키워드이다.

*Foundation 및 Cocoa에서 String을 사용하는 방법은 Bridging Between String and NSString 에서 확인할 수 있습니다.

String Literals (문자열 리터럴)

String 값을 소스코드에 직접 나타낸 것을 문자열 리터럴이라고 한다.

문자열 리터럴은 텍스트의 앞뒤에 큰따옴표 (double quotation marks, ")를 붙인 형태이다.

 

상수/변수의 초기값을 할당할 때, 문자열 리터럴을 사용한다.

let someString = "Some string literal value"
// 문자열 리터럴로 초기값을 지정했으므로 상수 someString의 타입은 String으로 추론 (infer)된다.

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

여러 줄에 걸친 문자열이 필요할 경우, 3개의 큰따옴표를 앞뒤에 붙인 multiline string literal을 사용한다.

문자열은 여는 따옴표 (opening quotation mark) 다음 줄부터 시작하고, 닫는 따옴표 (closing quotation mark) 이전 줄에서 끝난다.

 

문자열이 길어서 소스코드에서 문자를 읽기 어려울 때, 코드 줄의 끝부분에 역슬래쉬 (backslash, \)를 넣으면, 소스코드 상에서만 줄바꿈을 할 수 있다. 문자열값에는 아무런 영향이 없다.

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."
"""  // 줄 끝부분의 역슬래쉬 (\)는 문자열값에 아무런 영향이 없다. (소스코드를 쉽게 읽기 위해 줄을 바꾸는 기능이다.)

multiline string은 주변 코드와 일치하도록 들여쓰기 (indentation)할 수 있다.

닫는 따옴표의 위치를 기준으로 추가적인 공백 (whitespace)이 있으면, 해당 공백은 문자열에서 들여쓰기로 반영된다.

Multiline String Literals의 들여쓰기

Special Characters in String Literals (문자열 리터럴에 포함된 특수문자)

문자열 리터럴은 아래의 특수문자를 포함할 수 있다. 이 특수문자는 문자열에서 특별한 기능이 있으므로 제어문자라고도 한다.

  • escaped special character (escape character \ (역슬래쉬)를 사용하며, 문자열에서 탈출시켰다고 표현함) :
    \0 (null character, String 종료를 나타냄), \\ (backslash), \t (horizontal tab), 
    \" (double quotation mark), \' (single quotation mark),
    \n (line feed, 줄바꿈), \r (carriage return)
    *carriage return : 커서를 현재 행의 가장 왼쪽으로 옮기는 것이다. 현재 컴파일러는 /n으로 /r/n 기능을 대체하므로 /r은 필요 없다.)
  • 임의의 유니코드 스칼라값 (arbitrary Unicode scalar value) :
    \u{n} 형태로 쓴다. 이때 n은 1~8자리의 16진수 (hexadecimal number)이다.
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
  • 상수 wiseWords는 2개의 escaped 큰따옴표를 포함한다.
  • dollarSign, blackHeart, sparklingHeart 3개의 상수는 각각 유니코드 스칼라 포맷 (Unicode scalar format)을 나타낸다.

multiline string literal은 3개의 큰따옴표를 사용하므로 escaping 하지 않아도 큰따옴표를 포함할 수 있다.

multiline string이 텍스트 """ 를 포함하려면, 적어도 1개 이상의 큰따옴표를 escaping 해야 한다.

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
이것도 escape 된다 1) " 2) "" 3) "\"" 4) ""\"
"""

print(threeDoubleQuotationMarks)
/*
Escaping the first quotation mark """
Escaping all three quotation marks """
이것도 escape 된다 1) " 2) "" 3) """ 4) """
*/

Extended String Delimiters (확장된 문자열 구분자)

확장된 구분자 (extended delimiter) 내부에 문자열을 넣으면, 특수문자의 기능을 없앨 수 있다.

문자열을 나타내는 큰따옴표 앞뒤에 숫자 기호 (number sign) #을 붙인다.

 

특수문자의 기능을 선택적으로 사용하고 싶다면, #의 개수만큼 해당 특수문자의 \ 뒤에 #를 붙인다.

특수문자의 기능은 없애고, 문자열 보간법을 사용하고 싶다면, \ 뒤에 #를 붙인다.

print(#"Line 1\nLine 2"#)
// Line 1\nLine 2  (\n이 줄바꿈을 하지 않고, 문자 그대로 출력된다.)

print(#"Line 1\#nLine 2"#)
// Line 1
// Line 2  (줄바꿈을 한다.)

print(###"Line1\###nLine2"###)
// Line 1
// Line 2  (줄바꿈을 한다. #의 개수가 일치해야 한다.)

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#
print(threeMoreDoubleQuotationMarks)
// Here are three more double quotes: """  (텍스트 """를 아무런 영향없이 포함한다.)

// 특수문자의 기능은 없애고, 문자열 보간법을 사용하는 경우
let name: String = "AppleCider"
print(#""삶이 너에게 Apple을 준다면, \#(name)를 만들어라" - 애플사이다"#)
// "삶이 너에게 Apple을 준다면, AppleCider를 만들어라" - 애플사이다

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

보통 문자열을 이어붙이기를 할 때, 먼저 빈 문자열 (empty String)을 만들고 시작한다.

빈 문자열 값을 생성하는 방법은 1) 빈 문자열 리터럴을 할당하거나, 2) 이니셜라이저 문법을 통해 String 인스턴스를 생성하는 방법이 있다.

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

// Bool 타입의 isEmpty 프로퍼티를 통해 빈 문자열인지 확인할 수 있다.
if emptyString.isEmpty {  
    print("Nothing to see here")
}
// Prints "Nothing to see here"

String Mutability (문자열 변경 가능 여부)

문자열을 상수에 할당하면, 해당 문자열은 수정 불가 (immutable)하다. 반면 변수에 할당하면, 해당 문자열은 수정 가능 (mutable)하다.

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 (상수의 값을 변경하면 컴파일 에러가 발생한다.)

Note: NSString 클래스 또는 NSMutableString 클래스를 선택하여 문자열의 변경 가능 여부를 결정하는 Object-C 및 Cocoa의 접근법과 다르다. 

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

Swift의 String 타입은 값 타입 (value type)이다.

어떤 문자열 값을 1) 함수의 전달인자로 전달하거나, 2) 상수/변수에 할당할 때 해당 문자열 값의 복사값 (copied)이 전달된다.

매번 새로운 복사값이 생성되고, 해당 복사값이 전달되거나 할당되는 것이다.

참고 - C에서 String은 Character의 Array이며, 참조 타입 (reference type)이다. 참조 타입은 복사값 대신 주소 (reference)를 전달한다.

값 타입은 Structures and Classes 챕터의 Structures and Enumerations Are Value Types 섹션에서 자세히 다루겠습니다.

 

String의 이러한 복사값 전달방식 (copy-by-default String behavior) 덕분에 어떤 함수로부터 문자열값을 전달받은 경우, 정확히 해당 문자열값이 들어있다고 확신할 수 있다. 원본 문자열이 변경되어도 복사값에는 아무런 영향이 없다.

 

✅ 실제로는 Swift의 컴파일러는 문자열 사용을 최적화 (optimize) 한다. 따라서 복사가 필요한 상황이 발생해야만 메모리 상에서 복사를 수행한다.

즉, 처음부터 복사본을 위한 메모리를 새로 할당하지 않고, 일단 원본의 메모리를 공유한다. (*값 타입에서 원본과 복사본의 메모리 주소는 같다. 반면, 참조 타입에서 원본과 원본의 참조를 가리키는 변수의 메모리 주소는 다르다.) 그러다가 복사본이 수정되는 상황이 발생하면, 수정 직전에 복사본에 새로운 메모리를 할당한다.

이것을 Copy-on-write라고 한다. 쓰기 (수정)를 하는 시점에 복사한다는 의미이다. 메모리 최적화를 위한 기능이다. 값 타입인 Int, Double, String, Array, Set, Dictionary에 적용된다.

Working with Characters (문자 다루기)

String의 개별적인 Character 값에 접근하려면, for-in문을 사용한다.

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

Charater 타입 지정 (Type Annotation)을 하고, single-character string literal를 사용하여 1개의 문자가 들어있는 Character 타입의 상수/변수를 생성할 수 있다.

String 이니셜라이저의 전달인자로 Character 타입의 배열 (Array)을 전달하여 String 값을 생성할 수 있다.

let exclamationMark: Character = "!"   // 타입 지정

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

Concatenating Strings and Characters (문자열 및 문자 이어붙이기)

1) 더하기 연산자 + 또는 더하기 할당 연산자 += 를 사용하여 문자(열)을 이어붙인다.
*성능 측면에서 문자열 보간법을 사용하는 것이 + 연산자를 사용하는 것보다 좋다.

또한, 2) String 타입의 append 메서드를 통해 String 타입 변수에 문자(열)을 이어붙인다.

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

Note: Character 타입의 변수에 문자(열)을 이어붙일 수 없다. Character는 1개의 문자를 나타내는 타입이기 때문이다.

 

여러 개의 multiline string literal을 사용하여 문자열을 이어붙이는 경우, 줄바꿈에 유의한다.

let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree (주의 - two와 three 사이에 줄바꿈이 없다. 상수를 각각 print를 해야 매개변수 terminator에 의해 자동으로 줄바꿈이 된다.)

let goodStart = """
one
two

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

String Interpolation (문자열 보간법)

문자열 리터럴 내부에 상수/변수/리터럴/수식의 값을 삽입하여 문자열을 구성할 때 유용한 방법이다.

single-line 및 multiline string literal 모두 문자열 보간법을 사용할 수 있다.

 

또한, extended string delimiters를 사용하여 문자열 보간법 기능을 없앨 수 있다.

특수기호의 기능은 없애고, 문자열 보간법을 사용하려면 \ 뒤에 #를 붙인다.

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."  // 특수문자 기능은 없애고, 문자열 보간법은 사용한다.

print("문자열 보간법의 괄호 내부에 \("문자열 리터럴1")은 넣을 수 있다")  // 문자열 보간법의 괄호 내부에 문자열 리터럴1은 넣을 수 있다
print("문자열 보간법의 괄호 내부에 \("문자열 리터럴1", "라터럴2")은 넣을 수 있다") // 컴파일 에러 발생 - Extra argument in call
print("문자열 보간법의 괄호 내부에 줄바꿈 \(\n)을 넣을 수 없다")  // 컴파일 에러 발생 - Cannot find 'n' in scope
  • 문자열 보간법이 평가 (evaluate)되는 시점에, placeholder인 \(multiplier)는 상수 multiplier의 값으로 대체된다. 
  • 수식 Double(multiplier) * 2.5의 결과값인 7.5가 문자열에 삽입된다.
Note: The expressions you write inside parentheses within an interpolated string can’t contain an unescaped backslash (\), a carriage return, or a line feed. However, they can contain other string literals.

 

문자열 보간법 \()의 괄호 내부에는 \, \r, \n 를 넣을 수 없으며, 다른 문자열 리터럴은 넣을 수 있다.

Unicode (유니코드)

유니코드는 여러 시스템 간에 텍스트를 *인코딩 (Encoding), 표현, 처리하기 위한 국제 표준이다.

유니코드를 사용하여 거의 모든 언어의 문자를 표현하고, 텍스트 파일 또는 웹 페이지 등 외부 소스의 문자를 읽고 쓸 수 있다.

Swift의 String 및 Character 타입은 *유니코드를 기준 (Unicode-compliant)으로 한다. 즉, 유니코드에서 지원하는 모든 문자를 사용할 수 있다.

 

*유니코드는 전 세계 언어의 문자를 컴퓨터로 다루기 위한 표준이다. 유니코드 인코딩을 통해 시스템이 달라도 동일한 방법으로 유니코드를 해석하여 사용할 수 있다. 

유니코드 및 유니코드 인코딩이 필요한 이유 (간단 요약) 포스팅을 참고해주세요.

Unicode Scalar Values (유니코드 스칼라값)

Swift의 String 타입은 유니코드 스칼라값으로부터 만들어진다. 유니코드 스칼라값은 문자 또는 수식어 (modifier)를 나타내는 21bit의 특수한 숫자이다. ex) 유니코드 스칼라값 U+0061 = "a" (LATIN SMALL LETTER A, 소문자 A), 유니코드 스칼라값 U+1F425 = "🐥" (FRONT-FACING BABY CHICK, 앞을 보는 병아리 이모티콘)

 

*그래서 유니코드 스칼라값이 뭘까?

앞서 문자 코드표를 통해 문자를 컴퓨터가 이해할 수 있는 숫자 (0&1)로 변환한다고 했다. 유니코드 스칼라값은 "유니코드 문자에 배당된 숫자"라고 할 수 있다. 보통 "U+" + 16진수 형태이다. U+0000부터 U+D7FF까지, U+E000부터 U+10FFFF까지의 값이 있다.

(문자 배당에서 제외된 U+D800부터 U+DFFF까지의 값이 남아있다.)

 

21bit의 유니코드 스칼라값이 모두 문자 표현에 할당된 것은 아니다. (제외된 U+D800..DFFF을 말한다.) 일부 값은 나중에 할당하거나 UTF-16 인코딩 시 Surrogate 값으로 사용하도록 지정되어 있다.

문자 표현에 할당된 스칼라값은 위 예시의 LATIN SMALL LETTER A, FRONT-FACING BABY CHICK 와 같이 스칼라값의 이름이 있다.

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

*Grapheme이란?

문자소, 즉 의미를 나타내는 최소의 문자 단위를 의미한다.

영어에서 보통 음소 (의미를 나타내는 음성상의 최소 단위)와 일치한다. 예를 들면 fun의 "f", phantom의 "ph", laugh의 "gh"이 하나의 grapheme이다. 한글에서는 자모음 단위와 일치한다. 예를 들면 사의 "ㅅ", 이의 "ㅇ", 다의 "ㄷ"이 하나의 grapheme이다.

 

1개의 Character 타입의 인스턴스는 1개의 Extended grapheme cluster를 나타낸다.

1개의 extended grapheme cluster는 1개 이상의 유니코드 스칼라로 구성된다. 이것은 사람이 읽을 수 있는 문자를 표현한다.

 

예를 들어 문자 é 를 유니코드 스칼라로 표현하는 방법은 두 가지이다.

1) 1개의 유니코드 스칼라로 표현 - é (LATIN SMALL LETTER E WITH ACUTE, or U+00E9)

2) 2개의 유니코드 스칼라로 표현 - e (LATIN SMALL LETTER E, or U+0065) 및   ́ (COMBINING ACUTE ACCENT, or U+0301)

COMBINING ACUTE ACCENT 스칼라는 바로 앞의 스칼라에 적용되며, 유니코드를 인식하는 텍스트 해석 시스템 (Unicode-aware text-rendering system)에서 e 를 é 로 바꾼다.

 

두 방법 모두 문자 é 는 1개의 Character 값에 해당하며, 1개의 extended grapheme cluster를 나타낸다.

표현 방법은 다르지만, 동일한 Character로 간주한다. 

let eAcute: Character = "\u{E9}"                 // é
let combinedEAcute: Character = "\u{65}\u{301}"  // e followed by
// eAcute is é, combinedEAcute is é
 
print(eAcute == combinedEAcute) // true - 표현 방법은 다르지만, 동일한 Character로 간주한다.

Extended grapheme cluster는 복잡하게 구성된 문자를 1개의 Character 값으로 표현하는 유연한 방식이다.

예를 들어 한글 음절 (Hangul syllables)은 1) 이미 결합된 형태 (precomposed) 또는 2) 분해된 형태 (decomposed)로 표현할 수 있다.

마찬가지로 두 방법 모두 동일한 Character로 간주한다.

let precomposed: Character = "\u{D55C}"                  // 한 
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ᄒ, ᅡ, ᆫ 
// precomposed is 한, decomposed is 한

print(precomposed == decomposed) // true

Extended grapheme cluster를 통해 스칼라로 표현한 다른 문자를 변형한 문자, 국기 이모티콘 등을 만들 수 있다.

  • enclosing mark (둘러싸는 기호)를 나타내는 스칼라 (ex. COMBINING ENCLOSING CIRCLE, or U+20DD)를 사용하여 다른 문자를 원으로 둘러싼 형태의 문자로 표현할 수 있다. 이것도 1개의 Character 값이다.
  • regional indicator symbols (지역 표시 기호)를 나타내는 스칼라 (ex. REGIONAL INDICATOR SYMBOL LETTER U (U+1F1FA), REGIONAL INDICATOR SYMBOL LETTER S (U+1F1F8))를 결합하여 국기 이모티콘 문자로 표현할 수 있다. 
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝ - 다른 문자 é를 변형하여 é를 둘러싼 원 형태의 문자를 만들었다.
print(eAcute == enclosedEAcute) // false

let regionalIndicatorSymbolU = "\u{1F1FA}" // 🇺
let regionalIndicatorSymbolS = "\u{1F1F8}" // 🇸
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}" 
// regionalIndicatorForUS is 🇺🇸 - 다른 문자 🇺, 🇸를 결합하여 국기 이모티콘 문자를 만들었다.

// 참고
//print(regionalIndicatorSymbolU+regionalIndicatorSymbolS == regionalIndicatorForUS) 
// 컴파일 에러 발생 - Binary operator '==' cannot be applied to operands of type 'String' and 'Character'

let combinedSymbol: Character = Character(regionalIndicatorSymbolU + regionalIndicatorSymbolS) // Cannot convert value of type 'String' to specified type 'Character' -> Character 인스턴스 생성 필요
print(combinedSymbol == regionalIndicatorForUS) // true

Counting Characters (문자 개수 세기)

문자열 (String)에서 문자 (Character)의 개수를 셀 경우, String의 count 프로퍼티를 사용한다.

 

Character 값을 표현할 때 extended grapheme cluster를 사용하므로 문자열 이어붙이기 및 문자열 수정 (string concatenation and modification)이 문자열의 문자 개수에 영향이 없을 수도 있다.

예를 들어 문자열 "cafe" 뒤에 COMBINING ACUTE ACCENT (U+0301) 스칼라를 이어붙이면, "café"가 되므로 문자 개수는 동일하다.

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters") // String의 count 프로퍼티 사용
// 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" - 문자 개수는 4개이다.

word += "\u{301}"    // COMBINING ACUTE ACCENT, U+0301 - 스칼라를 이어붙여서 e가 é로 변경된다.

print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in café is 4" - 여전히 문자 개수는 4개이다.
Note: Extended grapheme clusters can be composed of multiple Unicode scalars. This means that different characters—and different representations of the same character—can require different amounts of memory to store. Because of this, characters in Swift don’t each take up the same amount of memory within a string’s representation. As a result, the number of characters in a string can’t be calculated without iterating through the string to determine its extended grapheme cluster boundaries.

The count of the characters returned by the count property isn’t always the same as the length property of an NSString that contains the same characters. The length of an NSString is based on the number of 16-bit code units within the string’s UTF-16 representation and not the number of Unicode extended grapheme clusters within the string.

 

✅ Extended grapheme cluster는 1개 또는 여러 개의 유니코드 스칼라로 구성된다. 따라서 문자마다 (동일한 문자더라도 다른 방법으로 표현된 경우를 포함) 문자를 저장하기 위해 필요한 메모리 용량이 다를 수 있다. 이 때문에 Swift에서는 문자열의 각 문자 (Character)가 동일한 메모리 공간을 차지하지 않는다.

결과적으로 문자열에서 문자의 개수를 셀 경우, 문자열을 반복 (iterate)하여 extended grapheme cluster 경계 (boundary)를 확인해야만 한다.

 

✅ 동일한 문자열이더라도 String의 count 프로퍼티를 통해 구한 문자의 개수는 NSString의 length 프로퍼티를 통해 구한 값과 다를 수 있다. NSString의 length는 UTF-16 표현 방식으로 16bit 단위의 개수를 기반으로 하기 때문이다. (String의 count는 유니코드의 extended grapheme cluster의 개수를 기반으로 한다.)

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

문자열 접근 및 수정은 1) String의 메서드 및 프로퍼티를 사용하거나 2) 서브스크립트 (Subscript) 문법을 사용하는 방법이 있다.

*서브스크립트는 Subscripts 챕터에서 자세히 다루겠습니다.

String Indices (문자열 인덱스)

String 값은 인덱스 타입 (index type)인 String.Index를 가진다. 이것은 문자열 내에서 각 문자의 위치 (position)에 해당한다.

 

위에서 언급했듯이 문자마다 문자를 저장하기 위해 필요한 메모리 용량이 다를 수 있다. 따라서 특정 위치에 어떤 문자가 있는지 확인하기 위해 해당 문자열의 처음 또는 끝에서부터 각각의 유니코드 스칼라를 반복 (iterate)해야 한다.

❗️이 때문에 Swift에서는 문자열을 정수값으로 인덱싱하여 접근할 수 없다. (can’t be indexed)

 

1) String의 메서드 및 프로퍼티를 사용

startIndex 프로퍼티를 사용하면, 문자열에서 첫 번째 문자의 위치에 접근한다.

endIndex 프로퍼티를 사용하면, 문자열에서 마지막 문자의 다음의 위치 (the position after the last character)에 접근한다.

따라서 endIndex 프로퍼티는 String의 서브스크립트에 대해 유효한 전달인자가 아니다. 

빈 문자열이면, startIndex 프로퍼티 값과 endIndex 프로퍼티 값은 동일하다.

 

String의 index(before:) / index(after:) 메서드를 사용하면, 주어진 인덱스의 1칸 앞 / 뒤에 접근할 수 있다. 

index(_:offsetBy:) 메서드를 사용하면, 주어진 인덱스에서 (n칸만큼) 멀리 떨어진 위치에 접근할 수 있다. (+ 뒤, - 앞)

indices 프로퍼티를 사용하면, 문자열 내 모든 문자의 각 인텍스에 접근한다.

 

2) 서브스크립트 문법을 사용

서브스크립트 문법을 사용하면, 문자열 내의 특정 인덱스의 문자에 접근할 수 있다. (instance[index] 형태이다.)

단, 문자열 범위를 벗어난 위치의 인덱스 또는 문자에 접근하면, 런타임 에러가 발생한다. (컴파일 에러가 아니다.)

let greeting = "Guten Tag!"
greeting[greeting.startIndex] // G 
// 1) 첫번째 문자 G의 위치에 접근하고, 2) 해당 index를 통해 서브스크립트 문법을 사용한다.

greeting[greeting.index(before: greeting.endIndex)] // ! 
// 1) 마지막 문자 !의 다음 위치에 접근하고, 2) 해당 index를 메서드의 argument로 전달하여 해당 위치의 1칸 앞에 접근, 3) 해당 index를 통해 서브스크립트 문법을 사용한다.

greeting[greeting.index(after: greeting.startIndex)] // u

let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a
// 1) 첫번째 문자 G의 위치에 접근하고, 2) 해당 index를 메서드 argument로 전달하여 해당 위치의 +7칸 뒤에 접근, 3) 해당 index를 통해 서브스크립트 문법을 사용한다.

// 참고
print(greeting.startIndex) // Index(_rawBits: 1) - 이 자체가 Index type이고, 서브스크립트 instance[index]의 index 부분에 전달된다.
print(greeting.endIndex)   // Index(_rawBits: 655361)
print(greeting.index(before: 3)) // 컴파일 에러 - Cannot convert value of type 'Int' to expected argument type 'String.Index'

// 문자열 범위를 벗어난 위치에 접근
greeting[greeting.endIndex] // 런타임 에러 (Fatal error: String index is out of bounds)
greeting.index(after: greeting.endIndex) // 런타임 에러

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

*print 함수의 terminator 매개변수에 대한 설명은 print 함수로 줄바꿈 없이 출력, 문자열 사이 구분하기 포스트를 참고해주세요.

Note: You can use the startIndex and endIndex properties and the index(before:), index(after:), and index(_:offsetBy:) methods on any type that conforms to the Collection protocol. This includes String, as shown here, as well as collection types such as Array, Dictionary, and Set.

 

startIndex/endIndex 프로퍼티, index(before:)/index(after:)/index(_:offsetBy:) 메서드는 Collection protocol을 준수하는 모든 타입에서 사용할 수 있다. 즉, String 외에도 collection 타입인 Array (배열), Dictionary (딕셔너리), Set (집합)에서 사용 가능하다.

Inserting and Removing (삽입 및 삭제)

insert(_:at:) 메서드를 사용하면, 문자열의 특정 인덱스에 1개의 문자를 삽입한다.

insert(contentsOf:at:) 메서드를 사용하면, 문자열의 특정 인덱스에 다른 문자열을 삽입한다.

 

remove(at:) 메서드를 사용하면, 문자열의 특정 인덱스의 1개 문자를 삭제한다.

removeSubrange(_:) 메서드를 사용하면, 문자열의 특정 인덱스 범위의 부분 문자열 (Substring)을 삭제한다.

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"
Note: You can use the insert(_:at:), insert(contentsOf:at:), remove(at:), and removeSubrange(_:) methods on any type that conforms to the RangeReplaceableCollection protocol. This includes String, as shown here, as well as collection types such as Array, Dictionary, and Set.

 

insert(_:at:)/insert(contentsOf:at:)/remove(at:)/removeSubrange(_:) 메서드는 RangeReplaceableCollection을 준수하는 모든 타입에서 사용할 수 있다. 즉, String 외에도 collection 타입인 Array (배열), Dictionary (딕셔너리), Set (집합)에서 사용 가능하다.

Substrings (부분 문자열)

서브스크립트 문법을 사용하거나, prefix(_:) 메서드를 사용하여 문자열 (String)으로부터 부분 문자열 (Substring)을 얻은 경우, 그것은 다른 문자열이 아니라 부분 문자열 타입의 인스턴스이다.

Swift의 부분 문자열은 문자열과 대부분 동일한 메서드를 가진다. 따라서 문자열을 다루는 방식대로 부분 문자열을 처리할 수 있다.

 

✅ 문자열과 부분 문자열 모두 문자를 저장하기 위한 메모리 공간을 가진다. 둘의 차이점은 성능 최적화 (performance optimization)로 인해 부분 문자열이 원본인 문자열 (또는 다른 부분 문자열)을 저장하는 메모리의 일부를 재사용 (reuse)한다는 것이다. 

(문자열도 비슷한 최적화를 하지만, 두 문자열이 메모리를 공유하면 두 문자열은 동일하다.)

 

이러한 성능 최적화로 인해 문자열 또는 부분 문자열을 수정하기 전까지는 실제로 메모리를 복사하지 않아도 된다는 이점이 있다. 

❗️하지만 이 때문에 부분 문자열은 단기간 사용하는 것이 바람직하다. 부분 문자열을 1개라도 사용하고 있는 중이면, 원본 문자열 전체를 메모리에 저장하고 있어야 하기 때문이다. 장기간 저장해야 한다면 부분 문자열을 문자열 인스턴스로 변환해야 한다.

(Swift에서 타입 변환은 새로운 타입의 인스턴스를 생성하는 것이다.)

// 부분 문자열 생성방법-1. 서브스크립트 사용
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex // 문자열의 앞에서부터 "," 문자를 찾으며, ","가 처음 나타나는 위치의 인덱스를 할당한다.
let beginning = greeting[..<index] // beginning is "Hello"

print(type(of: index))     // Index 출력 - type(of:) 메서드로 타입을 확인한다.
print(type(of: beginning)) // Substring 출력 

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

// 부분 문자열 생성방법-2. prefix 메서드 사용
let greeting2 = "Hello! AppleCider"
greeting2.prefix(6)   // "Hello!"
greeting2.prefix(100) // "Hello! AppleCider" - 전달인자로 문자열 길이보다 큰 값을 전달해도 crash가 발생하지 않는다.

let index2 = greeting2.index(greeting2.startIndex, offsetBy: 5) // 첫번째 문자의 위치에서 +5칸 뒤의 index를 상수에 할당한다. (문자 "!"의 위치)
greeting2.prefix(through: index2) // "Hello!" - 서브스크립트 greeting[...index]와 동일하다.
greeting2.prefix(upTo: index2)    // "Hello" - 서브스크립트 greeting[..<index]와 동일하다. ("!"를 포함하지 않음)

print(type(of: greeting2.prefix(through: index2))) // Substring
print(type(of: greeting2.prefix(upTo: index2)))    // Substring

 

위 예시에서 1) 원본 문자열인 greeting, 2) 해당 문자열의 부분 문자열인 beginning, 3) 해당 부분 문자열을 타입 변환한 문자열인 newString의 메모리 상 관계는 다음과 같다.

문자열의 일부 메모리를 재사용하는 부분 문자열

Note: Both String and Substring conform to the StringProtocol protocol, which means it’s often convenient for string-manipulation functions to accept a StringProtocol value. You can call such functions with either a String or Substring value.

 

문자열 및 부분 문자열 모두 StringProtocol protocol을 준수한다. 따라서 문자열을 조작하는 함수 (string-manipulation functions)가 StringProtocol protocol 값을 수용하면 편리하게 사용할 수 있다. 이러한 함수는 문자열값이나 부분 문자열값으로 호출할 수 있다.

Comparing Strings (문자열 비교)

Swift에는 텍스트값을 비교하는 세 가지 방법이 있다. 1) string and character equality, 2) prefix equality, 3) suffix equality.

String and Character Equality (문자열 및 문자 동일 여부)

값이 같음 연산자 (equal to operator) == 값이 다름 연산자 (not equal to operator) != 를 통해 확인한다.

*비교 연산자에 대한 자세한 내용은 [Swift Language Guide 정독 시리즈] 2. Basic Operators의 비교 연산자를 참고해주세요.

 

2개의 String 값이 있을 때, 해당 extended grapheme clusters가 "표준적으로 동일" (canonically equivalent)하면, 두 문자열은 동일하다고 간주한다. extended grapheme clusters가 표준적으로 동일하다는 것은 무슨 뜻일까? 서로 다른 유니코드 스칼라로 구성되었더라도 언어적 의미와 형태가 동일하다 (the same linguistic meaning and appearance)는 의미이다.

 

예를 들어, 앞에서 봤듯이 문자 é 를 구성하는 두 가지 방법이 있다. 

1) 1개의 유니코드 스칼라로 표현 - é (LATIN SMALL LETTER E WITH ACUTE, or U+00E9)

2) 2개 (a pair)의 유니코드 스칼라로 표현 - e (LATIN SMALL LETTER E, or U+0065) 및   ́ (COMBINING ACUTE ACCENT, or U+0301)

다른 방법으로 구성되었더라도 문자 é가 되어 의미와 형태가 동일하다. 따라서 둘의 extended grapheme clusters는 표준적으로 동일 (canonically equivalent)하다.

 

반대로 영어 문자인 LATIN CAPITAL LETTER A (U+0041, or "A")는 러시아어 문자인 CYRILLIC CAPITAL LETTER A (U+0410, or "А")과 표준적으로 동일하지 않다. 문자의 형태는 동일해 보이지만, 의미가 다르기 때문이다.

// 비교연산자 ==를 통해 동일한 문자열인지 확인한다.
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"

// 스칼라 구성이 다르더라도 문자의 의미와 형태가 같으므로 동일한 문자열이다.
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?" // "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?" // "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

// 영어의 A, 러시아어의 A는 문자의 의미가 다르므로 동일하지 않은 문자열이다.
let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters aren't equivalent.")
}
// Prints "These two characters aren't equivalent."
Note: String and character comparisons in Swift aren’t locale-sensitive.

 

Swift의 문자열 및 문자 비교는 국가별 언어 규칙의 영향을 받지 않는다. (aren’t locale-sensitive)

*locale-sensitive가 무슨 뜻일까?

국가별로 알파벳 구성이 다르며, 알파벳 정렬 기준도 다르다. 또한 문자를 구성하는 규칙도 다르다.

러한 언어 규칙은 사용자의 locale 정보에 포함되어 있으며, locale-sensitive는 이러한 국가별 언어 규칙의 영향을 받는다는 것을 뜻한다.

 

(라틴계 언어를 사용하는 유럽 국가들은 국가가 달라도 동일하게 사용하는 알파벳이 존재할 수 있지만, 국가별 알파벳 정렬 기준은 다르다.

예를 들어 핀란드어 ["ǟψ", "äψ", "ǟx", "äx"]를 핀란드 locale의 알파벳 순으로 정렬하면 ["äx", "äψ", "ǟx", "ǟψ"]가 된다.

스웨덴어 ["ǟ", "ä", "ã", "a", "ă", "b"]를 스웨덴 locale의 알파벳 순으로 정렬하면 ["a", "ă", "ã", "b", "ä", "ǟ"]가 된다.)

- Reference : [Reddit] locale-sensitive?

Prefix and Suffix Equality (접두사 및 접미사 동일 여부)

hasPrefix(_:) / hasSuffix(_:) 메서드를 호출하면, 문자열이 특정한 접두사 또는 접미사를 가지는지 확인한다.

이 메서드는 String 값 1개를 전달인자로 받으며, 불리언값을 반환한다.

아래 예시에서 String 타입 배열에 메서드를 사용했다.

// representing the scene locations from the first two acts of Shakespeare’s Romeo and Juliet.
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"
]

// hasPrefix(_:) 메서드 사용
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"

// hasSuffix(_:) 메서드 사용
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"
Note: The hasPrefix(_:) and hasSuffix(_:) methods perform a character-by-character canonical equivalence comparison between the extended grapheme clusters in each string.

 

hasPrefix(_:) / hasSuffix(_:) 메서드는 문자열의 extended grapheme clusters가 표준적으로 동일 (canonically equivalent)한지 확인할 때, 한 문자씩 (character-by-character) 비교한다.

Unicode Representations of Strings (문자열을 유니코드로 표현하기)

유니코드 문자열 (Unicode string)을 텍스트 파일이나 다른 저장공간에 쓸 때 (write), 해당 문자열의 유니코드 스칼라는 유니코드로 정의된 인코딩 방식 (encoding forms)으로 인코딩 (encoded)된다.

인코딩 방식은 여러 종류가 있고, 각 인코딩 방식은 코드 단위 (code units)라고 부르는 작은 덩어리 단위로 문자열을 나누어 인코딩한다.

예를 들어 UTF-8 / UTF-16 / UTF-32 인코딩 방식 (문자열을 8 / 16 / 32 bit 코드 단위로 인코딩함)이 있다. 

유니코드 및 유니코드 인코딩이 필요한 이유 (간단 요약) 포스팅을 참고해주세요.

 

Swift에서 유니코드로 표현한 문자열에 접근하는 여러 가지 방법이 있다.

1) for-in문을 사용하여 문자열을 반복 (iterate)하여 각각의 Character 값에 Unicode extended grapheme cluster 단위로 접근한다.

* 이 포스팅의 Working with Characters 섹션을 참고해주세요.

 

또는, 2) 세 종류의 인코딩 방식에 따라 String 값에 접근한다.

  • A collection of UTF-8 코드 단위 (String의 utf8 프로퍼티로 접근함)
  • A collection of UTF-16 코드 단위 (String의 utf16 프로퍼티로 접근함)
  • A collection of 21-bit 유니코드 스칼라값, UTF-32 인코딩 방식과 동일하다 (String의 unicodeScalars 프로퍼티로 접근함)

아래 예시의 문자열을 표현하는 여러 가지 인코딩 방식을 살펴보자.

let dogString = "Dog‼🐶"
// 문자 D, o, g, ‼ (DOUBLE EXCLAMATION MARK, or Unicode scalar U+203C), 🐶  (DOG FACE, or Unicode scalar U+1F436)로 구성된 문자열이다.

UTF-8 Representation (문자열을 UTF-8로 표현하기)

문자열을 UTF-8로 표현한 것에 접근하려면, String의 utf8 프로퍼티를 for-in문으로 반복 (iterate)한다.

utf8 프로퍼티의 타입은 String.UTF8View 이며, UInt8 (부호가 없는 8bit의 정수 타입)의 묶음 (colletion)이다.

UTF-8 코드 단위로 문자열을 나누었다.

for codeUnit in dogString.utf8 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "
  • 문자열을 UTF-8 인코딩 방식으로 표현한 값이 출력된다. (문자열을 UTF-8 코드 단위로 나누어 표현한 상수 codeUnit 값이 10진수로 출력된다.)
  • 68, 111, 103 : 앞의 3개 값은 문자 D, o ,g 를 나타낸다. 이 문자의 UTF-8 표현은 ASCII 표현과 동일하다. (1byte 크기)
  • 226, 128, 188 : 중간의 3개 값은 문자를 나타낸다. UTF-8 표현으로 3byte 크기이다.
  • 240, 159, 144, 182 : 뒤의 4개 값은 🐶 문자를 나타낸다. 이 문자는 UTF-8 인코딩 방식에서 4byte 크기이다.

UTF-16 Representation (문자열을 UTF-16으로 표현하기)

문자열을 UTF-16으로 표현한 것에 접근하려면, String의 utf16 프로퍼티를 for-in문으로 반복 (iterate)한다.

utf16 프로퍼티의 타입은 String.UTF16View 이며, UInt16 (부호가 없는 16bit의 정수 타입)의 묶음 (colletion)이다.

UTF-16 코드 단위로 문자열을 나누었다.

for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "
  • 문자열을 UTF-16 인코딩 방식으로 표현한 값이 출력된다. (문자열을 UTF-16 코드 단위로 나누어 표현한 상수 codeUnit 값이 10진수로 출력된다.)
  • 68, 111, 103 : 앞의 3개 값은 문자 D, o ,g 를 나타낸다. 이 문자들의 UTF-16 표현은 ASCII 표현과 동일하다. (1byte 크기)
    ASCII 표현과 동일하므로 UTF-8 표현과도 동일하다.
  • 8252 : 문자를 나타낸 값이다.  문자의 유니코드 스칼라값은 "U+203C"이며, 16진수 203C는 10진수로 8252 이다. 
    이 문자는 UTF-8 표현으로 1byte 크기이다. (UTF-8 표현으로는 3byte 크기이다.)
  • 55357, 56374 : 🐶 문자를 나타낸 값이다. 엄밀히는 🐶 문자를 UTF-16 *Surrogate pair로 표현한 값이다.
    이 값은 high-surrogate 값 (U+D83D, 10진수로 55357) 및 low-surrogate 값 (U+DC36 (10진수로 56374)이다.
    이 문자는 UTF-16 인코딩 방식에서 2byte 크기이다.

*Surrogate란?

UTF-16 인코딩 방식에서 16bit로 값을 표현할 수 없는 문자들이 존재한다. 이 문자들은 Surrogate 영역에 해당하는 2개의 16bit 문자를 결합하여 나타낸다. high-surrogate (또는 leading-surrogate) 값은 D80016에서 DBFF16까지 1024개 값, low-surrogate (또는 trailing-surrogate) 값은 DC0016에서 DFFF16까지 1024개 값이 지정되어 있다. (앞에서 21bit의 유니코드 스칼라값 중에서 "유니코드 문자에 배당되지 않은 숫자"의 일부는 UTF-16 인코딩의 Surrogate 값으로 사용된다고 설명했다.)

Unicode Scalar Representation (문자열을 유니코드 스칼라 / UTF-32로 표현하기)

문자열을 유니코드 스칼라값으로 표현한 것에 접근하려면, String의 unicodeScalars 프로퍼티를 for-in문으로 반복 (iterate)한다.

utf32 프로퍼티의 타입은 UnicodeScalarView 이며, UnicodeScalar 타입 값의 묶음 (colletion)이다.

UnicodeScalar 타입은 value 프로퍼티를 가지며, 21bit의 스칼라값을 반환한다. 이 스칼라값은 UInt32 (부호가 없는 32bit 정수 타입) 값으로 나타낸다.

유니코드 스칼라 코드 단위로 문자열을 나누었다.

for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "
  • 문자열을 유니코드 스칼라로 표현한 값이 출력된다. (문자열을 유니코드 스칼라 코드 단위로 나누어 표현한 상수 scalar 값이 10진수로 출력된다.)
  • 68, 111, 103 : 앞의 3개 값은 문자 D, o ,g 를 나타낸다. 이 문자들의 유니코드 스칼라 표현은 ASCII 표현과 동일하다. (1byte 크기)
    ASCII 표현과 동일하므로 UTF-8, UTF-16 표현과도 동일하다.
  • 8252 :  문자를 나타낸 값이다.  문자의 유니코드 스칼라값은 "U+203C"이며, 16진수 203C는 10진수로 8252 이다. 
    이 문자는 유니코드 스칼라 표현으로 1byte 크기이다. 이 문자의 유니코드 스칼라 표현은 UTF-16 표현과 동일하다. 
  • 128054 : 🐶 문자를 나타낸 값이다. 🐶 문자의 유니코드 스칼라값은 "U+1F436"이며, 16진수 1F436는 10진수로 128054 이다.  
    이 문자는 유니코드 스칼라 표현으로 1byte 크기이다. (UTF-8로는 4byte, UTF-16으로는 2byte 크기이다.)

value 프로퍼티에 접근하는 방법 대신, 아래와 같이 UnicodeScalar값 자체로 새로운 String 값을 생성할 수 있다.

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

 

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments