2014년 6월 10일 화요일

Swift - 문자열(String)

문자열은 일반적으로 문자(Character)가 2개 이상 모여서 이루어진 사람이 인식하기 위한 글자를 모두 표현하는 String 타입을 의미한다. 그냥 대충 더블쿼터로 묶인 "블라 블라" 이런거다. -_-;; (참고로 이 글은 Swift 4 기준으로 수정되었다)

문자열의 정의는 위에서 이야기 한 것 처럼 문자열로 표시하기만 하면 알아서 타입이 String으로 만들어진다.
let someString = "This is string."
var otherString = "Mutable String"
물론 String 클래스를 이용해 생성하는 것도 가능하다.
let someString2 = String("This is string 2.")
빈 문자열도 생각나는 대로 적으면 되긴 된다.
var emptyString1 = ""
var emptyString2 = String()
var emptyString3 = String("")
String 클래스에서 더하기 연산자(+)는 문자열을 연결시키는 용도로 사용 할 수 있다. 마치 Python 처럼...
var concatString = "LEFT " + "RIGHT"    // "LEFT RIGHT"
concatString += " END"                  // "LEFT RIGHT END"
이 '+' 연산자를 이용하면 NSString 을 이용 할 때의 지옥이 사라진다. 환상적이다.

문자열의 길이

다짜고짜 예제부터 보자.
"SOME STRING".characters.count
위 코드는 10 이라는 숫자를 돌려준다. 그런데 이 코드는 아래처럼 더 축약이 가능하다.
"SOME STRING".count
둘 다 같다고 생각하자. 이름만 봐도 대충 유추가 가능한 이 프로퍼티는 유니코드 기준 문자의 갯수를 알려준다.

바이트 크기를 알고 싶을 때를 위해 lengthOfBytes(using:) 메소드를 제공한다. 이름에서 보듯이 특정 인코딩을 이용해 길이를 알려주는 메소드다.
var s = "test"
s.lengthOfBytes(using: .utf8)  // 4
UTF-8인코딩을 기준으로 영문자의 경우 문자의 갯수를 잘 알 수 있다. 하지만 한글 등 멀티바이트 문자를 사용하게 되면 오해(?)를 불러오는 결과를 초래한다.
s = "한글"
s.lengthOfBytes(using: .utf8)  // 6
s.lengthOfBytes(using: .utf16) // 4
메소드 이름이 '바이트의 길이' 라는 점에 유의해야 한다. 그래서 유니코드의 경우 인코딩에 따라 다른 결과를 돌려준다. 따라서 한국인인 우리가 원하는 결과가 아니다라는 것을 알아둬야 한다. 한글 등 현대적인 유니코드 기반의 길이를 원한다면 앞서 살펴본 .count 프로퍼티를 이용하자.

이번에는 문자열의 구성 단위인 Character를 이용해 문자열을 만들어 보자
var hc1 = Character("한")
var hc2 = Character("글")
s = hc1 + hc2               // "한글"
이번에는 시험삼아 문자열 이터레이션도 해보자.
for ch in "새로운한글" {
    println(ch)
}
위의 결과는 콘솔에 새/로/운/한/글 각 한 글자를 한 라인씩 출력한다. 여기서 ch의 타입은 Character 이다. String이 Character의 모임이라는 것을 알 수 있다. 제일 처음 문자 갯수를 세는 예제에서 .characters 라는 프로퍼티를 참고하는 것을 생각해보면 연관관계가 보일 것이다.

문자열 포매팅

포매팅이라는 표현은 C의 printf 나 sprintf 등에서 사용하는 방식과 비슷하다. %s 자리에 특정 문자열을 대체하는 것 처럼 말이다. Objective-C 라면 NSString의 stringWithFormat 메소드에 해당한다.

Swift의 문자열은 특별한 포매팅 방법을 제공한다. 영어로 String Interpolation 이라는 기능이다.
var a = 1
var b = "TEST"
var c = 5.6
var someString = "a = \(a), b = \(b), c = \(c)"
// someString = "a = 1, b = TEST, c = 5.6"
괄호와 괄호 앞에 백슬래쉬(\)를 붙여서 문자열의 내용과 포함시킬 내용을 구분한다. 마치 Python의 format과 비슷하다. 하지만 Swift의 문자열 대치 커맨드는 좀 더 확장된 기능을 제공하는데 여기다 그냥 Swift 코드를 적어 넣을 수도 있다.
var someString2 = "2 + 6 = \(2 + 6)"
// someString2 = "2 + 6 = 8"
2 + 6 이라는 결과가 그대로 문자열로 변경되어서 해당 위치로 쏘옥 들어간다. 즉 \() 내용 안에 들어있는 코드가 실행된 결과가 문자열로 변경되어 들어간다는 말이다. 이건 정말 편하다.

물론 전통적인 기존 방식을 못 쓰는 것은 아니지만, 스위프트 고유가 아닌 NSString의 힘을 빌려써야 한다.
let s = String(format: "%04d", arguments: [10])
// s = "0010"
어느 순간부터 NSString 자체의 기능이 String 라는 이름으로 쓸 수 있게 되었다. 친절해졌다.

문자열의 비교

일반적으로 비교 시 사용하는 논리 연산자(Logical Operator)를 String 타입에 거의 비슷한 용도로 활용이 가능하다.
"AAA" == "BBB"      // false
"CCC" != "DDD"      // true
"EEE" == "EEE"      // true
"AAA" > "BBB"       // false
"CCC" > "BBB"       // true
스위프트는 포인터를 문법에서 배제한 언어다. '==' 등등으로 비교하는게 레퍼런스 포인터 간의 비교가 아니라 실제 데이터 비교 (정확히 표현하자면 비교연산자를 오퍼레이터 오버로딩) 이다.

덕분에 이런 문자열 비교를 switch - case 문에서도 활용이 가능하다는 건 정말 편한 것 같다.

검색

문자열에서 특정 문자열을 검색하는 건 range(of:) 메소드를 이용해서 가능하다.
s = "ABCDE"
let range = s.range(of: "C")       // Range
s[range]                           // "C"
range(of:) 메소드의 결과값은 Range 타입인데 구성요소는 String.Index 라는 특수한 타입으로 되어있어서 C처럼 정수형으로 처리하는 것은 아예 생각하지 말자. 그래도 뭔가 정보를 얻고 싶다면 Range 에서 제공하는 lowerBound 와 upperBound 라는 프로퍼티 값을 잘 살펴보자.

일부만 가져오기(substring)

String 의 subscript 를 이용해 문자열을 슬라이싱 하는 방법은 아래왁 같다.
var str = "this is test string"
let lowerBound = String.Index(encodedOffset: 5)
let upperBound = String.Index(encodedOffset: 6)
str[lowerBound...upperBound]   // "is"
Swift 의 Range 를 이용해 문자열의 subscript 를 이용하면 문자열의 일부를 잘라낼 수 있다. 다만 앞서 검색과 비슷하게 String.Index 라는 타입을 이용해 범위(Range) 를 만들어서 이를 subscript 하는 귀찮은 점이 있다.

이전에는 substring 같은 메소드가 있기는 있었지만 deprecated 되었으니 이제는 이런 방식을 쓰면 된다.

앞 뒤 공백 잘라내기(Trim)

살짝 좀 귀찮은 방법으로 Trim 기능을 이용 할 수 있다.​
let someString = "   Blah blah foo bar   "
someString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
// "Blah blah foo bar"
아이고 귀찮아. 그냥 .trim() 같은거 만들어 주면 안되겠나?! 라고 생각된다면 그냥 extension 으로 만들면 된다. ;-)

기타 기능들

String 타입도 많은 기능이 있는데 이 중 유용할 것 같은 프로퍼티와 메소드만 간추려본다:
  • isEmpty(): 빈 문자열이면 true
  • ​hasPrefix("..."): 특정 문자열로 시작하면 true
  • hasSuffix("..."): 특정 문자열로 끝나면 true
  • uppercased(): 몽땅 대문자로 바꾼 문자열
  • lowercased(): 몽땅 소문자로 바꾼 문자열
혹시 문자열을 정수 등의 숫자로 컨버팅 하고 싶다면 아래와 같은 식으로 시도해보자.
let integerValue = Int(someString)
위의 예제에서 만약 변환이 실패하면 nil 이 돌아오니 옵셔널을 확인하는 것을 잊지 말자.

[관련글] Swift - 옵셔널(Optionals)
[관련글] Swift - 오퍼레이터 오버로드(Operator Overloads)

[돌아가기] 스위프트(Swift) 가이드

댓글 2개 :

익명 :

좋은 정보에 먼저 감사드립니다.^^;

문자열 포매팅에서 NSString(format 과 유사하기

println(String(format: "hex string: %X", 123456))
println(String(format: "a float number: %.5f", 1.0321))

String(format: 이라는 방법도 있네요.

Renn Seo :

@익명:
네. 해당 글을 쓸 당시에는 Swift String에 NSString Bridge가 자동으로 되지 않던 시절이라 그렇습니다만 현재(1.2)는 자동으로 브릿지 매핑이 되어 있습니다.

그런데 Xcode7 Beta 5(Swift 2.0 Beta)에서 NSString Bridge가 다시 사라져 버렸습니다. 어떻게 될지 알 수가 없네요. ;;;;