swift를 사용하다보면 string을 사용할 일이 정말 많다.
Swift에서 readLine()으로 문자열을 입력받으면 옵셔널String타입으로 데이터를 받고,
SwiftUI에서 textfiled로 데이터를 받아와도 옵셔널String으로 받는등 입력을 받는경우에는 string이 많이 사용된다.
이런 중요한 string 이번에 한번 제대로 알아보자
(그리고 본 게시글에서 String과 문자열은 동일한 의미로 사용되니 유의할것)
var greeting:String = "hello, world!"
다음과 같이 ""쌍 따옴표 안에 저장하고 싶은 문자열을 입력한다.
만약 문자열 사이에 상황에 따라 다른 데이터를 넣고 싶다면 쌍따옴표 안에 ()를 쓰고 안에 데이터를 넣으면 된다.
var name = "gildong"
var greeting:String = "hello, \(name)!"
+나 +=을 사용한다.
var str1 = "hello"
var str2 = " world!"
이렇게 두개의 string이 있을때 결합은 다음과 같다.
str1 += " world!"
//or
str1 += str2
//or
var str1 = "hello" + " world!"//처음부터 이렇게 선언
let banner = """
__,
( o /) _/_
`. , , , , // /
(___)(_(_/_(_ //_ (__
/)
(/
"""
""" 3개의 쌍따옴표를 사용해서 작성하면 된다.
(swift 5부터 사용가능하다.)
""쌍따옴표 안에 문자를 작성할때 \같은 문자는 사용할 수 없다, \가 문자열의 종료나 ()같은 곳에 사용되면서 일종의 예약어 처럼 작동하고 있기 때문이다 이 불편함을 해소하기 위해서 #을 이용해서 Raw String이라는 것을 사용가능하다.
// 일반 문자열에서는 백슬래시와 따옴표를 이스케이프해야 함
let regularString = "This is a \"normal\" string with escape sequences like \\n."
// Raw String에서는 이스케이프 없이 그대로 작성 가능
let rawString = #"""
This is a "raw" string with no need for escape sequences like \n.
"""#
This is a "raw" string with no need for escape sequences like \n
사용방법은 단순하다 ""쌍따옴표 맨앞과 맨뒤에 #을 붙여준다.
여기서 이전처럼 ()같이 \을 이용하고 싶으면 #을 붙여 주면 된다.
let rstr1 = #"raw string with escape line: \#n"#
let rstr2 = #"This is rstr1: "\#(rstr1)!" "#
raw string with escape line:
This is rstr1: raw string with escape line:
이렇게 /바로 뒤에 #을 붙여 사용한다.
그리고 여기서 #의 개수는 임의의 개수를 사용하면 된다.
하지만 #을 사용할때 개수가 동일해야 한다.
String은 일종의 Character타입의 배열이라고 봐도 무방하다
var greeting:String = "hello"
문자열 greeting은 h, e, l, l, o 이렇게 5개의 문자가 합쳐진 배열처럼 취급이 가능하다.
완전히 배열과 동일하지는 않지만 이렇게 데이터를 처리하도록 도와주는 기능들이 여럿 존재한다.
var greeting = "hello world"
print(greeting.count)
5
다음과 같이 count를 사용하면 문자열의 길이를 반환한다.
let name = "Marie Curie"
let firstSpace = name.firstIndex(of: " ") ?? name.endIndex
let firstName = name[..<firstSpace]
name.firstIndex(of: " ")는
Index(_rawBits: 262407)?
이런 옵셔널 타입의 인덱스 값을 반환한다.
그래서 뒤에 ?? 를 이용해서 옵셔널 처리를 해준것이다.
이를 이용해서 string을 서브스크립트[ ]를 사용해서 내부 데이터를 접근 할 수 있다.
그러나 .firstIndex(of: " ")에서 " "문자열을 나누는 조건인데 여기에는 ""이 불가능하다.
즉 hello라는 문자열의 문자 하나하나씩(h,e,l,l,o) 나누는 것이 불가능하고 일련의 단위로 분리된 문자열로만 추출이 가능하다는 것이다.
문자단위로 추출하고 싶다면 배열로 만들어서 처리해야 한다.
var name = "gil dong"
var names = Array<Character>(name)
print(type(of: names), names)
Array<Character> ["g", "i", "l", " ", "d", "o", "n", "g"]
우선 가장 간단한 방법은 Array()생성자를 이용하는 방법이다. 타입지정은 <>각 괄호를 이용해 지정하면 되고 ()괄호안에 배열로 바꾸고 싶은 문자열을 넣으면 된다.
var name = "gil dong"
var names = name.split(separator: "")
print(names)
["g", "i", "l", " ", "d", "o", "n", "g"]
다음과 같이 .split(separator: "")를 사용하면 문자 추출이 가능하다. (separator:) 옵션에서 다양한 분할 조건 삽입 가능
그러나 이 names배열이 Character타입이면 좋겠으나 SubString 타입이라는 것이다.
var names1:[String] = names //error
var names1:[Character] = names
SubString타입은 쉽게 말하면 Swift에서 자른 메모리 낭비 적게 다루기 위한 타입이라고 생각하면 되는데, 이대로는 사용이 안되기 때문에 string이나 Character타입으로 사용하기 위해선 형변환이 필요하다.
for문 같은 반복문을 이용해서 원소 하나하나씩 형변환해줘도 되지만 배열의 .map{ }을 사용하면 한줄에 바로 변환이 가능하다.
var names1:[String] = names.map{ String($0) }
//String타입이 아닌 배열로 변환하는 경우 한번 더 형변환 필요
var names2:[Character] = names.map{ Character(String($0))! }
(여기서 $0의 형태가 생소하다면 클로저를 다시 공부하자)
여기서 Character(), String()은 각 타입의 생성자를 이용해서 형변환 해준것이다.
swift에서는 문자를 유니코드로 다룬다.
(기본값은 UTF-32, = String.unicodeScalars)
그래서 swift로 코딩을 할땐 한글 문자열 저장은 물론이고 변수의 이름도 한글로 하는 것이 가능하다. 또한 유니코드 이모지 🌍 를 글자처럼 사용하는것도 가능하다.
var 인삿말 = "안녕하세요"
print(인삿말)
안녕하세요
let text = "Hello, 🌍!"
for scalar in text.unicodeScalars {
print("\(scalar.value) ", terminator: " ")
}
72 101 108 108 111 44 32 127757 33
.unicodeScalars를 붙여주면 앞서 설명했던 utf-32값으로 나타내 준다.
let text = "Hello, 🌍!"
for scalar in text.utf8 {
print("\(scalar) ", terminator: " ")
}
72 101 108 108 111 44 32 240 159 140 141 33
let text = "Hello, 🌍!"
for scalar in text.utf16 {
print("\(scalar) ", terminator: " ")
}
72 101 108 108 111 44 32 55356 57101 33
이처럼 같은 문자열이라 하더라도 유니코드의 형태가 달라지면 값이 달라지므로 주의할 것
Swift는 C계열인 Objective-C를 뒤를 잇는 애플의 언어라서 그런지 아스키코드로 변환하는 기능을 지원한다.
let text = "Hello, World!"
for character in text {
if let ascii = character.asciiValue {
print("\(character)의 ASCII 값: \(ascii)")
} else {
print("\(character)는 ASCII 범위에 없습니다.")
}
}
H의 ASCII 값: 72
e의 ASCII 값: 101
l의 ASCII 값: 108
l의 ASCII 값: 108
o의 ASCII 값: 111
,의 ASCII 값: 44
의 ASCII 값: 32
W의 ASCII 값: 87
o의 ASCII 값: 111
r의 ASCII 값: 114
l의 ASCII 값: 108
d의 ASCII 값: 100
!의 ASCII 값: 33
다만 이 경우 유니코드를 사용하는 swift의 문자열중에 ascii를 지원하지 않는 문자의 경우 변환이 불가능 하기 때문에 반환타입은 옵셔널이 된다.
(아스키코드로 변환이 불가능한 것은 nil을 반환한다.)
마지막으로 swift의 String은 다른언어의 String과는 다를 수 있다는 것을 설명하고 싶다.
예를 들어 java의 경우 string은 불변이고 인스턴스타입이기 때문에 아래 와 같은 경우 새로운 인스턴스가 새로 생긴다.
String hello = "hello";
hello += "world";
겉으로 보기에는 hello변수 하나만 있는것처럼 보이지만 실제로는 "world"라는 문자열을 추가 하는 과정에서 새로운 인스턴스가 생성되고 hello가 "world"인스턴스를 가리키는 형태로 만들어 진다. 이러한 구조는 많은 문자열 추가가 일어나게 되면 성능에 영향을 미칠수 있다. 그래서 java에서는 StringBuilder를 사용하기도 한다.
하지만 swift의 경우 string은 값타입이다.
그래서 string의 인스턴스 생성은 초기에 생성될때 한번만 생성된다, 문자열의 추가가 일어나더라도 무조건 새로운 인스턴스를 만들지 않는다.
(String을 Character타입의 배열이라고 생각하면 된다.)
var str1 = "Hello"
str1 += " World"
위 코드 처럼 동일한 상황이 일어난다면 swift는 새로운 인스턴스를 만들어서 가리키는게 아닌 기존의 str1의 메모리 공간을 확장해서 데이터를 삽입한다.
그래서 java에서 StringBuilder같은 존재를 swift에서는 따로 사용할 필요가 없다.