2014년 6월 10일 화요일

Swift - 문자열(String)

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

문자열의 정의는 위에서 이야기 한 것 처럼 문자열로 표시하기만 하면 알아서 타입이 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 을 이용 할 때의 지옥이 사라진다. 환상적이다.

문자열의 길이

스위프트의 String 타입은 lengthOfBytesUsingEncoding() 메소드를 제공한다. 이름에서 보듯이 특정 인코딩을 이용해 길이를 알려주는 메소드다.
var s = "test"
s.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)   // 4
UTF-8인코딩을 기준으로 영문자의 경우 문자의 갯수를 잘 알 수 있다. 하지만 한글 등 멀티바이트 문자를 사용하게 되면 오해(?)를 불러오는 결과를 초래한다.
s = "한글"
s.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)   // 6
s.lengthOfBytesUsingEncoding(NSUTF16StringEncoding)  // 4
메소드 이름이 '바이트의 길이' 라는 점에 유의해야 한다. 그래서 유니코드의 경우 인코딩에 따라 다른 결과를 돌려준다. 따라서 한국인인 우리가 원하는 결과가 아니다라는 것을 알아둬야 한다. 그렇다고 아예 한국적인(?) 방법이 없는 것도 아니다.
countElements(s)      // 2
countElements() 라는 함수를 이용하면 정확하게 2 라는 길이를 알 수 있다. 물론 영문자의 경우도 정확하게 가져온다. 이 함수는 정확하게 말하자면 String을 구성하고 있는 각 Character의 갯수를 알아내는 함수다. Character를 이용해 문자열을 만들어 보자
var hc1 = Character("한")
var hc2 = Character("글")
s = hc1 + hc2               // "한글"
이번에는 시험삼아 문자열 이터레이션도 해보자.
for ch in "새로운한글" {
    println(ch)
}
위의 결과는 콘솔에 새/로/운/한/글 각 한 글자를 한 라인씩 출력한다. 여기서 ch의 타입은 Character 이다. String이 Character의 모임이라는 것을 알 수 있다.

문자열 포매팅(String Formatting)

포매팅이라는 표현은 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 = NSString(format: "%04d", 10)
// s = "0010"

문자열의 비교

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

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

검색

문자열에서 특정 문자열을 검색하는 건 rangeOfString() 메소드를 이용해서 가능하다.
s = "ABCDE"
let range = s.rangeOfString("C")
range.startIndex                   // 2
range.endIndex                     // 3
s[range]                           // "C"
rangeOfString() 메소드는 사실 좀 더 복잡한 파라미터를 제공하지만 단순하게 이렇게도 쓸 수 있다. 그런데 여기서 주의할 것은 rangeOfString()이 리턴하는 범위(위에서는 range 변수)는 일반적인 Range 타입이 아니라는 점이다. 타입 이름은 좀 독특한 VSs5Range 형식인데 왜 일반적인 Range 타입과 다른지는 의문이다. 더구나 이 타입은 그냥 쓸 수도 없다 ㅠㅠ

일부만 가져오기(substring)

특정 인덱스를 기준으로 뒷 혹은 앞쪽의 모든 문자열을 가져오는 메소드는 substringFromIndex() 와 substringToIndex() 가 있다.
var str = "this is test string"
str.substringFromIndex(5)      // "is test string"
str.substringToIndex(10)       // "this is the"
그리고 중간의 일부만 가져오기 위해 substringWithRange() 라는 메소드가 있다. 인자로 Range를 받는데 이 녀석을 도데체 어떻게 만들어야 하는지를 모르겠다 으악 -_-;;; 뭐야 도데체!!!

임시방편이로 앞서 소개한 앞과 뒤 부분을 가져오는 메소드를 섞어 쓰면 비슷하게 사용 할 수는 있겠지만 인덱스 계산이 상당히 귀찮을 것이다. 그래서 다른 방법으로, String을 NSString 형식으로 만들어서 가져오는 방법이 있다.
str = "ABCDE"
let s2 = str as NSString
s2.substringWithRange(NSRange(location:0, length:3))  // "ABC"
아직 미완성이라고 생각하는 것이 좋을까?

일단 위의 검색 시 사용되는 range 타입과 생성방법을 알게되면 좀 더 편하게 substring을 할 수 있을 것 같기도 하다.

앞 뒤 공백 잘라내기(Trim)

살짝 좀 귀찮은 방법으로 Trim 기능을 이용 할 수 있다.​
var someString = "   Blah blah foo bar   "
someString.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
// someString = "Blah blah foo bar"
아이고 귀찮아. 그냥 .trim() 같은거 만들어 주면 안되겠나?! (슬쩍 보면 알겠지만 저 메소드는 NSString에 구현되어 있는 거다. 결국 스위프트의 String은 NSString에 다리를 놓고 있다는 걸 알 수 있다 -_-)

기타 기능들

String 타입도 많은 기능이 있는데 이 중 유용할 것 같은 프로퍼티와 메소드만 간추려본다:
  • isEmpty(): 빈 문자열이면 true
  • ​hasPrefix("..."): 특정 문자열로 시작하면 true
  • hasSuffix("..."): 특정 문자열로 끝나면 true
  • uppercaseString: 몽땅 대문자로 바꾼 문자열
  • lowercaseString: 몽땅 소문자로 바꾼 문자열
  • toInt(): 정수형으로 변경해서 리턴. 옵셔널이기 때문에 변경이 불가능하면 nil이 리턴된다.
왜 toDouble() 같은건 없을까. 여기서도 스위프트가 아직 미완성이 아닐까 생각되게 만든다.

[관련글] 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가 다시 사라져 버렸습니다. 어떻게 될지 알 수가 없네요. ;;;;