예를 들어 Python에서 문자열에서 1번 문자를 출력하려면 다음과 같다.
s = "hello"
print(s[1]) # e
Python 뿐만 아니라 C언어에서도 비슷한 형식으로 사용한다.
하지만 Swift에서 위와 같은 방식으로 접근한다면..
let s: String = "hello"
print(s[1]) //에러
에러가 발생한다.
Swift에서는 0, 1, 2..와 같은 Int
타입 숫자로 문자열의 각 문자 요소에 접근할 수 없다.
Apple 공식 문서에 소개 되어 있기를 Swift에서 String
타입은 Character
타입의 집합이고 Character
타입은 UnicodeScalar
값의 집합이다.
UnicodeScalar
값은 Unicode에서 사용하는 21-bit의 정수 값으로 문자 하나를 나타내는데 사용하는 고유 값을 말한다.
ex) "A" == U+0041, "😀" == U+1F600
그 결과 아래와 같은 상황이 발생한다.
let A: String = "A"
print("string length: \(A.utf8.count)") //string length: 1
print("string length: \(A.unicodeScalars.count)") //string length: 1
let cat: String = "🐱"
print("string length: \(cat.utf8.count)") //string length: 4
print("string length: \(cat.unicodeScalars.count)") //string length: 1
let firstName: String = "김"
print("string length: \(firstName.utf8.count)") //string length: 3
print("string length: \(firstName.unicodeScalars.count)") //string length: 1
이렇게 영어로 되어 있는 문자열은 상관 없지만 문자열에 영어가 아닌 이모티콘이나 한글 등이 포함된 경우 일반적으로 많이 쓰이는 utf-8
로 인코딩 하면 문자의 크기가 유동적이다.
그럼 "UnicodeScalar
를 기준으로 문자열에 접근하면 되지 않을까?" 라고 할 수 있다.
let family: String = "👨👩👧👧"
print("string length: \(family.utf8.count)") //string length: 25
print("string length: \(family.unicodeScalars.count)") //string length: 7
이모티콘에 따라서도 문자의 크기는 1이지만 UnicodeScalar
가 무조건 1이 아닌 경우도 존재한다.
결론은 UnicodeScalar
자체이던 UTF-8
형식으로 인코딩하던 혹은 UTF-16
형식으로 인코딩 하던 어떤 포맷이더라도 문자의 크기가 가변적이기 때문에 문자의 위치를 가져오기 위한 어떤 포맷을 한 기준으로 사용할 수 없다는 것이다.
대신 대안으로 String.Index
타입을 인자로 받는 subscript
가 존재한다.
해당 subscript
의 선언은 다음과 같다.
extension String : BidirectionalCollection {
...
public subscript(i: String.Index) -> Character { get }
String.Index
타입을 매개변수로 전달 받아 Character
를 반환한다.
그렇다면 위의 예시를 다시 이렇게 접근할 수 있다.
let s: String = "hello"
let i: String.Index = //String.Index 타입 값
print(s[i])
이렇게 하면 i번째 위치에 있는 문자를 subscript
를 통해 반환 받을 수 있다.
그렇다면 String.Index
타입의 값은 어떻게 얻을 수 있을까?
이것을 얻기 위한 String
타입의 여러 메서드가 존재한다. 그 중에서 몇가지만 알아보려고 한다.
해당 메서드는 문자열의 첫번째 위치의 String.Index
를 반환한다.
let s: String = "hello"
let i: String.Index = s.startIndex
print(s[i]) //h
해당 메서드는 매개변수로 전달된 특정 위치의 바로 앞 위치를 반환한다.
let s: String = "hello"
<let b: String.Index = s.index(before: k)
print(s[b]) //l
해당 메서드는 매개변수로 전달된 특정 위치의 바로 뒤 위치를 반환한다.
let s: String = "hello"
let a: String.Index = s.index(after: s.startIndex)
print(s[a]) //e
해당메서드는 첫번째 매개변수로 특정 위치를 전달하면 해당 값으로 부터 offsetBy
만큼 위치에 있는 위치를 반환한다.
주로 첫번째 매개변수에는 0번 위치인 startIndex
의 반환 값을 전달한다.
let s: String = "hello"
let i: String.Index = s.index(s.startIndex, offsetBy: 0)
print(s[i]) //h
let j: String.Index = s.index(s.startIndex, offsetBy: 4)
print(s[j]) //o
특정 문자의 첫번째 위치를 알고 싶다면 firstIndex(of:)
를 사용할 수 있다.
주의할 점은 firstIndex(of:)
는 존재하지 않는 문자가 전달인자로 전달되는 것을 방지하기 위해 반환 값이 Optional이므로 Optional에 대한 처리를 해줘야 한다.
let str: String = "hello"
if let i = str.firstIndex(of: "h") {
print(str[i]) //h
}
endIndex
는 문자열의 마지막 문자의 위치보다 +1 큰 위치를 반환한다.
let str: String = "hello"
let b: String.Index = str.index(before: str.endIndex)
print(str[b]) //o
마찬가지로 일반적인 Int
타입 숫자로는 문자열 슬라이싱도 불가능하다.
let s: String = "hello"
print(s[1...3]) //에러
하지만 위에서 본 String.Index
타입으로 슬라이싱이 가능하다.
let s: String = "hello"
let i: String.Index = s.startIndex
let a: String.Index = s.index(after: s.startIndex)
print(s[i...a]) //he
문자열의 문자를 하나씩 순회하는 방식은 for-in
구분으로 배열을 순회하는 방식과 동일한 방법으로 접근할 수 있다.
let str: String = "hello"
for s in str {
print(s)
}
//h
//e
//l
//l
//o