Where who wants to meet someone

[2주차] 1. Generics에 대해 설명하시오 본문

Apple Developer/면접 스터디

[2주차] 1. Generics에 대해 설명하시오

Lust3r 2023. 7. 17. 15:38
728x90
  1. Generics에 대해 설명하시오
  2. MVC 구조에 대해 블록 그림을 그리고, 각 역할과 흐름을 설명하시오
  3. setNeedsLayout과 setNeedsDisplay의 차이에 대해 설명하시오

1. Generics에 대해 설명하시오

Generics | Swift Documentation

 

Documentation

 

docs.swift.org

여러 타입에 대해 작동하는 코드를 작성하고 해당 타입에 대한 요구 사항을 지정.

 

Generic 코드를 사용하면 사용자가 정의하는 요구 사항에 따라 모든 타입에서 작동할 수 있는 유연하고 재사용 가능한 함수 및 타입을 만들 수 있다. 중복을 피하고 의도를 명확히, 추상화된 방식으로 표현하는 코드를 만들 수 있다.

 

Generics는 Swift의 가장 강력한 기능 중 하나. Swift 표준 라이브러리의 대부분은 Generic 코드로 빌드됨.

사실, 인식하지 못하더라도 Language Guide 전체에서 Generic을 사용하고 있음. 가령 Swift의 ArrayDictionary 타입은 모두 generic collection임. Int 값을 포함하는 배열, String 값을 포함하는 배열 또는 실제로 Swift에서 생성할 수 있는 다른 타입의 배열을 만들 수 있음. 마찬가지로 지정된 타입의 값을 저장하는 dictionary를 만들 수 있으며 해당 타입에 제한이 없다.

 

The Problem That Generics Solve

두 개의 Int 값을 교환하는 swapTwoInts(_:_:)라는 메서드가 있다고 해보자.

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

inout을 사용함으로써 실제 a와 b의 값을 변경하고자 하고 있고, 메서드 내의 코드를 통해 서로의 값을 바꿈을 알 수 있다.

 

하지만 Int 값을 바꾸는 것이 아니라 String, Double 등의 다른 타입이라면 같은 내용의(중복된) 메서드를 또 만들어줘야 한다.

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}


func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

모든 타입의 두 값을 교환하는 하나의 함수를 만드는 것이 더 유용하고 훨씬 유연할 것이다.

이럴 때 사용하는 것이 Generic이다.

 

Note
바꾸려는 두 값의 타입이 동일해야 한다. Swift는 type-safe 언어이기 때문에 서로 다른 타입의 변수가 서로 값을 교환하는 것을 허용하지 않는다. 시도하는 경우 컴파일 타임 오류가 발생하게 된다.

 

Generic Functions

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

이전의 코드와 동일하나 달라진 것은 메서드명 뒤에 <T>와 타입으로 T가 주어졌다는 것이다.

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

함수의 generic 버전은 실제 타입 이름 대신 placeholder 타입 이름을 사용한다. placeholder 타입 이름은 T가 무엇이어야 하는지에 대해 아무 말도 하지 않지만, T가 무엇을 나타내든 a와 b는 모두 동일한 T 유형이어야 한다.

T대신 사용할 실제 타입은 swapTwoValues 메서드가 호출될 때마다 결정된다.

 

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3


var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

위의 코드에서 swapTwoValues 메서드는 각각 T를 Int와 String으로 유추한다.

 

Note
Swift에서는 swap(_:_:) 메서드를 표준 라이브러리에서 제공하고 있기 때문에 위와 같은 역할을 하는 메서드가 필요한 경우 자체 구현하지 않고 기본 제공되는 메서드를 사용할 수 있음.

 

Type Parameters

타입 매개변수를 지정하면 이를 사용하여 함수의 파라미터를 정의하거나 함수의 반환 타입 또는 함수 내에서 타입 주석으로 사용할 수 있다.

각각의 경우에 타입 매개변수는 함수가 호출될 때마다 실제 타입으로 대체됨.

쉼표로 구분된 꺾쇠 괄호 안에 여러 타입 매개변수를 작성하여 타입 매개변수를 두 개 이상 제공할 수 있다.

 

Naming Type Parameters

대부분의 경우 타입 매개변수에는 Dictionary의 Key 및 Value, Array의 요소와 같은 설명이 포함된 이름이 있다.

이 이름은 타입 매개변수와 generic 타입 또는 사용되는 함수 사이의 관계에 대해 알려준다. 하지만 그 사이에 의미 있는 관계가 없을 때는 위의 swapTwoValues 메서드에서 T와 같이 T, U 및 V와 같은 단일 문자를 사용하여 이름을 지정하는 것이 전통적.

 

Note
항상 타입 매개변수에 대문자 카멜 케이스 이름을 제공하여 값이 아닌 타입에 대한 placeholder임을 나타내야 한다.

 

Generic Types

Generic 함수 외에도 Swift를 사용하면 고유한 제네릭 타입을 정의할 수 있다. Array 및 Dictionary와 유사한 방식으로 모든 타입에서 작동할 수 있는 사용자 지정 클래스, 구조체, 열거형이다.

 

stack이라는 generic collection 타입을 만드는 방법을 공식문서에서 보여준다. 스택은 정렬된 값 집합으로 배열과 유사하지만 Swift의 배열 타입보다 작업 집합이 더 제한적이다.

Array를 사용하면 모든 위치에서 새 항목을 삽입하고 제거할 수 있지만 stack을 사용하면 끝에만 새 항목을 추가할 수 있고, 끝에서만 항목을 제거할 수 있다.

 

Note
스택의 개념은 navigation 계층에서 viewController를 모델링하기 위해 UINavigationController 클래스에서 사용된다.
pushViewController 메서드를 호출하여 내비게이션 스택에 viewController를 추가하고(push), popViewControllerAnimated 메서드를 호출하여 내비게이션 스택에서 viewController를 제거(pop)한다.
stack은 컬렉션 관리에 엄격한 후입선출 접근 방식이 필요할 때마다 유용한 모델이다.

  1. 3개의 값이 있음
  2. 네 번째 값이 맨 위로 push됨
  3. 이제 스택은 4개의 값을 가지고 있고, 가장 최근의 값이 맨 위에 있음
  4. 스택의 맨 위의 항목이 pop됨
  5. 값을 pop한 후에 스택은 다시 3개의 값을 가짐
struct IntStack {
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

위의 코드는 generic 버전이 아닌 stack의 예제.

struct Stack<Element> {
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

위의 코드는 generic 버전의 stack 예제

generic을 사용했고, placeholder에 대문자 Element를 주어 타입임을 알렸다.

Element는 추후 제공될 타입에 대한 placeholder 이름을 정의하고, 이 타입은 구조체 정의 안 어디에서나 Element로 참조될 수 있다.

이 경우 Element는 다음 세 위치에서 placeholder로 사용된다.

  • 빈 배열로 초기화되는 items라는 프로퍼티를 생성하려면 타입이 Element이어야 함
  • push 메서드에 Item이라는 단일 매개변수가 있음을 지정하려면 Element 타입이어야 함
  • pop 메서드가 반환하는 값이 Element 타입임을 지정

generic 타입이기 때문에 stack은 Array 및 Dictionary와 유사한 방식으로 Swift에서 유효한 타입의 stack을 만드는 데 사용할 수 있다.

 

Stack 뒤에 꺾쇠 괄호와 함께 타입을 작성하여 해당 타입을 담을 Stack 인스턴스를 만들 수 있다.

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// the stack now contains 4 strings

stackOfStrings에서 pop을 하면 맨 위에 있는 값인 "cuatro" 가 제거되고 반환된다.

let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to "cuatro", and the stack now contains 3 strings

 

Extending a Generic Type

Generic 타입을 확장할 때 extension의 정의의 일부로 타입 매개변수 목록을 제공하지 않는다. (?)

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

위의 코드는 Stack 구조체를 확장한 것인데, topItem은 Element 타입으로, 읽기 전용의 연산 프로퍼티이다.

Stack에서처럼 <Element>를 주지 않더라도, Stack의 extension이기 때문에 똑같이 Element를 사용할 수 있는 것. (위의 물음표 부분)

 

이렇게 extension을 통해 topItem 연산 프로퍼티를 만들었으므로 Stack의 최상위 항목을 제거하지 않고 접근하여 확인할 수 있다.

if let topItem = stackOfStrings.topItem {
    print("The top item on the stack is \(topItem).")
}
// Prints "The top item on the stack is tres."

 

Type Constraints

Swift의 Dictionary 타입은 Key값을 사용하는데, 이 때 key는 유일한 값이어야 하기 때문에 hashable이라는 프로토콜을 따라야 한다.

그렇지 않으면 key로 value에 접근했을 때 적절한 value를 얻지 못할 수 있기 때문이다.

이와 같이 특정 타입이 반드시 어떤 프로토콜을 따라야 하는 경우가 있는데, Generic도 이런 경우가 필요할 수 있음.

Generic에서는 특정 클래스를 상속하거나 특정 프로토콜을 따르거나 합성하도록 명시할 수 있다.

 

Type Constraint Syntax

메서드의 타입 매개변수는 콤마로 구분되는데, 이 구분된 매개변수 이름 뒤에 콜론을 붙인 뒤 단일 클래스 또는 프로토콜 제약 조건을 배치하여 Type Constraint를 줄 수 있다. Generic 함수에 대한 타입 제약조건의 기본 구문은 다음 코드와 같다.

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // function body goes here
}

위의 메서드를 보면, someT / someU라는 두 개의 타입 매개변수가 있다. 첫 번째 타입 매개변수인 T는 SomeClass의 서브클래스여야 하는 타입 제약조건이 있고, 두 번째 타입 매개변수인 U는 SomeProtocol을 준수해야 하는 타입 제약조건이 있다.

 

Type Constraints in Action

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

위의 findIndex 메서드는 String 배열을 받아 String을 찾는데 있다면 index 값을, 없다면 nil을 반환(Optional Int)하는 구조이다.

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
    print("The index of llama is \(foundIndex)")
}
// Prints "The index of llama is 2"

그러나 Array에서 값의 Index를 찾는 것은 String에만 유용한 것이 아니다(다른 타입에서도 쓸 수 있다).

앞서의 swapTwoValues 메서드와 마찬가지로 String 부분을 T로 바꿔줌으로써 generic 함수와 동일한 기능으로 만들 수 있다.

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

하지만 위의 코드의 경우 컴파일할 수가 없는데, 그 이유는 value와 valueToFind의 비교 구문에서의 동등성 검사 때문이다.

Swift는 모든 타입에서 == 연산자를 사용할 수 있는 것이 아니다. 가령 복잡한 데이터 모델을 나타내기 위해 자신만의 class나 struct를 생성하는 경우 해당 타입에 대한 "같음"의 의미는 추측할 수 없기 때문이다.

이렇기 때문에 T에 들어올 수 있는 모든 타입에 대해 작동한다고 보장할 수 없어 컴파일타임에 오류가 나는 것이다.

 

Swift 표준 라이브러리는 Equatable이라는 프로토콜을 정의하며, 해당 타입의 두 값을 비교하기 위해 == 연산자와 != 연산자를 쓰려면 이를 준수하는 타입이 필요하다. Swift의 모든 표준 타입은 해당 프로토콜을 자동으로 지원한다.

 

Equatable인 모든 타입은 == 연산자를 지원하기 때문에 findIndex(of:in:) 함수와 함께 안전하게 사용할 수 있다.

이 사실을 표현하기 위해 함수를 정의할 때 타입 매개변수 정의의 일부분으로 Equatable 타입 제약 조건을 작성해주면 된다.

func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

함수 이름 뒤에 꺾쇠 괄호를 넣고, T가 Equatable한 타입이어야 한다는 타입 제약 조건을 작성하고, 타입 매개변수 목록에 T를 사용하면 오류가 나지 않는다.

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

 

Associated Types

프로토콜을 정의할 때 프로토콜 정의의 일부로 하나 이상의 associated type을 선언하는 것이 때때로 유용하다.

associated type은 프로토콜의 일부로 사용되는 타입에 placeholder 이름을 제공한다.

관련 타입에 사용할 실제 타입은 프로토콜이 채택될 때까지 지정되지 않는다.

 

Associated Types in Action

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

Container 프로토콜은 모든 Container가 제공해야 하는 3가지 필수 기능을 정의한다.

  • append(_:)메서드를 사용하여 container에 새 item을 추가할 수 있어야 한다.
  • Int 값을 반환하는 count 프로퍼티를 통해 container의 item의 개수에 접근할 수 있어야 한다.
  • Int형의 index값을 사용하는 subscript를 사용하여 container의 각 item을 검색할 수 있어야 한다.

이 프로토콜은 Container의 항목을 저장하는 방법이나 허용되는 타입을 지정하지 않았다. 프로토콜은 Container로 간주되기 위해 모든 타입이 제공해야 하는 3가지 기능만 지정한다.

이 프로토콜을 준수하는 타입은 이 3가지 요구사항을 충족하는 한 추가적인 기능을 제공할 수 있다.

 

Container 프로토콜을 준수하는 모든 타입은 저장하는 값의 타입을 지정할 수 있어야 한다. 특히 올바른 타입의 item만 Container에 추가되도록 해야 하며 해당 subscript가 반환하는 item의 타입에 대해서도 명확해야 한다.

 

이러한 요구 사항을 정의하기 위해 Container 프로토콜은 특정 Container에 대한 타입이 무엇인지 모른 채 Container가 보유할 요소의 타입을 참조하는 방법이 필요하다. 이 프로토콜은 append(_:) 메서드에 전달되는 모든 값이 Container의 요소 타입과 동일해야하고, Container의 subscript가 반환하는 값이 Container의 요소 타입과 동일한 유형이어야 한다고 지정해야 한다.

 

이를 위해서 Container 프로토콜은 associatedtype 이라는 타입을 선언. 프로토콜은 Item이 무엇인지 정의하지 않는다.

해당 정보는 모든 준수 타입이 제공하도록 남겨진다. 그럼에도 불구하고 Item alias는 Container에 있는 item의 타입을 참조하는 방법을 제공하고 모든 Container의 예상 동작이 적용되도록 하기 위해 append(_:) 메서드 및 subscript와 함께 사용할 타입을 정의하는 방법을 제공한다.

 

struct IntStack: Container {
    // original IntStack implementation
    var items: [Int] = []
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // conformance to the Container protocol
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStack 타입은 Container 프로토콜의 3가지 요구사항을 모두 구현하고, 이러한 요구사항들을 충족하기 위해 기존 기능 일부를 래핑한다.

 

또한 IntStack은 이 Container 구현에 대해 사용할 적절한 Item이 Int 타입임을 지정한다. typealias Item = Int.

Swift의 타입 추론 덕분에 실제로 IntStack에서 구체적인 Int Item을 선언할 필요가 없다. Container 프로토콜의 모든 요구 사항을 준수하기 때문에 Swift는 단순히 append(_:) 메서드의 item 매개변수 타입과 subscript 반환 타입을 보고 사용할 적절한 Item을 유추할 수 있기 때문이다. 실제로 위의 코드에서 typealias Item = Int 문구를 제거해도 어떤 타입이 들어와야 할 지 명확하기 때문에 오류가 나지 않는다.

 

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items: [Element] = []
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

또는 위의 코드처럼 generic을 사용하여 파라미터 타입과 subscript의 반환 타입으로 사용할 수 있다.

 

Extending an Existing Type to Specify an Associated Type

Extension을 사용하여 Adding Protocol Conformance with an Extension에 설명된 대로 기존 타입을 확장하여 프로토콜에 적합성을 추가할 수 있다.

여기에는 associated type이 있는 프로토콜이 포함된다. Swift의 Array 타입은 이미 append(_:) 메서드, count 속성 및 요소를 검색하기 위한 Int 인덱스가 있는 subscript를 제공한다. 이 3가지 기능은 Container 프로토콜의 요구사항과 일치한다.

이는 Array가 프로토콜을 채택한다고 선언함으로써 Container 프로토콜을 준수하도록 Array를 확장할 수 있음을 의미.

Declaring Protocol Adoption with an Extension에 설명된 대로 빈 확장을 사용하여 이 작업을 수행한다.

extension Array: Container {}

 

Adding Constraints to an Associated Type

해당 제약 조건을 충족하도록 요구하기 위해 프로토콜의 associated type에 타입 제약 조건을 추가할 수 있다.

예를 들어 다음 코드는 Container의 항목이 Equatable해야 하는 Container 버전을 정의.

protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

 

Using a Protocol in Its Associated Type's Constraints

프로토콜은 자체 요구 사항의 일부로 나타날 수 있다. 예를 들어 다음 코드는 suffix(_:) 메서드의 요구 사항을 추가하여 Container 프로토콜을 개선하는 프로토콜. suffix(_:) 메서드는 Container 끝에서 주어진 수의 요소를 반환하여 Suffix 타입의 인스턴스에 저장

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30
extension IntStack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack<Int> {
        var result = Stack<Int>()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack<Int>.
}

 

Generic Where Clauses

Type constraints에 설명된 것처럼 타입 제약을 사용하면 generic 함수, subscript 또는 타입과 관련된 매개변수에 대한 요구사항을 정의할 수 있음.

 

associated type에 대한 요구사항을 정의하는 것도 유용할 수 있음. generic where절을 정의하여 이를 수행함.

generic where절을 사용하면 associated type이 특정 프로토콜을 준수해야 하거나 특정 타입 매개변수와 associated type이 동일해야 한다고 요구할 수 있다.

generic where절은 where 키워드로 시작하고 그 뒤에 관련 타입에 대한 제약 조건이나 타입과 associated type간의 동등 관계가 온다. 타입이나 함수 바디의 여는 중괄호 바로 앞에 generic where절을 작성한다.

 

아래 예제에서는 allItemsMatch라는 generic 함수를 정의한다. 이 함수는 두 개의 Container 인스턴스에 동일한 항목이 동일한 순서로 포함되어 있는지 확인한다. 이 함수는 모든 항목이 일치하면 bool값 true를 반환하고, 일치하지 않으면 false 값을 반환한다.

 

검사할 두 Container는 동일한 타입일 필요는 없지만 동일한 타입의 Item을 보유해야 한다. 이 요구사항은 타입 제약조건과 generic where절의 조합을 통해 표현된다.

func allItemsMatch<C1: Container, C2: Container>
        (_ someContainer: C1, _ anotherContainer: C2) -> Bool
        where C1.Item == C2.Item, C1.Item: Equatable {


    // Check that both containers contain the same number of items.
    if someContainer.count != anotherContainer.count {
        return false
    }


    // Check each pair of items to see if they're equivalent.
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }


    // All items match, so return true.
    return true
}

someContainer는 C1, anotherContainer는 C2 타입.

  • C1은 반드시 Container 프로토콜을 준수해야 한다.
  • C2도 또한 Container 프로토콜을 준수해야 한다.
  • C1의 Item은 C2의 Item과 같아야 한다.
  • C1의 Item은 Equatable 프로토콜을 준수해야 한다.

1~2번째 요구사항은 함수의 타입 매개변수 목록에서 정의되고, 3~4번째 요구사항은 함수의 generic where절에서 정의된다.

이 요구사항들은 의미한다.

  • someContainer는 C1 타입의 container이다
  • anotherContainer는 C2 타입의 container이다
  • someContainer와 anotherContainer는 같은 타입의 item을 포함한다
  • someContainer의 item은 not equal 연산자(!=)로 그것들이 서로 다른지 확인할 수 있다.

3~4번째 요구사항이 결합되어 다른 컨테이너의 항목도 != 연산자로 확인할 수 있음.
동일한 컨테이너의 항목과 정확히 동일한 유형이기 때문.

 

이러한 요구 사항을 통해 allItemsMatch(_:_:) 함수는 컨테이너 유형이 다른 경우에도 두 컨테이너를 비교할 수 있음.

 

위의 함수는 두 컨테이너에 동일한 수의 항목이 포함되어 있는지 확인하는 것으로 시작한다. 다른 수의 항목을 포함하는 경우 일치시킬 수 있는 방법이 없으며 함수는 false를 반환.

 

이 검사를 수행한 다음, for-in 루프와 반개방 범위 연산자(..<)를 사용하여 someContainer의 모든 항목을 반복. 각 항목에 대해 함수는 someContainer의 항목이 anotherContainer의 해당 항목과 같지 않은지 확인한다.

두 항목이 같지 않으면 두 컨테이너가 일치하지 않는 것이기에 함수는 false를 반환

불일치를 찾지 않고 반복이 끝나면 두 컨테이너가 일치하고 함수는 true를 반환.

 

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
	print("All items match.")
} else {
	print("Not all items match.")
}
// Prints "All items match."

위의 코드에서는 Stack과 array를 비교하는데, 서로 다른 타입이지만 Container 프로토콜을 준수하고, 동일한 타입(String)의 값을 포함

 

Extensions with a Generic Where Clause

extension의 일부로 generic where절을 사용할 수도 있다. 아래 예제 코드는 일반 Stack 구조를 확장하여 isTop(_:) 메서드를 추가

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
    	guard let topItem = items.last else {
        	return false
        }
        return topItem == item
    }
}

해당 메서드는 먼저 stack이 비어있지 않은지 확인한 다음(guard let) 주어진 항목을 스택의 최상위 항목과 비교한다(topItem == item)

where절이 없이 이 작업을 수행하려고 하면 문제가 발생.

isTop 메서드의 구현은 == 연산자를 사용하지만 Stack의 정의는 해당 항목이 Equatable할 것을 요구하지 않기 때문에 연산자가 컴파일 타임 오류를 발생시키는 것.

where 절을 사용하면 extension에 새로운 요구사항을 추가할 수 있으므로 Stack의 item이 Equatable할 때만 extension이 isTop 메서드를 추가한다.

 

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// Prints "Top element is tres."
struct NotEquatable { }

var notEquatableStack = Stack<NotEquatable>()

let notEquatableValue = NotEquatable()

notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error

 


다형성이란?

https://www.codestates.com/blog/content/객체-지향-프로그래밍-특징

 

객체 지향 프로그래밍의 4가지 특징ㅣ추상화, 상속, 다형성, 캡슐화 -

객체 지향 프로그래밍은 객체의 유기적인 협력과 결합으로 파악하고자 하는 컴퓨터 프로그래밍의 패러다임을 의미합니다. 객체 지향 프로그래밍의 기본적인 개념과 그 설계를 바르게 하기 위

www.codestates.com

객체지향 프로그래밍은 프로그램을 보다 유연하고 변경이 용이하게 하고자 객체 지향적으로 프로그램을 설계하는 방식.

 

이러한 객체지향 프로그래밍의 4가지 특징으로는 다음이 있음.

1. 추상화

2. 상속

3. 다형성

4. 캡슐화

 

이 중 다형성은 특정한 무언가가 되는 것이 아니라 이것도 될 수 있고 저것도 될 수 있는 것.

무엇을 먹는다고 했을 때 그 먹는 것이 짜장면이 될 수도 있고 떡볶이가 될 수도 있다.

 

가령 moveForward() 라는 메서드를 사용해야 하면, car와 motorBike 각각에 대한 기능을 Driver에 줘야하는데, 다형성을 사용하면
car와 motorBike의 상위 클래스인 Vehicle로 묶어 하나의 기능으로 다 대응이 가능하게 됨.

이것이 다형성이고, Generic과 유사함.

상속이란?

위에서의 moveForward()라는 메서드가 Vehicle에 있다면, 이 Vehicle을 상속한 car와 motorBike에서는 똑같이 moveForward() 메서드를 사용할 수 있고 override를 통해 각 이동수단만의 특성을 나타낼 수도 있다.