<jupyter_start><jupyter_text>Copyright 2019 The TensorFlow Authors. [Licensed under the Apache License, Version 2.0](scrollTo=Afd8bu4xJOgh).<jupyter_code>// #@title Licensed under the Apache License, Version 2.0 (the "License"); { display-mode: "form" }
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.<jupyter_output><empty_output><jupyter_text>View on TensorFlow.org  Run in Google Colab  GitHub에서 소스 보기  프로토콜 지향 프로그래밍 및 제네릭이 튜토리얼에서는 프로토콜 지향 프로그래밍과 일상적인 예제에서 제네릭과 함께 사용되는 방법에 대한 다양한 예제를 살펴 봅니다.  프로토콜상속은 프로그램의 여러 구성 요소 간에 코드를 공유할 수 있도록 프로그래밍 언어로 코드를 구성하는 강력한 방법입니다.Swift에는 상속을 표현하는 여러 가지 방법이 있습니다. 다른 언어를 접해봤다면 이러한 방법 중 하나인 클래스 상속에 이미 익숙할 것입니다. 그러나 Swift에는 프로토콜이라는 방법이 있습니다.이 튜토리얼에서는 다른 요소의 균형을 맞춰 유사한 목표를 달성할 수 있는 하위 클래스화의 대안인 프로토콜을 살펴봅니다. Swift에서 프로토콜은 여러 추상 멤버를 포함합니다. 클래스, 구조체 및 열거형은 여러 프로토콜을 준수할 수 있으며, 준수 관계는 소급하여 설정할 수 있습니다. 이 모든 것은 하위 클래스화를 사용하여 Swift에서 쉽게 표현할 수 없는 일부 설계를 가능하게 합니다. 프로토콜(확장 및 프로토콜 제약)의 사용과 프로토콜의 한계를 지원하는 관용구를 살펴보겠습니다.  Swift 💖의 값 형식!참조 의미 체계가 있는 클래스 외에도 Swift는 값으로 전달되는 열거형 및 구조체를 지원합니다. 열거형과 구조체는 클래스에서 제공하는 많은 기능을 지원합니다. 한 번 살펴보겠습니다!먼저 열거형이 클래스와 어떻게 유사한지 살펴보겠습니다.<jupyter_code>enum Color: String {
    case red = "red"
    case green = "green"
    case blue = "blue"
    // A computed property. Note that enums cannot contain stored properties.
    var hint: String {
        switch self {
            case .red:
                return "Roses are this color."
            case .green:
                return "Grass is this color."
            case .blue:
                return "The ocean is this color."
        }
    }
    
    // An initializer like for classes.
    init?(color: String) {
        switch color {
        case "red":
            self = .red
        case "green":
            self = .green
        case "blue":
            self = .blue
        default:
            return nil
        }
    }
}

// Can extend the enum as well!
extension Color {
    // A function.
    func hintFunc() -> String {
        return self.hint
    }
}

let c = Color.red
print("Give me a hint for c: \(c.hintFunc())")

let invalidColor = Color(color: "orange")
print("is invalidColor nil: \(invalidColor == nil)")<jupyter_output><empty_output><jupyter_text>이제 구조체를 살펴보겠습니다. 구조체는 상속할 수 없지만 대신 프로토콜을 사용할 수 있습니다.<jupyter_code>struct FastCar {
    // Can have variables and constants as stored properties.
    var color: Color
    let horsePower: Int
    // Can have computed properties.
    var watts: Float {
       return Float(horsePower) * 745.7
    }
    // Can have lazy variables like in classes!
    lazy var titleCaseColorString: String = {
        let colorString = color.rawValue
        return colorString.prefix(1).uppercased() + 
               colorString.lowercased().dropFirst()
    }()
    // A function.
    func description() -> String {
        return "This is a \(color) car with \(horsePower) horse power!"
    }
    // Can create a variety of initializers.
    init(color: Color, horsePower: Int) {
        self.color = color
        self.horsePower = horsePower
    }
    // Can define extra initializers other than the default one.
    init?(color: String, horsePower: Int) {
        guard let enumColor = Color(color: color) else {
            return nil
        }
        self.color = enumColor
        self.horsePower = horsePower
    }
}

var car = FastCar(color: .red, horsePower: 250)
print(car.description())
print("Horse power in watts: \(car.watts)")
print(car.titleCaseColorString)<jupyter_output><empty_output><jupyter_text>마지막으로 클래스와 달리 값 형식이 어떻게 전달되는지 살펴보겠습니다.<jupyter_code>// Notice we have no problem modifying a constant class with 
// variable properties.
class A {
  var a = "a"
}

func foo(_ a: A) {
  a.a = "foo"
}
let a = A()
print(a.a)
foo(a)
print(a.a)

/* 
Uncomment the following code to see how an error is thrown.
Structs are implicitly passed by value, so we cannot modify it.
> "error: cannot assign to property: 'car' is a 'let' constant"
*/

// func modify(car: FastCar, toColor color: Color) -> Void {
//   car.color = color
// }

// car = FastCar(color: .red, horsePower: 250)
// print(car.description())
// modify(car: &car, toColor: .blue)
// print(car.description())<jupyter_output><empty_output><jupyter_text>프로토콜 사용하기여러 자동차에 대한 프로토콜을 만드는 것으로 시작하겠습니다.<jupyter_code>protocol Car {
    var color: Color { get set }
    var price: Int { get }
    func turnOn()
    mutating func drive()
}

protocol Electric {
    mutating func recharge()
    // percentage of the battery level, 0-100%.
    var batteryLevel: Int { get set }
}

protocol Gas {
    mutating func refill()
    // # of liters the car is holding, varies b/w models.
    var gasLevelLiters: Int { get set }
}<jupyter_output><empty_output><jupyter_text>객체 지향 세계(다중 상속 없음)에서는 `Electric` 및 `Gas` 추상 클래스를 만든 다음 클래스 상속을 사용하여 `Car`에서 둘 다 상속한 다음 특정 자동차 모델을 기본 클래스로 만들었을 것입니다. 그러나 여기서는 둘 다 **zero** 결합을 가진 완전히 별개의 프로토콜입니다! 이를 통해 더욱 융통성 있게 전체 시스템을 설계할 수 있습니다.Tesla를 정의해 보겠습니다.<jupyter_code>struct TeslaModelS: Car, Electric {
    var color: Color // Needs to be a var since `Car` has a getter and setter.
    let price: Int
    var batteryLevel: Int
    
    func turnOn() {
        print("Starting all systems!")
    }

    mutating func drive() {
        print("Self driving engaged!")
        batteryLevel -= 8
    }

    mutating func recharge() {
        print("Recharging the battery...")
        batteryLevel = 100
    }
}

var tesla = TeslaModelS(color: .red, price: 110000, batteryLevel: 100)<jupyter_output><empty_output><jupyter_text>이것은 `Car` 및 `Electric` 프로토콜을 모두 준수하는 새로운 구조체 `TeslaModelS`를 지정합니다.이제 가스 구동 자동차를 정의해 보겠습니다.<jupyter_code>struct Mustang: Car, Gas{
    var color: Color
    let price: Int
    var gasLevelLiters: Int
    
    func turnOn() {
        print("Starting all systems!")
    }
    
    mutating func drive() {
        print("Time to drive!")
        gasLevelLiters -= 1
    }
    
    mutating func refill() {
        print("Filling the tank...")
        gasLevelLiters = 25
    }
}

var mustang = Mustang(color: .red, price: 30000, gasLevelLiters: 25)<jupyter_output><empty_output><jupyter_text>기본 동작으로 프로토콜 확장하기예제에서 알 수 있는 것은 중복성이 있다는 것입니다. 전기 자동차를 충전할 때마다 배터리 비율 수준을 100으로 설정해야 합니다. 모든 전기 자동차의 최대 용량은 100%이지만, 가스 자동차는 가스 탱크 용량에 따라 다르기 때문에 전기 자동차의 경우 기본 수준을 100으로 설정할 수 있습니다.이것이 Swift의 확장이 유용할 수 있는 부분입니다.<jupyter_code>extension Electric {
    mutating func recharge() {
        print("Recharging the battery...")
        batteryLevel = 100
    }
}<jupyter_output><empty_output><jupyter_text>이제부터 생성되는 새로운 전기 자동차는 모두 충전할 때 배터리를 100으로 설정하게 됩니다. 고유한 기본 동작으로 클래스, 구조체 및 열거형을 데코레이팅할 수 있었던 것입니다. ![프로토콜 코믹](https://koenig-media.raywenderlich.com/uploads/2015/06/protocols-extend.png)만화를 제공한 [Ray Wenderlich](https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3)에게 감사드립니다! 그러나 한 가지 주의해야 할 사항은 다음과 같습니다. 첫 번째 구현에서 `foo()`를 `A`의 기본 구현으로 정의하지만, 프로토콜에서는 필수로 지정하지 않습니다. 따라서 `a.foo()`를 불러내면 '`A default`'가 출력됩니다.<jupyter_code>protocol Default {}

extension Default {
    func foo() { print("A default")}
}

struct DefaultStruct: Default {
    func foo() {
        print("Inst")
    }
}

let a: Default = DefaultStruct()
a.foo()<jupyter_output><empty_output><jupyter_text>그러나 `A`에 `foo()`를 요구하면 '`Inst`'를 얻습니다.<jupyter_code>protocol Default {
    func foo()
}

extension Default {
    func foo() { 
        print("A default")
    }
}

struct DefaultStruct: Default {
    func foo() {
        print("Inst")
    }
}

let a: Default = DefaultStruct()
a.foo()<jupyter_output><empty_output><jupyter_text>이는 첫 번째 예제의 정적 디스패치와 Swift의 프로토콜에 대한 두 번째 예제의 정적 디스패치 간의 차이로 인해 발생합니다. 자세한 내용은 이 [매체 게시물](https://medium.com/@PavloShadov/https-medium-com-pavloshadov-swift-protocols-magic-of-dynamic-static-methods-dispatches-dfe0e0c85509)을 참조하세요.  기본 동작 재정의하기그러나 원하는 경우 기본 동작을 재정의할 수 있습니다. 한 가지 중요한 점은 이것이 [동적 디스패치를 지원하지 않는다는 것](https://stackoverflow.com/questions/44703205/swift-protocol-extension-method-is-called-instead-of-method-implemented-in-subcl)입니다.예전 버전의 전기 자동차가 있어 배터리 상태가 90%로 감소했다고 가정해 보겠습니다.<jupyter_code>struct OldElectric: Car, Electric {
    var color: Color // Needs to be a var since `Car` has a getter and setter.
    let price: Int
    var batteryLevel: Int
    
    func turnOn() {
        print("Starting all systems!")
    }
    
    mutating func drive() {
        print("Self driving engaged!")
        batteryLevel -= 8
    }
    
    mutating func reCharge() {
        print("Recharging the battery...")
        batteryLevel = 90
    }
}<jupyter_output><empty_output><jupyter_text>프로토콜의 표준 라이브러리 사용이제 Swift에서 프로토콜이 동작하는 방식에 대해 알아봤으니 표준 라이브러리 프로토콜을 사용하는 몇 가지 일반적인 예를 살펴보겠습니다. 표준 라이브러리 확장하기Swift에 이미 존재하는 형식에 추가 기능을 더하는 방법을 살펴보겠습니다. Swift의 형식은 내장되어 있지 않지만 구조체로서 표준 라이브러리의 일부이므로 쉽게 수행할 수 있습니다.배열이 정렬되어 있는지 확인하면서 요소 배열에 대해 이진 검색을 시도해 보겠습니다.<jupyter_code>extension Collection where Element: Comparable {
    // Verify that a `Collection` is sorted.
    func isSorted(_ order: (Element, Element) -> Bool) -> Bool {
        var i = index(startIndex, offsetBy: 1)
        
        while i < endIndex {
            // The longer way of calling a binary function like `<(_:_:)`, 
            // `<=(_:_:)`, `==(_:_:)`, etc.
            guard order(self[index(i, offsetBy: -1)], self[i]) else {
                return false
            }
            i = index(after: i)
        }
        return true
    }
    
    // Perform binary search on a `Collection`, verifying it is sorted.
    func binarySearch(_ element: Element) -> Index? {
        guard self.isSorted(<=) else {
            return nil
        }
        
        var low = startIndex
        var high = endIndex
        
        while low <= high {
            let mid = index(low, offsetBy: distance(from: low, to: high)/2)

            if self[mid] == element {
                return mid
            } else if self[mid] < element {
                low = index(after: mid)
            } else {
                high = index(mid, offsetBy: -1)
            }
        }
        
        return nil
    }
}

print([2, 2, 5, 7, 11, 13, 17].binarySearch(5)!)
print(["a", "b", "c", "d"].binarySearch("b")!)
print([1.1, 2.2, 3.3, 4.4, 5.5].binarySearch(3.3)!)<jupyter_output><empty_output><jupyter_text>이를 위해 *'요소를 비파괴적으로 여러 번 통과할 수 있고 인덱싱된 아래 첨자로 액세스할 수 있는 시퀀스'*를 정의하는 [`Collection`](https://developer.apple.com/documentation/swift/collection) 프로토콜을 확장합니다. 배열은 대괄호 표기법을 사용하여 인덱싱할 수 있으므로 이 프로토콜을 확장하고자 합니다.마찬가지로 이 유틸리티 함수는 요소를 비교할 수 있는 배열에만 추가하려고 합니다. 이것이 `where Element: Comparable`이 있는 이유입니다.`where` 절은 Swift의 형식 시스템의 일부입니다. 이 내용은 다음에 자세히 다루겠지만, 간단히 말해서 작성하는 확장에 추가 요구 사항을 더할 수 있습니다. 예를 들어, 프로토콜 구현 형식을 요구하거나, 두 개 형식이 동일하도록 요구하거나, 클래스가 특정 슈퍼클래스를 갖도록 요구할 수 있습니다.[`Element`](https://developer.apple.com/documentation/swift/sequence/2908099-element)는 `Collection` 준수 형식에 있는 요소의 관련 형식입니다. `Element`는 [`Sequence`](https://developer.apple.com/documentation/swift/sequence) 프로토콜 내에서 정의되지만 `Collection`은 `Sequence`에서 상속되므로 `Element` 관련 형식을 상속합니다.[`Comparable`](https://developer.apple.com/documentation/swift/comparable)은 *'관계 연산자 `=` 및 `>`를 사용하여 비교할 수 있는 형식'*을 정의하는 프로토콜입니다. 정렬된 `Collection`에 대해 이진 검색을 수행하고 있으므로 이것은 물론 사실이어야 합니다. 그렇지 않으면 이진 검색에서 왼쪽 또는 오른쪽으로 재귀/반복할지 여부를 알 수 없습니다.구현에 대한 추가 정보로 사용된 `index(_:offsetBy:)` 함수에 대한 자세한 내용은 다음 [문서](https://developer.apple.com/documentation/swift/string/1786175-index)를 참조하세요.  제네릭 + 프로토콜 = 💥제네릭과 프로토콜은 중복 코드를 방지하기 위해 올바르게 사용된다면 강력한 도구가 될 수 있습니다.먼저 Colab 책의 끝에 있는 제네릭에 대해 간략히 설명하는 다른 튜토리얼 [A Swift Tour](https://colab.research.google.com/github/tensorflow/swift/blob/master/docs/site/tutorials/a_swift_tour.ipynb)를 살펴보세요.제네릭에 대한 일반적인 이해가 있다고 가정하고 몇 가지 고급 용도를 빠르게 살펴보겠습니다.단일 형식에 여러 프로토콜을 준수하는 형식과 같은 여러 요구 사항이 있는 경우 다양한 옵션을 사용할 수 있습니다.<jupyter_code>typealias ComparableReal = Comparable & FloatingPoint

func foo1<T: ComparableReal>(a: T, b: T) -> Bool {
    return a > b
}

func foo2<T: Comparable & FloatingPoint>(a: T, b: T) -> Bool {
    return a > b
}

func foo3<T>(a: T, b: T) -> Bool where T: ComparableReal {
    return a > b
}

func foo4<T>(a: T, b: T) -> Bool where T: Comparable & FloatingPoint {
    return a > b
}

func foo5<T: FloatingPoint>(a: T, b: T) -> Bool where T: Comparable {
    return a > b
}

print(foo1(a: 1, b: 2))
print(foo2(a: 1, b: 2))
print(foo3(a: 1, b: 2))
print(foo4(a: 1, b: 2))
print(foo5(a: 1, b: 2))<jupyter_output><empty_output><jupyter_text>상단에 있는 `typealias`의 사용에 주목하세요. 이렇게 하면 기존 형식의 명명된 별칭이 프로그램에 추가됩니다. 형식 별칭이 선언된 후 프로그램의 모든 위치에서 기존 형식 대신 별칭이 지정된 이름을 사용할 수 있습니다. 형식 별칭은 새 형식을 생성하지 않습니다. 단순히 이름이 기존 형식을 참조하도록 허용합니다.이제 프로토콜과 제네릭을 함께 사용할 수 있는 방법을 살펴보겠습니다.컴퓨터 매장에서 판매하는 모든 노트북에 대해 다음과 같은 요구 사항을 기준으로 매장 뒤에서 노트북을 정리하는 방법을 결정해야 한다고 가정해 보겠습니다.<jupyter_code>enum Box {
    case small
    case medium
    case large
}

enum Mass {
    case light
    case medium
    case heavy
}

// Note: `CustomStringConvertible` protocol lets us pretty-print a `Laptop`.
struct Laptop: CustomStringConvertible {
    var name: String
    var box: Box
    var mass: Mass
    
    var description: String {
        return "(\(self.name) \(self.box) \(self.mass))"
    }
}<jupyter_output><empty_output><jupyter_text>그러나 선반에는 무게 제한이 있으므로 `Laptop`을 질량별로 그룹화해야 하는 새로운 요구 사항이 있습니다.<jupyter_code>func filtering(_ laptops: [Laptop], by mass: Mass) -> [Laptop] {
    return laptops.filter { $0.mass == mass }
}

let laptops: [Laptop] = [
    Laptop(name: "a", box: .small, mass: .light),
    Laptop(name: "b", box: .large, mass: .medium),
    Laptop(name: "c", box: .medium, mass: .heavy),
    Laptop(name: "d", box: .large, mass: .light)
]

let filteredLaptops = filtering(laptops, by: .light)
print(filteredLaptops)<jupyter_output><empty_output><jupyter_text>그런데 `Mass`가 아닌 다른 것으로 필터링하려면 어떻게 해야 할까요?한 가지 옵션은 다음을 수행하는 것입니다.<jupyter_code>// Define a protocol which will act as our comparator.
protocol DeviceFilterPredicate {
    associatedtype Device
    func shouldKeep(_ item: Device) -> Bool
}

// Define the structs we will use for passing into our filtering function.
struct BoxFilter: DeviceFilterPredicate {
    typealias Device = Laptop
    var box: Box 
    
    func shouldKeep(_ item: Laptop) -> Bool {
        return item.box == box
    }
}

struct MassFilter: DeviceFilterPredicate {
    typealias Device = Laptop  
    var mass: Mass
    
    func shouldKeep(_ item: Laptop) -> Bool {
        return item.mass == mass
    }
}

// Make sure our filter conforms to `DeviceFilterPredicate` and that we are 
// filtering `Laptop`s.
func filtering<F: DeviceFilterPredicate>(
    _ laptops: [Laptop], 
    by filter: F
) -> [Laptop] where Laptop == F.Device {
    return laptops.filter { filter.shouldKeep($0) }
}

// Let's test the function out!
print(filtering(laptops, by: BoxFilter(box: .large)))
print(filtering(laptops, by: MassFilter(mass: .heavy)))<jupyter_output><empty_output><jupyter_text>좋습니다! 이제 모든 노트북 제약 조건을 기준으로 필터링할 수 있습니다. 그러나 지금은 `Laptop`만 필터링할 수 있습니다.상자에 담겨 있고 질량이 있는 모든 것을 필터링할 수 있다면 어떨까요? 이 노트북이 보관된 창고는 노트북과는 고객 기반이 다른 서버를 보관하는 데도 사용될 수 있습니다.<jupyter_code>// Define 2 new protocols so we can filter anything in a box and which has mass.
protocol Weighable {
    var mass: Mass { get }
}

protocol Boxed {
    var box: Box { get }
}

// Define the new Laptop and Server struct which have mass and a box.
struct Laptop: CustomStringConvertible, Boxed, Weighable {
    var name: String
    var box: Box
    var mass: Mass
    
    var description: String {
        return "(\(self.name) \(self.box) \(self.mass))"
    }
}

struct Server: CustomStringConvertible, Boxed, Weighable {
    var isWorking: Bool
    var name: String
    let box: Box
    let mass: Mass

    var description: String {
        if isWorking {
            return "(working \(self.name) \(self.box) \(self.mass))"
        } else {
            return "(notWorking \(self.name) \(self.box) \(self.mass))"
        }
    }
}

// Define the structs we will use for passing into our filtering function.
struct BoxFilter<T: Boxed>: DeviceFilterPredicate {
    var box: Box 
  
    func shouldKeep(_ item: T) -> Bool {
        return item.box == box
    }
}

struct MassFilter<T: Weighable>: DeviceFilterPredicate {
    var mass: Mass
    
    func shouldKeep(_ item: T) -> Bool {
        return item.mass == mass
    }
}

// Define the new filter function.
func filtering<F: DeviceFilterPredicate, T>(
    _ elements: [T], 
    by filter: F
) -> [T] where T == F.Device {
    return elements.filter { filter.shouldKeep($0) }
}


// Let's test the function out!
let servers = [
    Server(isWorking: true, name: "serverA", box: .small, mass: .heavy),
    Server(isWorking: false, name: "serverB", box: .medium, mass: .medium),
    Server(isWorking: true, name: "serverC", box: .large, mass: .light),
    Server(isWorking: false, name: "serverD", box: .medium, mass: .light),
    Server(isWorking: true, name: "serverE", box: .small, mass: .heavy)
]

let products = [
    Laptop(name: "a", box: .small, mass: .light),
    Laptop(name: "b", box: .large, mass: .medium),
    Laptop(name: "c", box: .medium, mass: .heavy),
    Laptop(name: "d", box: .large, mass: .light)
]

print(filtering(servers, by: BoxFilter(box: .small)))
print(filtering(servers, by: MassFilter(mass: .medium)))

print(filtering(products, by: BoxFilter(box: .small)))
print(filtering(products, by: MassFilter(mass: .medium)))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11202663807212597, 'fraction_numerical': 0.009142728144929172, 'mean_word_length': 2.9177537032942737, 'pattern_counts': {'":': 3, '<': 84, '<?xml version=': 0, '>': 104, 'https://': 11, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '404818', 'license': 'Apache-2.0', 'n_tokens_mistral': 8416, 'n_tokens_neox': 9576, 'n_words': 2078, 'path': 'site/ko/swift/tutorials/protocol_oriented_generics.ipynb', 'repo_name': 'NarimaneHennouni/docs-l10n', 'size': '17719'}