본문 바로가기
FRONT-END/iOS

Swift 문자열과 문자(String and Character)

by 랄라J 2023. 8. 28.

아스키코드와 유니코드

둘 다 문자와 문자열을 숫자로 저장하기 위한 체계이다. 아스키코드는 7bit를 사용해 0부터 127까지 매칭되는 것을 정의해 놓고 사용하는 방식이었다. 단, 아스키코드는 영어만 표현이 가능했다. 그 한계를 깨기 위해 유니코드는 한글 등 어떠한 언어 및 기호, 이모지까지의 문자도 표현할 수 있도록 매칭해 놓아 전 세계에서 통일된 방식이다. 영문자 부분은 아스키코드를 포함한다. 인코딩 방법은 8, 16, 32 Bits를 지원한다. UTF-8은 1~4 bytes 가변길이 인코딩으로 웹과 대부분 체계에서 주로 사용된다.

모든 문자열은 개별 인코딩된 유니코드 문자들로 구성된다. Swift에서는 문자열을 저장할 때, 하나의 문자를 유니코드의 스칼라값으로 저장한다. 그리고 언제든지 UTF-8, UTF-16 방식으로 쉽게 변환할 수 있는 방법도 제공해 준다. 유니코드가 다르기 때문에 Swift에서는 대문자와 소문자를 다른 문자로 인식한다.

var someString: String = "Some Swift"

someString.unicodeScalars
someString.utf8
someString.utf16

// 하나씩 출력해보기
for code in someString.unicodeScalars {
    print(code.value) // 83 111 109 101 32 83 119 105 102 116
}

// 유니코드 형태로 입력하는 방법
print("\u{53}\u{6F}") // So

 

단, Swift의 문자열에서는 하나의 문자를 유니코드의 스칼라값으로 저장하기 때문에 배열과 같은 단순 인덱스 접근이 불가능하다. 

var test1 = "한"
print(test1.count) // 1

var test2 = "\u{1112}\u{1161}\u{11AB}" // ㅎ + ㅏ + ㄴ
print(test2.count) // 1

 

Swift 문자열은 String과 NSString 2개의 문자열 자료형을 사용한다. 

NSString 타입을 String으로도 쉽게 타입 변환이 가능하다. 이렇게 양 방향으로 타입캐스팅으로 호환되는 자료형을 브릿징이 가능한 타입을 Toll-Free Bridged라고 한다.

NSString은 length로 글자수를 판별했고, String은 count로 글자수를 판단한다는 점에서 차이가 있다. 또한 유니코드를 처리하는 방식이 다른데, NSString은 UTF-16 기반으로 처리하고, String은 의미 글자 수 기반으로 처리한다는 점이 다르다.

이외 별개로 스위프트 문자열에서 온전히 구현할 수 없는 기능이 있어 NSAttributedString 타입을 사용하곤 한다. 예를 들면 약관 확인 글자에 링크를 연결해야 하는 경우 등에 사용된다.

 

문자열 여러 줄 입력하기

문자열을 여러 줄 입력하고 싶을 때는 첫째줄과 마지막줄에 """를 입력하면 된다. 단, 해당 줄에는 문자열 입력 불가하고, 문자열 내부에서는 쓰인 대로 줄 바꿈이 됨, 줄 바꿈 하지 않으려면 \ 입력하면 된다. 특수문자는 문자 그대로 입력된다. 

Raw String, # 확장 구분자 (Extended String Delimiters)를 활용하는 방법도 있다. # 기호로 문자열 앞 뒤를 감싸면 내부의 문자열을 글자 그대로 인식한다. 특수 문자를 편하게 입력할 수 있어 사용한다. # 개수는 개발자 마음대로 사용하면 된다.

 

문자열 보간법(String Interpolation)

문자열 내 \()로 상수, 변수, 리터럴값, 표현식 값을 표현할 수 있다.

애플이 미리 만들어 놓은 프로토콜인 아래 CustomStringConvertible을 채택해 구현하면 스트링 인터폴레이션 직접 구현이 가능하다.

// Swift 5 이전
protocol CustomStringConvertible {
    var description { get }
}

struct Dog {
    var name: String
}

let dog = Dog(name: "뽀삐")

extension Dog: CustomStringConvertible {
    var description: String {
        return "강아지의 이름은 \(name)입니다."
    }
}

print(dog) // 강아지의 이름은 뽀삐입니다.
// Swift 5 이후

struct Point {
    let x: Int
    let y: Int
}

let p = Point(x: 5, y: 7)
print("\(p)")

extention String.StringInterpolation {
	mutating func appendInterpolation(_ value: Point) {
        appendInterpolation("X좌표는 \(value.x), Y좌표는 \(value.y)입니다.")
    }
}

 

숫자를 문자열로 변환 출력하기

1. 출력 형식 지정자(Format Specifiers) 활용

// String(format: String, arguments: CVarArg...)

// %f 실수
// %d, %D 정수
// %@ 문자열

var pi = 3.1415926
String(format: "%.3f", pi) // 3.142
String(format: "%.2f", pi) // 3.14
String(format: "%.1f", pi) // 3.1
String(format: "%07.3f", pi) // 003.142 7자리로 표현하되 0과 .포함 소수점 아래 3자리

String(format: "%.d", 7) // 7
String(format: "%.2d", 7) //  7 두자리로 표현
String(format: "%.02d", 7) // 07 두자리로 표현하되, 0포함

String(format: "Hello, %@", "Mike") // Hello, Mike

 

2. 형식 지정자 활용

struct Point: Codable {
    var x: Double
    var y: Double
}

extension Point: CustomStringConvertible {
    var description: String {
        let formattedValue = String(format: "%1$.2f , %2$.2f", x, y)
        return "\(formattedValue)"
    }
}

let p = Point(x: 3.14159, y: 2.59637)
print("\(p)") // 3.14 , 2.60

 

활용 예시

var firstName = "Gildong"
var lastName = "Hong"

var korean = "사용자의 이름은 %2$@ $1$@ 입니다."
var english = "The username is $1$@ %2$@"

string = String(format: korean, firstName, lastName)
string = String(format: english, firstName, lastName)

 

3. NumberFormatter 클래스 활용

숫자 <-> 문자 변환을 다루는 클래스

.roundingMode				반올림모드
.maximumSignificantDigits		최대자릿수
.minimumSignificantDigits		최소자릿수
.numberStyle				숫자스타일
// 소수점 버리기
let numberFormatter = NumberFormatter()
numberFormatter.roundingMode = .floor		// 버림으로 지정
numberFormatter.maximumSignificantDigits = 3	// 최대 표현하기 원하는 자릿수

let value = 3.1415926
var valueFormatted = numberFormatter.string(for: value)!
print(valueFormatted) // 3.14


// 세자리수마다 콤마 넣기
numberFormatter.numberStyle = .decimal
let price = 10000000
let result = numberFormatter.string(for: price)!
print(result) // 10,000,000

 

서브스트링

var text = "Hello wolrd"

text.prefix(3) // Hel
text.suffix(3) // lrd

String.Subsequence이라는 타입을 종종 볼 수 있는데 이는 서브스트링이다. 서브스트링은 문자열의 메모리 공간을 공유한다. 스위프트 내부적으로 최적화되어 있다. 수정 등이 일어나기 전까지는 메모리를 공유한다.

다시 설명하자면, Hello world가 담긴 text 문자열에서 prefix와 같은 메서드를 사용해 앞에 3글자만 얻어올 때 이미 있는 text 문자열의 메모리 공간을 공유하는 것이다.

 

문자열 다루기

문자열 <-> 배열 변환하기

var someString = "Swift"

// 문자열을 문자(Character) 배열화하기
var array1 = Array(someString)

// 문자열을 문자열(String) 배열화하기
var array2 = someString.map{ String($0) }
var array2 = Array(arrayLiteral: someString)

// 문자 배열(Character)을 문자열로 변환하기
String(array1)

// 문자열 배열(String)을 문자열로 변환하기
array2.joined()

 

문자열 뒤죽박죽 섞기

var someString = "Swift"

someString.randomElement() // 문자열에서 랜덤으로 하나 뽑아내는 것
someString.shuffled()	// 섞어서 문자(Character) 배열로 리턴

String(someString.suffled())

someString.map{ String($0) }.suffled().joined()

 

문자열 대소문자 변형하기

var str = "Swift"

string.lowercased() // 전체 소문자로 바꾼 문자열 리턴
string.uppercased() // 전체 대문자로 바꾼 문자열 리턴
string.capitalized  // 대문자로 시작하는 글자로 리턴하는 속성

 

문자열 비어있는지 판단하기

var str = ""
str.count // 0
srt.isEmpty // true

str = " "
str.count // 1
str.isEmpty // false

str == nil // false

 

문자열 포함여부 판단

var str = "Swift"
str.contains("S") // true

 

String의 인덱스 타입

문자열도 Collection 프로토콜을 따르고 있다. 문자열은 문자 + 이모티콘 등 한 문자열 내에서도 서로 다른 메모리 크기를 사용할 수 있기 때문에 배열과 달리 인덱스가 정수가 아니라 배열처럼 바로 정수로 접근할 수 없다.

let greeting = "Guten Tag!"

greeting[greeting.startIndex] // G
greeting[greeting.endIndex] // !

greeting[greeting.index(greeting.startIndex, offsetBy: 2)] // t

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

greeting.firstIndex(of: "G") // 0 - 특정 문자 index 뽑아내기

// 개별 문자의 인덱스에 접근하기
for index in greeting.indices {
    print("\(greeting[index])", terminator: "")
}

for char in greeting {
    print("\(char) ", terminator: "")
}

// 범위 제한
var someIndex = greeting.index(greeting.startIndex, offsetBy: 7)

if greeting.startIndex <= someIndex && someIndex < greeting.endIndex {
    // 벗어나지 않는 경우 코드 실행
}

// 범위 
var range = greeting.range(of: "Tag!")!
greeting[range] // Tag!

greeting.range(of: "Tag!", options: [.caseInsensitive])
// caseInsensitive 대소문자 상관없이

// 거리
greeting.distance(from: greeting.startIndex, to: greeting.endIndex)

 

문자열 삽입하기

var str = "Hello"

str.insert("W", at: str.index(after: str.endIndex))
str.insert(contentOf: "World", at: str.index(after: str.endIndex))

 

문자열 교체하기

var welcome = "Hello there!"

if let range = welcome.range(of: " there") {
    welcome.replaceSubrange(range, with: "Swift!")
}

// 교체하되 원본 변환 없음
var newWelcome = welcom.replacingOccurrences(of: "Swift", with: "World")
// 참고, options를 전달하는 부분도 있음

 

문자열 추가하기

var str = "hi"

str + "mike"
str.append("!")

 

문자열 삭제하기

welcome = "Hello Swift"

welcome.remove(at: welcome.index(before: welcome.endIndex))

// 범위를 파악하고 지우기
welcome.removeSubrange(범위)

// 문자열의 앞 또는 뒤에서 문자 갯수 만큼을 삭제
welcome.removeFirst(갯수)
welcome.removeLast(갯수)

// 전체지우기
welcome.removeAll()
welcome.removeAll(keepingCapacity: true) // 메모리는 남겨두겠다는 옵션

 

문자열 비교하기

// 1. 비교연산자 (대소문자 구별)
"swift" != "Swift" // true
"swift".locercased() == "Swift".locercased() // true

// 2. 대소문자 무시하고 비교하는 메서드
// caseInsensitiveCompare
// 결과값 타입은 NSComparisonResult
// 1) .orderedSame : 동일
// 2) .orderedAscending : 오름차순
// 3) .orderedDesending : 내림차순

var a = "Swift"
var b = "swift"

a.caseInsensitiveCompare(b) // NSComparisonResult
a.caseInsensitiveCompare(b) == ComparisonResult.orderedSame // true

// 3. 문자열 비교 메서드
a.compare("swift", options: [.caseInsensitive]) == .orderedSame
// .caseInsensitive : 대소문자 무시
// .diacriticInsensitive: 발음구별기호 무시
// .widthInsensitive : 글자 넓이 무시
// .forcedOrdering: 강제적 오름차순/내림차순 정렬순
// .literal : 유니코드 자체 글자 그대로
// .numeric : 숫자 전체를 인식해서 비교
// .anchored : (앞부분부터) 고정시키고 (접두어)
// .backwards : 문자 뒷자리부터
// .regualrExpression : 정규식 검증

// 크기 비교하기 (유니코드 비교)
"swift" > "Swift" // true 글자 순서대로 유니코드 순서를 비교

var string = "Hello world"

// 전체 문자열에서 일부 문자열 포함 여부
string.contains("Hello")

// 접두어/접미어 포함여부
string.hasPrefix("Hello")
string.hasSuffix("world")

// 접두어/접미어 반환
string.prefix(2)
string.suffix(2)

// 공통 접두어 반환
string.commonPrefix(with: "Hello Swift")

// 앞/뒤를 drop시킨 나머지 반환
string.dropFirst(2)
string.dropLast(2)

 

 

정규식

특정한 규칙의 문자열을 판별하기 위한 방법

let phone = "010-1234-1234"

var phoneNumRegex = #"[0-9]{3}\-[0-9]{4}\-[0-9]{4}"#

if let _ = number.range(of: phoneNumRegex, options: [.regularExpression]) {
	print("유효한 전화번호")
}

 

특정 문자의 검색 및 제거

// 공백 제거
var userName = " Rarla "
var trimmedString = userEmail.trimmingCharacters(in: [" "])
var trimmedString = userEmail.trimmingCharacters(in: .whitespaces)

// 중간에 있는 공백 또는 특수문자 제거
var name = " R a r l a "
var removedName = name.components(separatedBy: " ").joined()

var phone = "010-1234-1234"
var newPhone = phone.components(separatedBy: "-").joined()

var numString = "1+2=3*4/5"
var removedNumString = numString.components(separatedBy: ["+", "-", "=", "*", "/"]).joined()

// components와 유사하지만 split으로도 구현 가능, split은 서브스트링으로 리턴함
var str = "Hello Swift"
var newStr = str.split(separator: " ").joined

// 특정 문자열 검색 활용
name = "hi!world"

if let range = name.rangeOfCharacter(from: .symbols) {
    print(name[range])
}

 

 

[참고]

반응형

댓글