String 자료형은 Character 의 연속체로서 "hello, world" 나 "albatross" 와 같은 것을 의미합니다. Swift 의 자료형 String 과 Character 는 코드 속의 텍스트를 유니코드와 빠르게 호환할 수 있도록 합니다.
Note: Swift 의 String 자료형은 Foundation 내 NSString 와 연결되어 있습니다. Foundation 역시 String 자료형을 표현하기 위해 NSString 를 정의하고 있습니다. 이에 따라, Foundation 을 import 한다면 String 에서 NSSring 메소드들을 별다른 캐스팅 없이 사용할 수 있게 됩니다.
자세한 사항은 Bridging Between String and NSString 을 참고해주세요.
Swift 에서 문자열 리터럴은 ""
에 감쌓인 캐릭터들의 연속체들입니다.
다음과 같이 문자열 리터럴을 초기화할 수 있습니다.
let someString = "Some string literal value"
번역 : 리터럴은 데이터 값 그 자체를 의미한다.
예시) let a = 1
a는 변수 / 1은 리터럴 이다.
필요하다면 다중 문자열 리터럴 - 캐릭터 연속체가 3개의 "
로 감싸인 형태- 를 사용할 수 있습니다.
let quotation = """
하얀 토끼는 자신의 안경을 내려놓았다. 그는 "어디서부터 시작하면 될까요,
주인님?" 라고 물어왔다.
"네가 시작한 곳에서 시작해라," 왕은 장엄하게 답하였다. "그리고 네가 마지막에 다다를 때 까지 가서, 그곳에서 멈추어라."
"""
// 번역 : Velog 의 편집기가 """ """ 를 인식 못 하고 있지만,
// 실제 사용 시 전문이 하나의 String 으로 들어가게 됩니다.
이때 가독성을 위해, 백슬래시(\) 를 사용하여 코드 내에서 띄워쓰기를 표현할 수 있습니다.
한편 종결 """
에서 공백 문자를 포함하면 다중 문자열 리터럴의 포맷에 들여쓰기를 할 수 있습니다. 이때 들여쓰이는 내용은 코드에만 반영될 뿐, 실제 출력에는 영향을 미치지 않습니다.
상단의 예시에서 종결 """
에 4개의 공백문자를 쓰면 """
"""
내부의 문자열은 4개의 공백문자를 무시하게 됩니다. 2번째 문단을 보면 4개의 공백문자가 추가로 들어가있는데 이는 반영되어 4개의 공백문자 이후에 문자열이 시작됩니다.
문자열 리터럴은 다음과 같은 특별한 캐릭터들을 사용할 수 있습니다.
\
, \0 == null
, \\ == 백슬래시
, \t == 수평 탭
, \n == 한줄 띄기
, \r == 리턴
, \" == 큰 따옴표
, \' == 작은 따옴표
\u{n}
의 형태로 적으며 n 에는 1 ~ 8 의 16진법 숫자가 들어갈 수 있습니다. (유니코드는 유니코드 에서 다룹니다.)let wiseWords = "\"상상이 지식보다 더 중요하다.\" - 아인슈타인"
// "상상이 지식보다 더 중요하다." - 아인슈타인
let dollarSign = "\u{24}" // $, 유니코드 스칼라 U+0024
let blackHeart = "\u{2665} // 🖤, 유니코드 스칼라 U+2665
let sparklingHeart = "\u{1F496} // 💖, 유니코드 스칼라 U+1F496
이때 """
를 사용한 다중 문자열 리터럴에서 """ 를 표현하고 싶다면 \"\"\"
형태로 표현해야 합니다.
문자열을 호출할 때 따옴표 양 옆에 문자열을 추가함으로써, 탈출 캐릭터를 \
에서 \추가한 캐릭터
형태로 바꿀 수 있습니다.
#"1번째 줄 \n 2번째줄"#
#"1번째 줄 \#n 2번째줄"#
// 출력 : 1번째 줄 \n 2번째줄
// 출력 : 1번째 줄
// 2번째 줄
빈 문자열 값을 생성하거나, 할당하고 싶다면 다음과 같은 방법으로 사용할 수 있습니다.
var emptyString = ""
var anotherEmptyString = String()
// 모두 빈 문자열이자 동일한 값을 지닙니다.
특정 문자열을 수정하거나 추가할 수 있으나, var
문자열에만 적용되고 let
문자열은 수정할 수 없습니다.
var variableString = "말"
variableString += "과 마차"
// variableString 은 이제 "말과 마차" 입니다.
let constantString = "하이랜더"
let constantString += "와 또 다른 하이랜더"
// 컴파일 에러가 납니다.
// 번역 : 하이랜더는 머리가 잘리지만 않으면 불사신인 존재끼리
// 영혼의 맞다이를 떠서 살아남는 사람이 상을 받는다는 영화에서 나온 존재
Swift 의 문자열 자료형은 값 타입입니다. 만약 새로운 문자열 값을 생성하고, 그 문자열이 함수나 메소드 등에 의해서 복사되거나, 변수나 상수에 할당된다면 복사본은 기존의 문자열 자료형에서 생성된, 새로운 문자열입니다. 값 타입은 이곳에서 더 상세하게 기술되어 있습니다.
Swift 의 복사 전제 문자열 행동은 문자열 값을 전달할 때 정확히 똑같은 값이지만, 어디에서 왔는지 확인할 필요 없는 새로운 값을 갖게 됩니다. 따라서 전달하는 문자열은 수정되지 않음을 확신할 수 있게 됩니다.
화면 뒷단에서 Swift 컴파일러는 문자열 사용의 최적화를 진행하며 오로지 필요할 때만 복사하게끔 합니다. 이에따라 개발자들은 언제나 문자열을 값 타입으로써 훌륭한 성능을 얻을 수 있게 됩니다.
근데 백준에서 왜 그렇게 안되는게 많아요
for-in
과 이터레이팅(반복)을 사용하여 각각의 캐릭터 값을 문자열로부터 얻을 수 있습니다.
for character in "Dog!🐶"{
print(character)
}
// D
// o
// g
// !
// 🐶
이와 유사하게, 하나의 값만 담고있는 문자열로부터 캐릭터 변수나 상수를 만들 수도 있습니다.
let exclmationMark: Character = "!"
문자열은 캐릭터 배열을 전달하여 만들 수도 있습니다.
let catCharacters: [Character] = ["고", "양", "이", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// "고양이!🐱" 가 출력된다.
문자열 값은 덧셈 연산자 +
를 활용하여 새로운 문자열 값으로 만들 수 있습니다.
// 번역 : 그런데 String + Character 은 안됩니다.
var string = "조아 햄 버 거"
var character: Character = "!"
print(string + character)
// 오류 - Character 를 String 으로 바꿀 수 없습니다.
// 이를 위해서 string.append() 를 사용할 수 있습니다.
var string = "조아 햄 버 거"
var character: Character = "!"
string.append(character)
print(string)
// 출력 : 조아 햄 버 거!
다중 문자열 리터럴 """
에서 한 줄을 띄우지 않으면 끝난 지점에서 바로 다음 문자열이 붙게 됩니다.
let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// one
// twoThree
let goodStart = """
one
two
"""
print(goodStart + end)
// one
// two
// three
문자열 보간은 문자열 내에 새로운 문자열 값을 포함하는 방법을 의미합니다.
let multiplier = 3
let message = "\(multiplier) 곱하기 2.5 는 \(Double(multiplier) * 2.5)"
// 출력 3 곱하기 2.5 는 7.5
문자열 보간 때 확장 문자열 구분자를 사용하면 확장 문자열 구분자를 포함하여야 합니다.
let multiplier = 3
let message = #"\#(multiplier) 곱하기 2.5 는 \#(Double(multiplier) * 2.5)"#
// 출력 3 곱하기 2.5 는 7.5
유니코드는 국제적인 규격으로 다양한 출력 시스템 내에서 인코딩, 표현과 텍스트 출력을 위해 사용됩니다. 유니코드를 통해 개발자는 외부 - 텍스트 파일이나 웹 페이지 등 - 에서 온 다른 언어의 캐릭터 자료형도 규격화된 양식 안에서 표현할 수 있게 되며, 해당 글자들을 읽고 쓸 수 있게 됩니다.
Swift 의 문자열과 캐릭터는 유니코드 호환 자료형이며 이번 목차에서 설명합니다.
화면 뒷단에서, Swift 의 네이티브 문자열 자료형은 유니코드 스칼라 값을 통해 만들어집니다. 유니코드 스칼라 값은 21 비트 크기의 숫자 혹은 캐릭터, 수정자를 표현하는 고유한 값입니다. 이를테면 U+0061 은 "LATIN SMALL LETTER A" 인 "a" 를 표현하고, U+1F425 는 "서 있는 병아리" 를 의미합니다.
번역 : 앞으로 나오는 유니코드는
한국어 문자뷰어 (한국 맥에서 fn 키를 누르면 나오는 표현) 를 사용합니다.
Swift 내 모든 인스턴스된 캐릭터 자료형들은 하나의 음운을 표현합니다. 확장 음운 단위체는 하나 이상의 유니코드 스칼라들을 합쳐서 만들어진 값이 됩니다.
확장된 음운 단위체들은 복잡한 문자 체계를 하나의 캐릭터 값으로 표현하는데 편리한 기능을 제공합니다. 예를 들어 한국어의 알파벳인 한글은 유니코드의 합쳐진 형태와 분리된 형태로 나누어서 표현할 수 있게 됩니다. Swift 에서는 두 형태 모두 하나의 캐릭터로써 값을 지니게 됩니다.
let precomposed: Character = "\u{D55c}" // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ㅎ, ㅏ, ㄴ
// precompose 는 "한" 이며
// decomposed 역시 "한" 이 된다.
한편 유니코드 스칼라에서 지역 상징 표현은 한 쌍의 캐릭터 값으로 묶어서 해당 국기를 표현할 수 있습니다.
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS == 🇺🇸
문자열 값의 캐릭터 갯수를 세기 위해서 문자열 값의 count 프로퍼티를 사용할 수 있습니다.
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie 은 \(unusualMenagerie.count) 캐릭터를 갖고 있습니다.")
// Prints "unusualMenagerie 은 40 캐릭터를 갖고 있습니다."
이때 Swift 는 확장 음운 단위체를 캐릭터에 사용하므로, 문자열 합치기나 수정이 언제나 문자열 캐릭터 갯수에 영향을 미치지 않습니다.
번역 :
한국어의 경우 "한" 으로 표기하든 "ㅎ","ㅏ","ㄴ" 으로 표기하든
swift 는 1개의 캐릭터로 인식하므로
언제나 문자열 + 문자열이 갯수가 늘어나는 것은 아니라는 것을 말한다.
예시 ) var ha:Character = "\u{1112}\u{1161}" // 하
var n:Character = "\u{11AB}" // ㄴ
var han = String(ha) + String(n)
print("\(han), \(han.count)")
// 출력 한, 1
Note: 확장 음운 단위체는 다중의 유니코드 스칼라와 합쳐질 수 있습니다.같은 캐릭터라도 다른 표기를 한다면 메모리의 양 역시 달라질 수 있음을 의미합니다. 이때문에 Swift 는 각각의 문자열들이 동일한 메모리를 저장하고 있지 않게 됩니다. 이와 마찬가지 이유로 문자열의 캐릭터 수 또한 반복(iterating)을 통해 해당 문자열의 확장 음운 단위들을 확인해야만 알 수 있게 됩니다. 특별하게 긴 문자열 값들을 다룬다면 갯수 프로퍼티는 반드시 반복(iterate) 를 통해서 전체 문자열 내 유니코드 스칼라를 확인할 것이고, 그에 따라 문자열 내 캐릭터의 갯수를 정한다는 것을 주의하세요.
같은 캐릭터를 포함하더라도 count 프로퍼티를 통해 반환된 캐릭터들의 카운트가 언제나 NSString 의 length 와 같은 값을 갖는 것은 아닙니다. NSString 의 length 는 UTF-16 표현에 의한 16비트 코드 유닛의 갯수를 세는 반면 count 는 확장 음운 단위에 준한 값으로 계산하기 때문입니다.
문자열 메소드나 프로퍼티, 서브스크립트 문법을 활용하여 문자열에 접근하거나 수정할 수 있습니다.
각 문자열 값은 String.Index 라는 인덱스와 연결되어 있습니다. String.Index 는 문자열 내 각 캐릭터들의 자리를 표현합니다.
상단에서 언급되엇듯, 다른 캐릭터들은 다른 메모리의 저장을 요구합니다. 이에 따라 캐릭터의 명확한 자리를 확정하고 싶다면 String 의 시작부터 끝까지 각각의 유니코드 스칼라를 반복해서 확인해야 합니다. 이러한 이유로 스위프트는 정수 값의 인덱스를 제공할 수 없습니다.
startIndex 프로퍼티를 사용하여 문자열의 첫번째 캐릭터에 접근할 수 있습니다. 한편 endIndex 프로퍼티를 사용하여 문자열의 마지막 캐릭터 다음의 값에 접근할 수 있습니다. 이에 따라 endIndex 프로퍼티는 문자열 내 subscript 의 매개변수로 유요한 값을 전달하지 못합니다. 만약 문자열이 비었다면 startIndex 와 endIndex 는 같습니다.
subscript 문법을 활용하여 문자열의 특정 인덱스에 접근할 수 있습니다.
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
indices 프로퍼티를 활용하여 문자열 내 각각의 캐릭터에 접근할 수 있습니다.
for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n T a g ! "
단일 캐릭터를 문자열의 특정 위치에 삽입하고 싶다면 inser(_:at:) 을 사용할 수 있습니다.
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"
subscript 나 prefix(_:) 와 같은 메소드를 사용하여 부분 문자열 =을 얻으면, 이 인스턴스된 결과는 문자열 이 아닌 부분 문자열이 됩니다. 부분 문자열 는 Swift 의 문자열과 유사한 자료형으로, 문자열과 같이 사용할 수 있습니다. 다만 문자열과 다르게 짧은 작업을 위해서 사용할 것을 권장합니다. 만약 결과값을 저장하여 긴 시간 동안 사용하고 싶다면 부분 문자열 을 문자열 인스턴스로 바꿔서 저장할 것을 권장합니다.
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)
부분 문자열과 문자열의 차이는, 최적화를 위해서 부분 문자열은 원본 문자열의 메모리를 재사용한다는 것 입니다. (문자열 역시 비슷한 최적화를 갖고 있으나, 만약 두 문자열이 같은 메모리를 공유한다면 이는 같은 것으로 간주됩니다.) 이 최적화는 실제로 문자열이나 부분 문자열의 값을 수정하기 전까지는 메모리를 복사하는 비용을 지불하지 않도록 도와줍니다. 그렇기에 부분 문자열을 장 기간 사용하는 것을 지향하게 됩니다. 부분 문자열은 문자열의 저장 공간을 재사용하기 때문에, 전체 문자열이 부분 문자열을 위해 계속해서 메모리상에 존재해야하기 때문입니다.
상단의 예시에서 greeting
은 문자열로, 실제 메모리 상에서 저장되어 있는 값입니다. 한편 beginning
은 greeting
의 부분 문자열이기 때문에 beginning
은 greeting
의 저장 공산을 재 사용하게 됩니다. 이에 반해 newString
은 새로운 문자열을 인스턴스하며 메모리상에 적재되었습니다. 이를 다음과 같이 도식화할 수 있습니다.
Note: 문자열과 부분 문자열은 모두 StringProtocol 프로토콜을 따릅니다. 이는 문자열과 관련된 메소드나 함수를 사용할 때 StringProtocol 값을 받아들인다는 뜻 입니다. 그렇기에 개발자는 문자열이나 부분 문자열에 StringProtocl 내 함수들을 사용할 수 있습니다.
Swift 는 동일, 접두사 동일, 접미사 동일 3개의 텍스트 비교 값을 제공합니다.
두 문자열 비교는 ==
혹은 !=
로 확인할 수 있습니다. 이때, 두 문자열 사이에 확장 음운 단위가 있다면 이는 사람이 일반적으로 받아들이는 언어적 의미에서 동일한지에 대해서 비교합니다.
즉 Unicode 를 활용한 확장 음운이 존재하면,
두 문자열이 "언어적"으로 같은 지 확인한다는 뜻입니다.
예를 들어 유니코드로 적은 "한" 과 "ㅎ","ㅏ","ㄴ" 은
동일한 문자열이 되지만
라틴알파벳 "à(`a)" 와 러시아 알파벳 "à(`a)" 는 다르게 인식된다는 뜻입니다.
두 문자열이 특정한 접두사나 접미사를 가지고 있는지 확인하려면 string prefix 혹은 suffix 를 사용할 수 있습니다.
메소드는 string.hasPrefix(_:)
,string.hasSuffix(_:)
으로, 두 메소드 다 문자열 매개변수를 받고 Boolean 값을 리턴합니다.
다음과 같은 예시에서 사용할 수 있습니다.
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"
]
위와 같은 본문이 있을 때, prefix 를 활용하여 접두사를 확인할 수 있습니다.
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"
한편 suffix 를 활용하여 접미사도 확인할 수 있습니다.
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"
prefix 와 suffix 역시 확장 음운 단위가 있다면, 문자열과 캐릭터 비교 에서 처럼 언어의 의미적 단위에서 비교하게 됩니다.
유니코드 문자열을 텍스트 파일에 작성하거나 저장할 때, 문자열 내 유니코드 스칼라들은 유니코드에서 사전 정의된 여러 인코딩 양식 중 하나를 따라 인코딩됩니다. 각 양식은 코드 유닛이라 부르는 작은 덩어리들로 문자열을 인코딩합니다.
양식에는 UTF-8 양식 (문자열을 8 비트 코드 유닛으로 인코딩), UTF-16 양식(문자열을 16 비트 코드 유닛으로 인코딩), UTF-32 양식(문자열을 32 비트 코드 유닛으로 인코딩) 이 있습니다.
Swift 는 문자열의 유니코드 표현법 접근을 위해 여러 방법을 제공합니다. for-in
구문을 활용해서 반복(iterating) 할 수 있으며, 유니코드 확장 음운 단위로서 각 캐릭터 값에 접근할 수 있습니다. 이 프로세스는 상단의 Working with Characters 에서 설명되었습니다.
대체적으로 문자열 값은 3개의 유니코드 컴파일 표현법으로 접근할 수 있습니다.
각 표현법은 같은 let dogString = "Dog!!"🐶
도 다르게 표현됩니다.
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 226 128 188 240 159 144 182 "
이때 "D", "o", "g"
는 ASCII
코드와 동일한 값입니다.
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "
마찬가지로 "D", "o", "g"
는 ASCII
코드와 동일한 값입니다.
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "
이때 참고로, 다음과 같은 표현으로 각 scalr 에 접근할 수 있습니다.
for scalar in dogString.unicodeScalars {
print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶