Protocols 02

Swift 문서는 여기!

Adding Protocol Conformance with an Extension

프로토콜을 채택하는 것은 extension에서 할 수도 있다. extension에서 프로토콜을 준수하면 인스턴스에서 사용 가능.

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

Conditionally Conforming to a Protocol

  • Generic type은 타입의 Generic 파라메터가 프로토콜을 준수하는 경우와 같은 특정 조건에서만 프로토콜의 요구사항을 만족시킬 수 있음
  • where 절을 추가해 프로토콜의 제약 조건을 작성한다
extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// Prints "[A 6-sided dice, A 12-sided dice]"
  • 위 코드에서는 Array의 Element가 TextRepresentable을 준수할 때 Array도 TextRepresentable 프로토콜을 준수하도록 한다.

Declaring Protocol Adoption with an Extension

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}
  • 어떤 유형이 프로토콜을 준수하고 있지만 아직 프로토콜을 채택하지 않은 경우 extension Hamster: TextRepresentable {} 처럼 빈 extension을 사용해 프로토콜을 채택할 수 있다.

Adopting a Protocol Using a Synthesized Implementation

  • Swift는 Equatable, Hashable, Comparable과 같은 프로토콜을 제공한다. 따로 코드를 구현하지 않아도 해당 프로토콜들을 채택하면 기능을 사용할 수 있다.
  • Equatable: 채택시 == 또는 !=으로 비교 가능
    • Equatable 프로토콜을 준수하는 stored property만 있는 struct
    • Equatable 프로토콜을 준수하는 associated type만 있는 Enum
    • associated type이 없는 Enum
  • Hashable: 채택시 hash(into:) 구현할 필요 없음
    • Hashable 프로토콜을 준수하는 Stored property만 있는 struct
    • Hashable 프로토콜을 준수하는 associated type만 있는 Enum
    • associated type이 없는 Enum
  • Comparable: <=, >, >=
    • raw vlaue가 없는 enum
    • associated type이 있는 경우에는 그 type이 Comparable을 준수해얗 ㅏㅁ ```swift struct Vector3D: Equatable { var x = 0.0, y = 0.0, z = 0.0 // Equatable 프로토콜을 준수하는 stored property만 있는 struct }

let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) if twoThreeFour == anotherTwoThreeFour { print(“These two vectors are also equivalent.”) } // Prints “These two vectors are also equivalent.”

enum SkillLevel: Comparable { case beginner case intermediate case expert(stars: Int) } var levels = [SkillLevel.intermediate, SkillLevel.beginner, SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)] for level in levels.sorted() { print(level) } // Prints “beginner” // Prints “intermediate” // Prints “expert(stars: 3)” // Prints “expert(stars: 5)”


## Collections of Protocol Types
```swift
let things: [TextRepresentable] = [game, d12, simonTheHamster]

for thing in things {
    print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
  • Array와 같은 Collection에서 Type의 유형을 protocol 로 지정할 수 있다.
    • protocol을 준수하는 어떤 타입이든 저장이 가능하고, 프로토콜에서 정의를 요구하는 값들을 사용할 수 있다.

Protocol Inheritance

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}
  • 프로토콜은 프로토콜을 상속할 수 있다. Class의 상속과 동일한 폼으로 작성하면 되고 쉼표를 넣어 상속받을 프로토콜을 여러개 나열할 수 있다.
    • InheritingProtocol을 채택하면 해당 프로토콜이 상속받은 SomeProtocol과 AnotherProtocol까지 모두 준수할 수 있도록 코드를 구현해야 한다.

Class-Only Protocols

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}
  • Class만 채택할 수 있는 프로토콜을 생성할 수도 있다. AnyObject를 프로토콜의 상속 목록에 추가한다.

Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics. For more about reference and value semantics, see Structures and Enumerations Are Value Types and Classes Are Reference Types.

Protocol Composition

class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}

let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
  • 프로토콜 여러개를 동시에 만족하는 조건을 걸어 사용하기.
    • &로 묶어서 여러개의 프로토콜을 준수하는 것만 체크할 수 있다.
    • 프로토콜과 Super Class를 묶을 수도 있다. (beginConcert 메서드처럼)

Checking for Protocol Conformance

  • is 또는 as를 사용해 프로토콜을 준수하고 있는지 확인할 수 있다. (is와 as이므로 당연히 해당 프로토콜로 타입캐스팅이 된다.)
    • is로 타입캐스팅 했을 때 프로토콜을 준수하고 있다면 True
    • as?로 타입캐스팅 했을 때 프로토콜을 준수하지 않으면 nil
    • as!로 타입캐스팅 했을 때 프로토콜을 준수하지 않으면 Runtime error

Optional Protocol Requirements

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12

  • 프로토콜의 메서드 or 프로퍼티 준수 여부를 optional하게 작성할 수 있다. @objc 키워드 + optional 키워드를 붙여준다.
    • (Int) -> String 메서드라면 얘는 ((Int) -> String)? 이렇게. return value가 optional인 것이 아니다.
    • @objc가 붙기 때문에 struct나 enum에서는 사용할 수 없고 Objectiv-C 클래스이거나 @objc가 붙은 클래스에서만 사용할 수 있다.
  • optional protocol requirement는 실제로 호출할 때 optional chaining을 사용해야 한다. (구현 여부가 선택적이므로 구현이 되지 않았을 수 있음)
    • 코드에서 Counter의 dataSource는 CounterDataSource 프로토콜을 준수하는 무언가이며, 해당 프로토콜을 준수하더라도 increment, fixedIncrement는 optional이므로 구현이 되어있지 않을 수 있다. 따라서 dataSource의 메서드를 호출할 때 optional chaining을 통해 접근한다.

Strictly speaking, you can write a custom class that conforms to CounterDataSource without implementing either protocol requirement. They’re both optional, after all. Although technically allowed, this wouldn’t make for a very good data source.

Protocol Extensions

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}
  • 프로토콜의 extension을 작성할 수 있다. extension 프로토콜로 만들고, 필요한 method, initializer, subscript, computed property를 구현할 수 있다.
    • 프로토콜 자체에서 동작을 정의하는 것이기 때문에 선언만 하는 게 아니라 직접 구현까지 함
    • 예시 코드에서 RandomNumberGenerator 프로토콜의 random 메서드를 활용해 randomBool() 메서드를 구현
    • 프로토콜을 채택+준수한 곳에서 사용 가능. 다른 프로토콜에서 확장 or 상속은 안됨

Providing Default Implementations

  • 프로토콜 extension을 사용해서 해당 프로토콜의 모든 메서드와 computed property를 구현할 수 있다.
    • 만약 프로토콜을 채택한 곳에서 프로토콜의 메서드 or 프로퍼티를 구현하면 extension에서 구현한 건 안쓰인다.

Protocol requirements with default implementations provided by extensions are distinct from optional protocol requirements. Although conforming types don’t have to provide their own implementation of either, requirements with default implementations can be called without optional chaining.

Adding Constraints to Protocol Extensions

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}
  • Protocol을 확장할 때 특정 조건을 만족하는 경우에만 확장의 구현을 사용할 수 있도록 제약 조건을 걸 수 있다.
    • extension 뒤에 where을 붙여준다. (위 코드에서는 Collection에 사용되는 Element가 Equatable한 경우에만 allEqual을 사용할 수 있다.

If a conforming type satisfies the requirements for multiple constrained extensions that provide implementations for the same method or property, Swift uses the implementation corresponding to the most specialized constraints.

Categories:

Updated:

Leave a comment