/ Etc...?!

우리회사 슈퍼 개발자 JJM과 함께한 Apple의 The Swift Programming Language 4.x - Part.1

스터디 내용

  • 대상 : 우리회사(favorie) 개발팀 및 CEO

  • 교재 : iBooks에서 Apple이 무료로 제공하는 The Swift Programming Language

  • 방법 : 회사에서 Objective-C로 만든 작은 앱Swift로 변경하면서 스터디를 진행함

개인적 소감

  • 세상의 모든 문제를 CPython으로 해결하는 입장에서 회사에서 제작하는 iOS 앱과 관려된 코드 리뷰나 개발 이슈(issue)를 함께 논의하기 위해서 스터디에 참여
  • 최근에 각광받는 모던(Modern) 프로그래밍 언어인 Swift를 실습을 통해서 배워보니, Kotlin이 익숙하고 새로운 Java에 대한 이해도가 살짝 좋아졌음
  • 흔하게 사용하던 Python의 문법을 탈피해서 다양한 형태의 문법을 도전하게 됨

스터디 내용 요약

  • 스터디가 끝나고 혼자서 복습하는 과정에서 Swift 4로 업데이트 되었기 때문에 요약은 Swift 4 (Beta)를 기준으로 작성하였음

  • 추신 : 이 문서가 이렇게 방대할꺼라 생각해본적 없는데, 이제 시작했으니 끝을 볼 수 밖에...

The Basics

  • Constants and Variables

    • 상수 및 변수(Constants and Variables)는 이름(maximumNumberOfLoginAttempts, welcomeMessage)을 특정 유형의 값(10, "Hello")를 연결함
    • 상수(Constants)는 값이 설정되면 변경할 수 없음(let maximumNumberOfLoginAttempts: Int = 10)
    • 변수(Variables)는 다른 값으로 설정을 변경 할 수 있음(var currentLoginAttempt: Double = 0.0)
  • Comments

    • Swift의 주석은 C의 주석과 매우 유사
    • 한 줄 주석은 두 개의 슬래시 (//)로 시작
    • 여러 줄 주석은 슬래시와 별표 (/*)로 시작하고 별표와 슬래시 (*/)로 끝남
  • Semicolons

    • 코드에 있는 각 문 다음에 세미콜론 (;)을 쓰지 않아도됨
    • 한 줄에 여러 개의 별도 명령문을 작성하려면 세미콜론이 필요(let cat = "🐱"; print(cat))
  • Integers

    • 부호가 있거나 없는 정수를 8, 16, 32 및 64 비트 형식으로 제공
    • 부호없는 정수가 UInt8 유형이고 부호가 있는 32 비트 정수가 Int32 유형
    • 정수 범위는 min, max 속성을 사용(let minValue = UInt8.min, let maxValue = UInt8.max)
  • Floating-Point Numbers

    • 부동 소숫점은 3.14159, 0.1-273.15와 같이 소숫점이 있는 형식
    • Double은 64 비트 부동 소수점 숫자를 ,Float은 32 비트 부동 소수점 숫자를 나타냄
  • Type Safety and Type Inference

    • SwiftType Safety 언어(예를 들어, 컴파일러가 코드의 일부를 String을 예상(Inference)하면 Int를 설정 할 수 없음)
    • SwiftType Safety 언어이기 때문에 컴파일 할 때 Type을 검사하고 일치하지 않는 Type의 경우 오류(Error)로 처리함
    • Type을 지정하지 않으면 SwiftType Inference(타입 추론)를 수행하여 초기 값으로 상수 또는 변수를 선언 할 때 특히 유용, 예를 들어 let meaningOfLife = 42의 경우 meaningOfLifeInt로 설정
  • Numeric Literals

    • 정수

      • 접두사가 없으면 10 진수(let decimalInteger = 17)
      • 접두사가 0b이면 2 진수(let binaryInteger = 0b10001)
      • 접두어가 0o이면 8 진수(let octalInteger = 0o21)
      • 접두사가 0x이면 16 진수(let hexadecimalInteger = 0x11)
    • 부동소숫점

      • 1.25e21.25 x 10^2 또는 125.0
      • 1.25e-21.25 x 10^-2 또는 0.0125
      • 0xFp215(F) x 2^2 또는 60.0
      • 0xFp-215(F) x 2^-2 또는 3.75
    • 서식

      • let paddedDouble = 000123.456 == 123.456
      • let oneMillion = 1_000_000 == 1000000
      • let justOverOneMillion = 1_000_000.000_000_1 == 1000000.0000001
  • Numeric Type Conversion

    • 정수

      • UInt8UInt16의 서로간 변환은 명시적이어야 함
      let twoThousand: UInt16 = 2_000
      let one: UInt8 = 1
      let twoThousandAndOne = twoThousand + one // Error
      let twoThousandAndOne = twoThousand + UInt16(one)
      
    • 부동소숫점

      • 정수는 부동소숫점으로 변환 할 때 명시적이어야 함
      let three = 3
      let pointOneFourOneFiveNine = 0.14159
      let pi = Double(three) + pointOneFourOneFiveNine
      
  • Type Aliases

    • 기존 타입을 대체할 수 있는 별명을 typealias를 사용해서 정의(typealias AudioSample = UInt16)
  • Booleans

    • Swift에는 Bool이라는 기본 유형이 존재, Bool은 오직 참(true) 또는 거짓(false) 일 수 있기 때문에 논리 값이라함
    • Swift는 타입 안정성을 위해 Bool아닌 타입을 허용하지 않음
    let i = 1
    if i {
        // ERROR!
    }
    
  • Tuples

    • 튜플(Tuples)은 여러값을 하나로 그룹화 함
    • 튜플 내의 값은 모든 유형이 될 수 있으며 동일한 유형일 필요는 없음
    let http404Error = (404, "Not Found")
    
    • 튜플의 내용은 별도의 상수 또는 변수로 분해 할 수 있으며,이 상수 또는 변수를 평소대로 접근 할 수 있음
    let (statusCode, statusMessage) = http404Error
    print("The status code is \(statusCode)")
    
    • 튜플의 값 중 일부만 필요하면 분해 할 때 밑줄 (_)로 튜플의 일부를 무시
    let (justTheStatusCode, _) = http404Error
    print("The status code is \(justTheStatusCode)")
    // Prints "The status code is 404"
    
    • 또는 0에서 시작하는 인덱스 번호를 사용하여 튜플의 개별 요소에 접근 가능
    print("The status code is \(http404Error.0)")
    // Prints "The status code is 404"
    print("The status message is \(http404Error.1)")
    // Prints "The status message is Not Found"
    
    • 튜플의 개별 요소에 이름을 지정할 수 있음
    let http200Status = (statusCode: 200, description: "OK")
    print("The status code is \(http200Status.statusCode)")
    // Prints "The status code is 200"
    
    • 튜플은 특히 함수의 반환 값으로 유용, 서로 다른 두 개의 값이 있는 튜플을 반환하면 함수는 단일 유형의 단일 값만 반환 할 수 있는 경우보다 결과에 대한 유용한 정보를 제공
  • Optionals

    • 값이 없는 상황에서는 Optionals(옵셔널)을 사용, 선택적 요소는 두 가지 가능성을 나타냄
    var serverResponseCode: Int? = 404
    serverResponseCode = nil
    var surveyAnswer: String? // nil
    
    • nilnonoptionals(옵셔널이 아닌)이 아닌 변수나 상수에서 사용할 수 없고, 초기 값을 설정하지 않은 옵셔널의 경우 nil이 할당
    • 옵셔널 변수나 상수에 값이 할당되어 있다고 확신(!) 한다면 변수나 상수의 이름 끝에 느낌표(!)를 추가하여 접근 할 수 있음
    • 옵셔널 바인딩(Optional Binding)은 옵션에 값이 들어 있는지 여부를 확인하고 임시 상수 또는 변수로 사용할 수 있음
    if let actualNumber = Int(possibleNumber) {
        print("\"\(possibleNumber)\" has an integer value of \(actualNumber)")
    } else {
        print("\"\(possibleNumber)\" could not be converted to an integer")
    }
    
    "`Int(possibleNumber)`에 반환 된 옵셔널 값을 포함하는 경우 `actualNumber`라는 새 상수를 옵션에 포함 된 값으로 설정"
    
    • 변수나 상수 값이 접근 될 때마다 값이 항상 존재한다고 가정 할 때 옵셔널을 제거할 수 있으며, 접근은 기존의 옵셔널 해제 방법을 사용
    let possibleString: String? = "An optional string."
    let forcedString: String = possibleString! // 강제로 옵셔널 해제
    let assumedString: String! = "An implicitly unwrapped optional string."
    let implicitString: String = assumedString // 옵셔널을 해제할 필요가 없음
    
  • Error Handling

    • 오류 처리를 통해 오류의 근본 원인을 판별하고 필요한 경우 오류를 프로그램의 다른 부분으로 전달할 수 있음
    • 오류를 발생시킬 수있는 함수를 호출 할 때 try 키워드를 추가, Swiftcatch 절에 의해 처리 될 때까지 현재 범위에서 오류를 자동으로 전파
    func makeASandwich() throws {
        // ...
    }
    
    do {
        try makeASandwich()
        eatASandwich()
    } catch SandwichError.outOfCleanDishes {
        washDishes()
    } catch SandwichError.missingIngredients(let ingredients) {
        buyGroceries(ingredients)
    }
    
  • Assertions and Preconditions

    • AssertionsPreconditions은 런타임에 발생하는 검사, 실행하기 전에 필수 조건이 충족되는지 확인하기 위해이 코드를 사용하며, true 평가되면 코드 실행이 평소대로 계속됨
    let age = -3
    assert(age >= 0, "A person's age can't be less than zero.")
    // This assertion fails because -3 is not >= 0.
    
    if age > 10 {
        print("You can ride the roller-coaster or the ferris wheel.")
    } else if age > 0 {
        print("You can ride the ferris wheel.")
    } else {
        assertionFailure("A person's age can't be less than zero.")
    }
    
    // In the implementation of a subscript...
    precondition(index > 0, "Index must be greater than zero.")
    

Basic Operators

  • Terminology

    • SwiftC언어에서 제공하는 거의 대부분의 연산자를 지원하며, 범위 연산자를 비롯한 확장된 연산자를 제공
    • Swift는 삼항연산자(a ? b : c)만 지원함
  • Assignment Operator

    • 오른쪽(rhs)이 튜플(tuple) 인 경우 해당 요소를 여러 상수 또는 변수로 한 번에 분해 할 수 있습

    let (x, y) = (1, 2)

  • Arithmetic Operators

    • 산술연산(+, -, *, /)에 대한 자세한 내용은 생략
    • 나머지 연산(Remainder Operator)의 경우 a % b의 결과와 a % -b의 결과는 같음

    -9 = (4 x -2) + -1

  • Compound Assignment Operators

    • 축약연산(*=, +=, /=, ...)도 생략
  • Comparison Operators

    • 비교연산(==, !=, >, <, >=, <=)은 우리의 마음속에 있으니 또 생략
    • 튜플의 경우 첫번째 원소부터 비교하니 주의 할 것
    1. (1, "zebra") < (2, "apple") // 참 2가 1보다 큼
    2. (3, "apple") < (3, "bird") // 참 3은 동일, "bird"가 "apple"보다 큼
    3. (4, "dog") == (4, "dog") // 참 4도 같고, "dog"도 같음
  • Ternary Conditional Operator

    • 삼항연산자는 question ? answer1 : answer2 이며, question이 참이면 answer1을 거짓이면 answer2를 반환
    let contentHeight = 40
    let hasHeader = true
    let rowHeight: Int
    if hasHeader {
        rowHeight = contentHeight + 50
    } else {
        rowHeight = contentHeight + 20
    }
    // rowHeight is equal to 90
    
  • Nil-Coalescing Operator

    • nil-coalescing 연산자(a ?? b)는 a가 값을 포함하고 있으면(not null) a의 값을 반환하고, a가 값이 없으면 b를 반환함
  • Range Operators

    • 수학의 관례를 따른 ... 연산자를 지원
    for index in 1...5 { // 1,5를 포함
        print("\(index) times 5 is \(index * 5)")
    }    
    
    for index in 1..<5 { // 1을 포함, 5는 포함하지 않음
        print("\(index) times 5 is \(index * 5)")
    }    
    
    for name in names[2...] { // 2부터(2포함) 끝까지
        print(name)
    }
    
    for name in names[...2] { // 처음부터 2까지(2포함)
        print(name)
    }
    
  • Logical Operators

    • 논리 연산자 개발자의 가슴에 살아 숨쉬고 있어서 생략
    • && 단락 연산자도 그 옆에 살고 있어서 생략
    if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
        print("Welcome!")
    } else {
        print("ACCESS DENIED")
    }
    // Prints "Welcome!"    
    

Strings and Characters

  • String Literals

    • 문자열(String)은 String 유형(Type)이며, 문자열의 내용은 문자(Character)의 컬렉션(Collection)을 포함하여 다양한 방법으로 접근 가능하고 유니 코드를 빠르게 처리 할 수있는 빠른 방법을 제공함
    • """를 사용해서 문자열을 생성할 경우 여는 따옴표와 닫는 따옴표 사이의 모든 줄을 포함
    let someString = "Some string literal value"
    
    let quotation = """
    The White Rabbit put on his spectacles. "Where shall I begin,
    please your Majesty?" he asked.
    
    "Begin at the beginning," the King said gravely, "and go on
    till you come to the end; then stop."
    """
    
    let threeDoubleQuotes = """
    Escaping the first quote \"""
    Escaping all three quotes \"\"\"
    """
    
  • 빈 문자열 초기화(Initializing an Empty String)

    • 빈 문자열을 선언해야 할 때는 리터럴("")을 사용할 수 있으며, 우아하게 Stirng()을 활용할 수 있음(개인적으로 ""을 선호함)
    var emptyString = ""
    var anotherEmptyString = String()
    if emptyString.isEmpty {
        print("Nothing to see here")
    }
    
  • String Mutability

    • 문자열을 변수(var)로 선언하면 수정이 가능하고, 상수(let)로 선언하면 수정이 불가능
    var variableString = "Horse"
    variableString += " and carriage" // "Horse and carriage"
    
    let constantString = "Highlander"
    constantString += " and another Highlander" // ERROR!
    
  • Strings Are Value Types

    • SwiftString은 값 유형(Value Types)
      • String 값을 만드는 경우 해당 String 값은 함수 나 메서드에 전달 될 때 또는 상수 또는 변수에 할당 될 때 복사(is copied)됨, 즉 기존의 String 값의 새로운 복사본이 만들어지고, 새로운 복사본이 전달되거나 할당됨
      • Swiftcopy-by-default 동작은 함수 나 메서드가 String 값을 전달할 때, 문자열이 어디에서 전달되었는지 관계없이 해당 String 값을 소유하고 있음을 확인할 수 있으며, 전달한 문자열은 직접 수정하지 않았다면 수정되지 않는다고 확신 할 수 있음
      • Swift의 컴파일러는 문자열 사용을 최적화하여 실제 복사가 필요한 경우에만 수행하기 때문에 성능상 문제는 발생하지 않음
  • Working with Characters

    • for-in을 사용하여 문자열을 반복하여 문자열의 개별 Character 값에 액세스 할 수 있음
    for character in "Dog!🐶" {
        print(character)
    }
    // D
    // o
    // g
    // !
    // 🐶    
    
    • 또는 Character 타입을 사용할 수 있음
    let exclamationMark: Character = "!"
    
  • Concatenating Strings and Characters

    • 문자열 값을 더하기 연산자 (+)와 함께 추가하거나 연결하여 새 문자열 값을 만들 수 있음
    let string1 = "hello"
    let string2 = " there"
    var welcome = string1 + string2    
    
    • String 타입의 append() 메소드로 String 변수에 Character 값을 추가 할 수 있음
    let exclamationMark: Character = "!"
    welcome.append(exclamationMark)
    
  • String Interpolation

    • 상수, 변수, 리터럴 및 표현식을 혼합하여 문자열 리터럴에 값을 포함하여 새 문자열 값을 생성 할 때는 백슬래쉬(\)를 사용함
    let multiplier = 3
    let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
    // message is "3 times 2.5 is 7.5"
    
  • Unicode

    • Swift의 문자열 및 문자 유형은 완전히 유니 코드 호환됨
    • Swift의 네이티브 String 타입은 유니 코드 스칼라 값 기반
    • Character 값으로 표현할 수있는 유연한 방법을 제공, 예를 들어 한글의 한글 음절은 사전 합성 또는 분해 시퀀스로 나타낼 수 있고, 두 표현은 모두 Swift에서 하나의 Character 값으로 간주됨
    let precomposed: Character = "\u{D55C}"                  // 한
    let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ᄒ, ᅡ, ᆫ
    // precomposed is 한, decomposed is 한    
    
  • Counting Characters

    • 문자열에서 Character 값의 개수를 가져 오려면 문자열의 count 속성을 사용
    let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
    print("unusualMenagerie has \(unusualMenagerie.count) characters")
    // Prints "unusualMenagerie has 40 characters"
    
  • Accessing and Modifying a String

    • String Indices
    let greeting = "Guten Tag!"
    greeting[greeting.startIndex] // G
    greeting[greeting.index(before: greeting.endIndex)] // !
    greeting[greeting.index(after: greeting.startIndex)] // u
    let index = greeting.index(greeting.startIndex, offsetBy: 7)
    greeting[index] // a    
    
    • Inserting and Removing
    var welcome = "hello"
    welcome.insert("!", at: welcome.endIndex) // welcome now equals "hello!"    
    welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex)) // welcome now equals "hello there!"
    welcome.remove(at: welcome.index(before: welcome.endIndex)) // welcome now equals "hello there"    
    let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
    welcome.removeSubrange(range) // welcome now equals "hello"    
    
  • Substrings

    • 문자열과 마찬가지로 각 하위 문자열에는 하위 문자열을 구성하는 문자가 저장되는 메모리 영역이 존재, 하위 문자열은 장기 보관에 적합하지 않음
  • Comparing Strings

    • Swift는 문자열 값, 접두어 및 접미사와 같은 텍스트 값을 비교하는 세 가지 방법을 제공
    let quotation = "We're a lot alike, you and I."
    let sameQuotation = "We're a lot alike, you and I."
    if quotation == sameQuotation {
        print("These two strings are considered equal")
    }
    
    let romeoAndJuliet = [
        "Act 1 Scene 1: Verona, A public place",
        "Act 1 Scene 2: Capulet's mansion",
        "Act 1 Scene 3: A room in Capulet's mansion",
        "Act 1 Scene 4: A street outside Capulet's mansion",
        "Act 1 Scene 5: The Great Hall in Capulet's mansion",
        "Act 2 Scene 1: Outside Capulet's mansion",
        "Act 2 Scene 2: Capulet's orchard",
        "Act 2 Scene 3: Outside Friar Lawrence's cell",
        "Act 2 Scene 4: A street in Verona",
        "Act 2 Scene 5: Capulet's mansion",
        "Act 2 Scene 6: Friar Lawrence's cell"
    ]    
    
    var act1SceneCount = 0
    for scene in romeoAndJuliet {
        if scene.hasPrefix("Act 1 ") {
            act1SceneCount += 1
        }
    }
    print("There are \(act1SceneCount) scenes in Act 1") // Prints "There are 5 scenes in Act 1"    
    
    var mansionCount = 0
    var cellCount = 0
    for scene in romeoAndJuliet {
        if scene.hasSuffix("Capulet's mansion") {
            mansionCount += 1
        } else if scene.hasSuffix("Friar Lawrence's cell") {
            cellCount += 1
        }
    }
    print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") // Prints "6 mansion scenes; 2 cell scenes"
    
  • Unicode Representations of Strings

    • 유니 코드 문자열이 텍스트 파일이나 다른 형태로 기록되면 해당 문자열의 유니 코드는 여러 유니 코드 정의 인코딩 양식 중 하나로 인코딩되며, 정의 인코딩 양식은 코드 단위로 알려진 작은 덩어리로 문자열을 인코딩됨
    • UTF-8 인코딩 형식 (8 비트 코드 단위로 문자열 인코딩), UTF-16 인코딩 형식 (16 비트 코드 단위로 문자열 인코딩) 및 UTF-32 인코딩 형식 (인코딩 32 비트 코드 단위의 문자열)
    let dogString = "Dog‼🐶"
    
    for codeUnit in dogString.utf8 {
        print("\(codeUenit) ", terminator: "") // Prints "68 111 103 226 128 188 240 159 144 182 "
    }
    
    for codeUnit in dogString.utf16 {
        print("\(codeUnit) ", terminator: "") // Prints "68 111 103 8252 55357 56374 "
    }
    
    for scalar in dogString.unicodeScalars {
        print("\(scalar.value) ", terminator: "") // Prints "68 111 103 8252 128054 "
    }
    
    for scalar in dogString.unicodeScalars {
        print("\(scalar) ")
    }
    // D
    // o
    // g
    // ‼
    // 🐶
    

Collection Types

Swift는 값을 저장하기 위한 배열(Array), 집합(Set) 및 사전(Dictionaries)이라는 세 가지 기본 형식을 제공

  • Mutability of Collections

    • 컬렉션을 변수에 할당하면 작성된 콜렉션이 변경 가능, 상수에 할당하면 해당 컬렉션은 변경되지 않으며 크기와 내용은 변경할 수 없음
  • Arrays

    • 배열은 동일한 유형의 값을 정렬 된 목록에 저장, 동일한 값이 여러 위치에 여러 번 배열에 나타날 수 있음
    • 배열 자료형은 Array<>와 같은 형태로 생성할 수 있으나, 약식인 []() 형태를 권장
    var someInts = [Int]() // 빈 배열
    
    • 동일한 값을 가지고 있는 배열을 만드는 방법은 아래와 같음
    var threeDoubles = Array(repeating: 0.0, count: 3)
    
    • 두 배열을 합치는 방법은 직관적인 + 연산을 사용하면 되며, 새로운 배열이 생성되며, 새로운 배열의 자료형은 두 배열에서 유추하여 결정
    var threeDoubles = Array(repeating: 0.0, count: 3)
    var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
    
    var sixDoubles = threeDoubles + anotherThreeDoubles
    
    • 배열 관련 연산

      • 전형적인 배열 초기화 방법
      var shoppingList = ["Eggs", "Milk"]
      
      • 비어있는 배열인지 확인하는 '전형적인' 방법
      if shoppingList.isEmpty {
          print("The shopping list is empty.")
      } else {
          print("The shopping list is not empty.")
      }
      
      • 배열에 값을 추가하는 '전형적인' 방법
      shoppingList.append("Flour") // shoppingList에 3개의 값이 저장되어 있음
      shoppingList += ["Baking Powder"] // shoppingList에 4개의 값이 저장되어 있음
      shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
      // shoppingList에 7개의 값이 저장되어 있음
      
      • 인덱스를 사용한 배열 참조 방법
      var firstItem = shoppingList[0]
      // firstItem은 "Eggs"
      shoppingList[4...6] = ["Bananas", "Apples"]
      // ["Eggs", "Milk", "Flour", "Baking Powder", "Bananas", "Apples"]        
      
      • 지정된 위치에 값을 추가하는 방법
      shoppingList.insert("Maple Syrup", at: 0) // `0`번째에 삽입
      
      • 배열에서 값을 삭제하는 방법
      let mapleSyrup = shoppingList.remove(at: 0) // "Maple Syrup" 삭제
      let apples = shoppingList.removeLast() // "Apples" 삭제
      
      • 배열을 순회하는 방법
      for item in shoppingList { // 전형적인 for .. in
          print(item)
      }        
      
      • 배열 순회시 인덱스를 사용하는 방법
      for (index, value) in shoppingList.enumerated() {
          print("Item \(index + 1): \(value)")
      }            
      
  • Sets

Set(집합)은 순서가 없는 동일한 유형의 고유 한 값(중복을 허용하지 않음)을 저장하기 때문에 순서가 중요하지 않고, 항목의 중복을 허용하지 않는 경우 배열을 대신해서 사용할 수 있음

집합에 저장하기 위해선 hashable 해야 하며, 집합에 저장되는 타입에 대한 해시 값을 계산하는 방법을 제공해야함

해시 값은 a == b 일 경우 a.hashValue == b.hashValue 와 같이 비교되는 모든 객체에 대해 동일한 Int 값임

Swift의 모든 기본형 (예 : String, Int, DoubleBool)은 기본적으로 hashable하며 집합 또는 사전의 키로 사용 할 수 있음

associated values와 상관없이 Enumeration(열거형)은 기본적으로 hashable 가능함

  • Performing Set Operations

    • 생성 및 초기화

    배열과 달리 집합은 축약형을 제공하지 않기 않는다. 그리고 빈 집합으로 값을 초기화 할 때 배열의 표기법을 사용하긴 하지만 집합으로 선언된 변수의 자료형은 변경되지 않음

    var letters = Set<Character>()
    
    print("letters is of type Set<Character> with \(letters.count) items.")
    // Prints "letters is of type Set<Character> with 0 items."
    
    letters.insert("a")
    // letters now contains 1 value of type Character
    
    letters = []
    // letters is now an empty set, but is still of type Set<Character>
    
    

    집합 자료형에 데이터를 추가하는 방법은 아래와 같고, 대부분의 연산은 배열과 유사함

    var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
    // favoriteGenres has been initialized with three initial items
    
    var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
    
    
    • 집합연산

    집합 고유 연산인 union(합집합), intersection(교집합), subtracting(차집합), symmetric Difference(대칭차집합)을 지원함

    let oddDigits: Set = [1, 3, 5, 7, 9]
    let evenDigits: Set = [0, 2, 4, 6, 8]
    let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
    
    oddDigits.union(evenDigits).sorted()
    // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    oddDigits.intersection(evenDigits).sorted()
    // []
    oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
    // [1, 9]
    oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
    // [1, 2, 9]    
    

    또한 포함관계를 나타내는 subset(부분집합), superset(상위집합), disjoint(서로소), strict subset(진부분집합)도 지원함

    let houseAnimals: Set = ["🐶", "🐱"]
    let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
    let cityAnimals: Set = ["🐦", "🐭"]
    
    houseAnimals.isSubset(of: farmAnimals)
    // true
    farmAnimals.isSuperset(of: houseAnimals)
    // true
    farmAnimals.isDisjoint(with: cityAnimals)
    // true    
    
  • Dictionaries

    Dictionaries(사전)은 순서가 정의되지 않은 콜렉션에서 동일한 유형의 키와 같은 유형의 키 사이의 관계를 저장합니다. 각 값은 사전 내에서 해당 값의 식별자 역할을 하는 고유 키와 관련있습니다.

    배열의 항목과 달리 사전의 항목에는 지정된 순서가 없습니다. 실제 단어 사전을 사용하여 특정 단어에 대한 정의를 찾는 것과 거의 같은 방식으로 식별자를 기반으로 값을 검색해야 할 때 사전을 사용합니다.

    • 생성 및 초기화

    [K:V] 의 자료형으로 사전을 선언하고, 배열과 달리 [16]Key가 되고 "sixteen"Value가 된다. 즉, 정수형 인덱스를 검색해서 해당 데이터를 검색함

    var namesOfIntegers = [Int: String]()
    
    namesOfIntegers[16] = "sixteen"
    namesOfIntegers = [:]
    

    airports의 경우 문자열 인덱스와 문자열 내용으로 선언했음

    var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
    var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
    
    • 사전 연산

    사전에 저장된 값을 변경하는 방법은 updateValue를 사용해서 변경하는 방법과 사전을 배열의 인덱스처럼 사용할 수 있음

    if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
        print("The old value for DUB was \(oldValue).")
    }
    // Prints "The old value for DUB was Dublin."
    
    if let airportName = airports["DUB"] {
        print("The name of the airport is \(airportName).")
    } else {
        print("That airport is not in the airports dictionary.")
    }
    // Prints "The name of the airport is Dublin Airport."
    
    airports["APL"] = "Apple International"
    // "Apple International" is not the real airport for APL, so delete it
    

    사전의 내용을 삭제하는 방법도 변경하는 방법과 동일함

    if let removedValue = airports.removeValue(forKey: "DUB") {
        print("The removed airport's name is \(removedValue).")
    } else {
        print("The airports dictionary does not contain a value for DUB.")
    }
    // Prints "The removed airport's name is Dublin Airport."
    
    airports["APL"] = nil
    // APL has now been removed from the dictionary
    

    사전을 순회하는 방법은 배열에서 인덱스를 사용한 순회와 비슷

    for (airportCode, airportName) in airports {
        print("\(airportCode): \(airportName)")
    }
    // YYZ: Toronto Pearson
    // LHR: London Heathrow
    
    for airportCode in airports.keys {
        print("Airport code: \(airportCode)")
    }
    // Airport code: YYZ
    // Airport code: LHR
    
    for airportName in airports.values {
        print("Airport name: \(airportName)")
    }
    // Airport name: Toronto Pearson
    // Airport name: London Heathrow
    
    let airportCodes = [String](airports.keys)
    // airportCodes is ["YYZ", "LHR"]
    
    let airportNames = [String](airports.values)
    // airportNames is ["Toronto Pearson", "London Heathrow"]