아리의 iOS 탐구생활

[Swift] String 고수가 될거야 본문

Swift/자료구조

[Swift] String 고수가 될거야

Ari Lee 2021. 8. 25. 00:18
반응형

✔️ Apple 공식문서 참고

 

Apple Developer Documentation

 

developer.apple.com

 

Strings and Characters — The Swift Programming Language (Swift 5.5)

Strings and Characters A string is a series of characters, such as "hello, world" or "albatross". Swift strings are represented by the String type. The contents of a String can be accessed in various ways, including as a collection of Character values. Swi

docs.swift.org

 

마지막 업데이트 날짜: 2021-09-16

 

<<< 스압 주의 >>>

PC로 보면 TOC 덕에 좀더 수월하게 보실 수 있습니다. (해상도 커야함...)

 

 

 

 

✔️ String init

코딩테스트를 풀면서 자주 사용하게되는 기본 메서드.

 

 

  • 반복되는 문자열로 초기화 init(repeating: String, count: Int)
print("*" + String(repeating: " *", count: 4))
// * * * * *

 

  • 문자열 길이 확인 count(Int)
let str = "안녕하세요."
str.count // 6

 

 

 

 

🔍 String Basic

Character 타입에서는 빈문자를 저장하려면 공백을 넣어야하지만 String에서는 공백없이 빈 문자를 저장할 수 있다.

let emptyChar: Character = " "
let emptyString = ""
let emptyString2 = String()
emptyString.count // 0

 

 

NSString 사용하면 참조타입으로 사용 가능하다. String 호환도 된다. 다만 타입캐스팅을 해야한다.

두 문자열 타입은 호환은 가능하지만 유니코드 형식이 다르기 때문에 조심해서 사용해야 한다.

var nsstr: NSString = "str"
let str: String = nsstr as String
nsstr = str as NSString

 

 

👉🏻  radix 진법변환

print(String(123, radix: 16)) // 7b
print(String(123, radix: 8)) // 173
print(String(123, radix: 2)) // 1111011

 

👉🏻  문자열 요소 확인

var str = "Hello, Swift String"
str.count // 문자열 갯수 확인 21
str.count == 0 // 비어있는지 확인 방법 1
str.isEmpty // 비어있는지 확인 방법 2

 

👉🏻  문자열  비교

var str = "Hello, Swift String"
str == "Apple" // 문자열이 같은지 확인
"apple" != "Apple" // 스위프트는 대소문자를 따진다. 그래서 true
"apple" < "Apple" // 같은 문자일 경우 문자코드를 비교한다. 'a'가 값이 더크기 때문에 false이다.


let largeA = "Apple"
let smallA = "apple"
largeA.compare(smallA) == .orderedSame // 이 메서드도 대소문자를 구분한다.
largeA.caseInsensitiveCompare(smallA) == .orderedSame // 이건 대소문자를 구분하지 않는 메서드다.
largeA.compare(smallA, options: [.caseInsensitive]) == .orderedSame // 위와 같은 기능을 한다.
  • 메소드를 통해 접두어, 접미어를 비교할 수도 있다.
let str = "Hello, Swift Programming!"
let prefix = "hello"
let suffix = "Programming"
str.lowercased().hasPrefix(prefix.lowercased()) // 소문자로 바꿔준 후 비교
str.hasSuffix(suffix) // 이 메서드도 대소문자를 구분하기때문에 주의한다. 문자열옵션을 지원하지 않는다.
  • hasPrefix() 접두어를 비교해준다.
  • hasSuffix() 접미어를 비교해준다.

 

 

👉🏻  대소문자 변환

var str = "Hello, Swift String"
print("""
      \(str.lowercased()) // 소문자로
      \(str.uppercased()) // 대문자로
      \(str) // 원본
      \("mac book".capitalized) // 맨 앞글자만 대문자로
      """)

 

👉🏻  randomElement()

문자열중 한개만 뽑아서 반환한다.

print("1234567890".randomElement())

 

 

👉🏻  shuffled()

섞인 문자열이 배열로 반환된다.

print("1234567890".shuffled())

 

 

 

 

✔️ Unicode

스위프트는 문자열을 저장할 유니코드의 독립적인 문자로 저장한다. 그래서 utf8 인코딩도 간편하게 있다. 하지만 대부분에 앱에서는 특정 인코딩을 처리할 필요가 없다. 그래서 그대로 문자열을 사용해도 문제없다. 그냥 이런 기능이 있다는 것만 알고 넘어가도 좋다.

만약 특정 인코딩이 필요하다면 공식 문서를 참고하자.

let string = "Swift String"
print(Array(string.utf8)) // 유니코드 스칼라 값의 8비트 인코딩
print(Array(string.utf16)) // 16비트 인코딩

 

 

이렇게 유니코드 문자를 직접 입력하는 것도 가능하다.

var up = "👍🏻"
up = "\u{1F44D}\u{1F3FB}"
//👍🏻
//올린 엄지
//유니코드: U+1F44D U+1F3FB, UTF-8: F0 9F 91 8D F0 9F 8F BB

해당 이모지의 유니코드를 알고싶다면 아래 사진을 따라오면 얻을 수 있다.

 

이모지 단축키 ( Ctrl + CMD + Space )

 

커진 이모지 창을 다시 원래대로 줄이고 싶다면 키울때 눌렀던 버튼을 다시 눌러주면 된다.

 

 

 

 

✔️ Multiline String Literals

항상 새로운 라인에서 입력을 시작해야 하고, 마지막에 따옴표 3개가 들여쓰기의 시작 기준이 되니 주의한다.

let swift = """
          __,
         (           o  /) _/_
          `.  , , , ,  //  /
        (___)(_(_/_(_ //_ (__
                     /)
                    (/
        """ // 이 마지막 따옴표 3개가 들여쓰기의 기준이 된다.
print(swift)

 

 

 

 

✔️ Raw String

문자열 리터럴을 '#'으로 감싸면 Raw String이 된다. 주로 특수문자가 포함된 문자를 구성할 때 사용한다.

기존에는 백슬래시를 통하여 특수문자를 작성했었는데, 이제 '#'으로 감싸서 사용해보자. 정규식 문자를 직관적이게 작성할 수 있다.

print("\"Hello\", Swift") // 기존 큰따옴표 추가시 백슬래시 활용
print(#""Hello", Swift"#) // raw string

print(#"Lee\nAri"#)
print(#"Lee\#nAri"#) // escape sequence를 사용하려면 백슬래시 옆에 '#'을 추가해야한다.
print(###"Lee\###nAri"###) // 샵의 갯수는 중요하지 않지만 갯수는 앞뒤 모두 동일해야한다.

let str1 = "Lee Ari"
print(#"\#(str1)"#) // 문자열 보간법과 동시 사용할 때도 '#'추가를 해줘야 한다.

 

 

 

 

✔️ String Interpolation

문자열 보간법을 이용하면 변수와 문자열을 같이 출력할 수 있게 된다. 보다 직관적으로 문자열을 구성할 수 있다. 그리고 최종 문자열을 쉽게 유추할 수 있다는 것도 장점이다.

let size = 12.345
print("\(size)KB") // 문자열 보간법

 

단점은 원하는 포맷은 직접 지정할 수가 없다.

 

 

 

 

✔️ Format Specifier

원하는 문자열을 지정하고 싶다면 문자열 생성자와 포맷 지정자를 같이 사용한다.

 

%f 실수출력 (%.1f : 소숫점 1자리까지만 출력)

print(String(format: "%.1fKB", 12.345)) // "12.3KB"
print(String(format: "%.4f", 12.345)) // "12.3450"
print(String(format: "%10.4f", 12.345)) // "   12.3450" 10은 문자열의 총 길이를 지정하고
print(String(format: "%010.4f", 12.345)) // "00012.3450" 010은 문자열의 총길이에 빈곳에는 0으로 채운다.

 

 

%@ 문자열과 참조타입을 대체할 사용한다.

print(String(format: "Hello, %@", "Swift"))

let firstName = "Ari"
let lastName = "Lee"
let korFormat = "내 이름은 %2$@ %1$@!" // 첫번째와 두번째 순서를 1$, 2$ 를 이용하여 바꾸었음
let engFormat = "my name is %@ %@!"
print(String(format: korFormat, firstName, lastName))
print(String(format: engFormat, firstName, lastName))
/*
내 이름은 Lee Ari!
my name is Ari Lee!
*/

 

 

특수문자 사용

print("\\") // 백슬래시는 두번써야 하나 나옴.
print("A\tB") // 탭추가
print("C\nD") // 줄바꿈
print("\'Hello\', Swift") // 따옴표도 백슬래시랑 같이 써야함

 

 

 

 

%d 정수를 대체할 사용한다.

print(String(format: "%d", 12)) // 정수만 대체된다
print(String(format: "[%d]", 12345)) // "[12345]\n" // 필요한 자릿수 만큼만 출력
print(String(format: "[%10d]", 12345)) // "[     12345]" // 자릿수가 양수로 지정하면 오른쪽 정렬
print(String(format: "[%-10d]", 12345)) // "[12345     ]" // 음수면 왼쪽 정렬

 

 

 

 

✔️ String Interpolation System

포맷은 디버깅엔 적합하지만, 사용자를 대상으로 출력하는 문자열로는 적합하지 않다.

struct Size {
    var width = 0
    var height = 0
}

let own = Size(width: 50, height: 30)
print("\(own)") // "Size(width: 50, height: 30)\n"

 

기존에는 프로토콜을 채택하는 방식으로 해당 문제를 해결하였다.

extension Size: CustomStringConvertible { // 원하는 포맷으로 문자열 출력.
    var description: String {
        return "\(width) x \(height)"
    }
}

 

동일한 방법을 새로 도입된 String Interpolation으로 해결해보자.

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Size) {  // 파라미터 타입은 String Interpolation을 지원할 타입으로 지정한다.
        appendInterpolation("\(value.width) x \(value.height)")
    }
}

이전과 다른점은 파라미터가 아닌 메소드를 통하여 파라미터를 전달할 수 있다.

 

 

아래는 Number Formatter Style 받는 메소드를 추가로 구현한 것이다.

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: Size) {
        appendInterpolation("\(value.width) x \(value.height)")
    }
    
    mutating func appendInterpolation(_ value: Size, style: NumberFormatter.Style) {
        let formatter = NumberFormatter()
        formatter.numberStyle = style
    
        if let width = formatter.string(for: value.width), let height = formatter.string(for: value.height) {
            appendInterpolation("\(width) x \(height)")
        } else { // 지정한 옵션으로 문자열을 구성할 수 없다면 이전과 동일한 방식으로 출력한다.
            appendInterpolation("\(value.width) x \(value.height)")
        }
    }
}
print("\(own)") // "50 x 30\n"
print("\(own, style: .spellOut)") // "fifty x thirty\n"

 

 

✔️ String Index

let str = “leeari”

str[3] // a

 

swift에서는 다른 언어들과는 다르게 위와 같은 방법으로 문자를 가져올수가 없다.

(매우 까다롭다...)

왜 까다로운지 찾아봤더니 공식문서에는 다음과 같이 적혀있다.

 

유니코드의 독립적인 형태로 문자열을 처리하기 때문에 이렇게 복잡한 인덱스를 사용한다.


쩝.. 그래도 왜 이런지는 궁금증으 해결되지는 않는다.. 나중에 다시한번 더 알아봐야겠다.

 

 

  • 위와 같은 방법으로 가져올수 있는 비슷~한 방법이 있는데, 그것은 배열로 변환하여 가져오는 것이다.
Array("leeari")[3] // a

String 인덱스 다루는게 까다로워서 배열로 변환하여 문제를 풀면 좀더 수월해지는 것 같았다.

 

문자열에서 직접 인덱스를 통하여 접근하려면,

startIndex와 endIndex로 접근할수 있으나 주의할 점은 endIndex마지막 문자열의 인덱스 다음을 가리킨다.

따라서 startIndex, endIndex, index를 잘 조합하여 접근해야한다.

 

 

index를 이용하여 접근하는 요소들은 모두 Character 형이다.
  • 인자로 들어온 인덱스에서 offsetBy 차이만큼 떨어진 곳을 접근
str[str.index(str.endIndex, offsetBy: -3)] // a

str[str.index(str.startIndex, offsetBy: 3)] // a
// 위에 예시인 str[3]과 같다고 볼 수 있다.

 

  • 인자로 들어온 인텍스 1칸 앞(after)과 뒤(before)를 접근
str[str.index(after: str.startIndex)] // e
str[str.index(before: str.endIndex)] // i

 

  • 범위연산자를 통해서 응용하여 접근하면 String형이다.
str[str.index(str.endIndex, offsetBy: -3)...str.index(str.endIndex, offsetBy: -1)]
/* ari */

 

  • indices를 아래와 같이 이용하면 보다 손쉽게 문자열 index에 접근이 가능하다.
for index in str.indices {
    print(str[index], terminator: " ")
}
// l e e a r i

 

  • 문자열의 value를 통해 인덱스를 알아내는 방법. int형 string형 모두 가능하다.
    Optional 타입이라서 강제로 추출하여 사용한다. 해당하는 값이 없어서 인덱스가 nil이라면 오류가 발생하기 때문에 주의한다.
str.index(of: “a”)
print(str[index!]) // a

 

  • index 총정리
let str = "Swift"
print(str[str.startIndex]) // 첫번째 글자 접근
print(str[str.index(before: str.endIndex)]) // 마지막 글자 접근
print(str[str.index(after: str.startIndex)]) // 두번째 문자 접근
print(str[str.index(str.startIndex, offsetBy: 2)]) // 세번째 문자 접근
print(str[str.index(str.endIndex, offsetBy: -2)]) // 네번째글자 접근

 

 

 

뭐 이렇게 인덱스를 사용하는 방법을 익혀보았으나 귀찮다면, 아래 방법으로 String 타입을 확장시키는 방법도 있다.

extension String {
    subscript(i: Int) -> String? {
        guard (0..<count).contains(i) else { return nil }
        let target = index(startIndex, offsetBy: i)
        return String(self[target])
    }
}

let str = "Swift"
str[3] // "f"
str[100] // nil

 

 

 

✔️ SubString

Substring은 원본 문자열의 메모리를 공유한다.

값을 읽기만 할때는 원본메모리를 공유하고, 값을 변경하는 시점에만 새로운 메모리가 생성한다.

String.SubSequence // 이건 왜 사용할까? 문자열을 처리할때 메모리를 절약하기 위해서...

 

 

코드 예제를 보며 이해해보자.

let str = "Hello, Swift"
let l = str.lowercased() // 전체 문자를 소문자로 바꿔서 새로운 문자열로 리턴하기 때문에 타입은 String이다.

var first = str.prefix(1) // 원본 문자열 메모리 공유
first.insert("!", at: first.endIndex) // 값을 변경하여 새로운 메모리 생성
print(str, first) // Copy-on-Write Optimization 불필요한 복사와 메모리 생성을 최대한 줄여서 코드의 실행 성능을 높혀준다.

let newFirst = String(str.prefix(1)) // 이렇게 생성자로 전달하여도 새로운 메모리를 생성한다.

let s1 = str[..<str.index(str.startIndex, offsetBy: 2)]
let s2 = str[str.index(str.startIndex, offsetBy: 2)...]
print(s1, s2, type(of: s1)) // 인덱스로 추출한 문자열도 Substring

let lower = str.index(str.startIndex, offsetBy: 2)
let upper = str.index(str.startIndex, offsetBy: 5)
let s3 = str[lower ... upper]
print(s3, type(of: s3)) // 이 문자열 또한 마찬가지이다.

 

 

 

 

✔️ append 와 appending

스위프트에서 동사는 원본을 직접 변경하고 값을 리턴하지 않는다. ing나 ed가 붙은 명사는 복사본을 리턴한다.

  • append는 문자열 뒤에 새로운 문자열을 연결할 수 있다.
  • appending은 복사본에 새로운 문자열을 추가하여 리턴한다.
  • appendingFormat은 원하는 포맷으로 구성된 문자열을 연결할 때 주로 사용한다.
var str = "Hello"
str.append(", ") // 동사는 원본을 직접변경
print(str) // "Hello, "

let s = str.appending("Swift") // 명사는 복사본을 리턴
print(str, s) // "Hello,  Hello, Swift"
print("Filer size is ".appendingFormat("%.1f", 12.3456)) // "Filer size is 12.3"

 

 

 

✔️ insert() 

insert() 메소드를 사용하여 특정 위치에 아래와 같이 문자열을 추가할 수 있다.

var str = "Hello Swift"
str.insert(",", at: str.index(str.startIndex, offsetBy: 5)) // Hello, Swift

 

인덱스를 사용할 땐 인덱스가 존재하지 않는다면 에러가 날 수 있기 때문에 옵셔널 바인딩을 통해 위치를 찾는 것이 안전하다.

if let index = str.firstIndex(of: "S") { // 옵셔널바인딩을 통해 특정 문자열의 위치 찾기
    str.insert(contentsOf: "Awesome", at: index)
}
// "Hello, AwesomeSwift"
if let index = str.lastIndex(of: "S") { // 옵셔널바인딩을 통해 특정 문자열의 위치 찾기
    str.insert(contentsOf: " ", at: index)
}
// "Hello, Awesome Swift"

 

 

 

 

✔️ replace, replacing

  • range는  특정 문자열의 범위를 리턴한다
  • replaceSubrange는 문자열의 범위를 받아서 그 범위에 with 문자열로 대체한다.
  • replacingCharacters는 문자열의 범위를 받고 그 범위에 with 문자열로 대체하여 복사본을 리턴한다.
let str = "Hello, Awesome Swift"
if let range = str.range(of: "Awesome Swift") { // 옵셔널 바인딩으로 특정 문자열의 범위 찾기
    str.replaceSubrange(range, with: "Swift") // 문자열의 범위를 with 문자열로 대체
}
//Hello, Swift
if let range = str.range(of: "Hello") {
    let s = str.replacingCharacters(in: range, with: "Hi")
    print(s) // Hi, Swift
    print(str) // Hello, Swift 원본을 변경하지 않았음
}
  • replacingOccurrences은 of에 검색할 문자열을 받고 with 문자열로 대체하여 새로운 값으로 리턴한다.
let str = "Hello, Swift"
print(str.replacingOccurrences(of: "Swift", with: "Awesome Swift")) // 대부분의 문자열 메서드는 대소문자를 구분한다.
// Hello, Awesome Swift
print(str.replacingOccurrences(of: "swift", with: "Awesome Swift")) // 대소문자 구분때문에 검색에 실패하였음.
// Hello, Swift
print(str.replacingOccurrences(of: "swift", with: "Awesome Swift", options: [.caseInsensitive])) // 옵션을 추가하여 대소문자 구분
// Hello, Awesome Swift

 

 

 

 

✔️ remove()

특정 문자나 범위를 삭제하려면 다음과 같이 remove 메소드를 활용할 수 있다.

  • remove는 인덱스를 전달하여 특정 문자를 삭제한다.
  • removeFirst는 삭제할 문자 갯수를 정수로 받아 앞에서부터 삭제한다.
  • removeLast는 삭제할 문자 갯수를 정수로 받아 뒤에서부터 삭제한다.
  • removeSubrange는 특정 문자열의 범위를 삭제한다.
  • removeAll은 문자열을 모두 삭제한다.
var str = "Hello, Awesome Swift!!!"
str.remove(at: str.index(before: str.endIndex)) // Hello, Awesome Swift!!

str.removeFirst(2) // llo, Awesome Swift!! ->앞 두글자 삭제
str.removeLast(2) // llo, Awesome Swift ->뒤 두글자 삭제

if let range = str.range(of: "Awesome") { // 특정 문자열의 범위만 삭제
    str.removeSubrange(range) // llo,  Swift
}
str.removeAll() // 모두 삭제.

 

 

 

✔️ Drop()

원본 문자열엔 아무런 영향을 주지않고 새로운 문자열을 리턴해준다.

returnType :  String.SubSequence
let str = "leeari"
str.dropFirst() // eeari
str.dropFirst(3) // ari
str.dropLast() // leear
str.dropLast(3) // lee

let str = "Hello, Awesome Swift!!!"
str.dropLast() // 원본문자열을 공유한다.
str.dropLast(3) // sub string이므로 원본 문자열을 공유한다.
let subst = str.drop(while: { (ch) -> Bool in
    return ch != ","
}) // 문자열을 돌면서 맞는 문자열을 만나기 전까지 삭제하다가 남은 문자열을 리턴한다.
print(subst)

 

 

 

 

✔️ contains()

문자열에 특정 문자가 포함되어 있는지 검색하는 메서드이다.

lowercased() 나 uppercased()를 활용하면 대소문자를 무시하고 검색할 수 있다.

let str = "Hello, Swift"
str.lowercased().contains("swift") // 대소문자를 무시하고 검색하는 방법

 

 

 

 

✔️ range(of:)

문자열의 특정 문자의 범위를 리턴해주는 메서드이다.

options: 파라미터를 전달하면 대소문자를 무시할 수 있다.

let str = "Hello, Swift"
str.range(of: "Swift")
str.range(of: "swift", options: [.caseInsensitive]) // 대소문자 무시 옵션

 

 

 

 

✔️ commonPrefix(with:)

메서드 호출대상에 포함되어 있는 접두어를 리턴해주는 메서드이다.

let str2 = "Hello, Programming"
let str3 = str2.lowercased()

var common = str.commonPrefix(with: str2) // 공통된 문자열만 뽑기
common = str.commonPrefix(with: str3, options: [.caseInsensitive]) // 옵션을 활용하여 대소문자 구분 무시
str3.commonPrefix(with: str, options: [.caseInsensitive])

 

 

 

✔️ trimmingCharacters(in:)

in으로 받은 문자집합에 포함된 문자가 문자열의 시작과 끝에 포함되어 있다면 제거하여 새로운 문자열을 반환해준다.

아래 예제는 시작점과 끝에 있는 공백을 제거해주는 예제이다.

let str = " a b "
print(str.trimmingCharacters(in: [" "])) // "a b"
반응형
Comments