ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift Language ) Protocol
    Swift/Swift Language 2021. 9. 16. 12:18

    Protocol 은 어떤 목적을 달성하기 위해 필요한 것들 (Properties, Methods, ...) 을 미리 정해놓은 청사진과 같은 개념입니다. 

    Enumeration, Struct, Class 는 Protocol 을 받으면서 실제 필요한 기능들을 상세히 정합니다. (Implementation) 그리고, 이러한 행위를 'Conform'  이라 부릅니다. (conform: 따르다) 

     

    Protocol 을 Extend 하면서 필요한 기능들을 상세히 정할 수도 있고, Conform 하는 Type 들이 이용할 다른 기능들도 추가할 수 있어요. 

    ( 이전 Extension 에서 언급은 했으나 아직 다루지 않은 부분이에요. )

     

    Protocol을 선언하는 방식은 Struct, Class 등을 선언하는 방식과 같아요 .

    protocol SomeProtocol { 
    	// protocol definition goes here 
    }

    Custom Type 에서 어떤 Protocol 을 adopt 하기 위해선, 처음 만들 때 혹은 나중에라도 선언해주면 됩니다. 

    struct SomeStruct: SomeProtocol, AnotherProtocol { 
        // struct definition goes here 
    }

    만약 class 가 super class 를 갖는 동시에 Protocol 을 adopt 하고 싶을 때에는 superclass 를 가장 앞에 써주세요. 

     

    class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { 
        // class definition goes here
     }

     

     

    Requirements

    Property Requirements

    Protocol 을 Adopt 하는 Type 에 대해서 해당 Protocol 을 Conform 할 때 필요한

    Property 의 이름, 타입, 그리고 gettable,  gettable and settable 중 어떤 것인지 정해줄 수 있어요.

    (instance property, type property 에 모두 적용, Stored or Computed 에 대해서는 정해주지 않아요.)

     

     

    gettable, Settable 에 대해 잠시 정리를 한다면, 

    gettable & settable.  -> constant stored property and read-only computed property  는 안돼요~
    gettable                      -> can be any kind of property, settable 이어도 됩니다.  
    (gettable 로 protocol 에서 이미 지정해주었지만, 필요에 따라 gettable & settable 이어도 된다는 의미입니다) 

     

     

    protocol SomeProtocol {
        var mustBeSettable: Int { get set }
        var doesNotNeedToBeSettable: Int { get }
    }

    Property Requirements는 항상 var 로 선언되어야 해요.

    위 코드에서 볼 수 있듯이 gettable & settable 은 { get set } 으로,  gettable 은 { get } 으로 선언합니다. 

     

     

     

    Type Property 를 Protocol 에서 정의할 때는 항상 앞에 static 을 붙여주셔야해요 . 

    This rule pertains even though type property requirements can be prefixed with the class or static keyword when implemented by a class:

     

    protocol AnotherProtocol {
        static var someTypeProperty: Int { get set }
    }

     

    protocol FullyNamed { 
        var fullName: String { get }
    }
    
    struct Person: FullyNamed { 
        var fullName: String 
    }
    
    let john = Person(fullName: "John Appleseed")
    print(john.fullName) // print "John Appleseed"

    참고 : Requirements 를 만족시켜주지 않으면 Swift에서 Compile Error 를 낼거에요 . 

     

     

    class Starship: FullyNamed {
        var prefix: String?
        var name: String
        
        init(name: String, prefix: String? = nil) {
            self.name = name
            self.prefix = prefix
        }
        var fullName: String {
            return (prefix != nil ? "\(prefix!) " : "") + name
        }
    }
    
    var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
    print(ncc1701.fullName)

     

     

     

    Method Requirements

    Protocol 을 Conform 하는 Type 에 대해 Instance Method 또는 Type Method 에 대해 Requirements 를 정해줄 수 있어요. 

    Instance, Type Method  모두에 대해 중괄호와 function body 가 없는 상태로 Protocol 에 정의되게 됩니다. 

    Variadic Parameter 는 허용되지만, Default Value 는 Protocol 을 정의할 때 사용할 수 없습니다. 

    Type Property 와 마찬가지로, Type Method 의 경우 정의될 때 앞에 static 키워드를 붙여주셔야 해요. 

     

     (This is true even though type method requirements are prefixed with the class or static keyword when implemented by a class:) ?? 

    protocol SomeProtocol {
        static func someTypeMethod()
    }

     

     

    protocol RandomNumberGenerator { 
        func random() -> Double 
    }
    class LinearCongruentialGenerator: RandomNumberGenerator {
        var lastRandom = 42.0
        let m = 139968.0
        let a = 3877.0
        let c = 29573.0
        func random() -> Double {
            lastRandom = ((lastRandom * a + c)
                .truncatingRemainder(dividingBy:m))
            return lastRandom / m
        }
    }
    
    let generator = LinearCongruentialGenerator()
    print("Here's a random number: \(generator.random())")
    // Prints "Here's a random number: 0.3746499199817101"
    print("And another one: \(generator.random())")
    // Prints "And another one: 0.729023776863283"

     

     

     

    Mutating Method Requirements

    만약 protocol 을 adpot 하는 type 의 어떤 instance 의 값을 바꿀 method 를 정의하게되면, 'mutating' 키워드를 앞에 넣어주세요.

    그렇게 해야 structures, enumerations 이 protocol 을 adpot 할 수 있고 method requirement 를 지킬 수 있답니다. (class 에서는 mutating 이 필요없어서 class 내에서 implement 할 때는 'mutating' 을 써줄 필요 없어요~ )

     

    protocol Toggleable { 
        mutating func toggle()
    }
    
    enum OnOffSwitch : Toggleable { 
        case on, off
        
        mutating func toggle() { 
            switch self { 
            case .on:
                self = .off
            case .off:
                self = .on
            }
        }
    }
    
    var lightSwitch = OnOffSwitch.on
    lightSwitch.toggle() // now it is off .

      

     

    Initializer Requirements

     

    Protocol 은 Conforming 하는 Type 에 대해 Initializer 에 대해서도 특정한 형식을 요구할 수 있어요. (보통의 initializer 와 동일 형식)

    이때 중괄호, 즉 body 부분은 제외합니다. 

    protocol SomeProtocol { 
        init(someParameter: Int)
    }

     

    class 에서 protocol 이 require 한 initializer 를 implement 할 때에는  

    designated , convenience initializer 모두에 대해 할 수 있고,

    앞에 'require' 키워드를 남겨줘야해요. 

    (final class 에 대해서는 required 키워드를 써주지 않습니다.)

    class SomeClass: SomeProtocol { 
        required init(someParameter: Int) { 
            // initializer implementation goes here
        }
    }

    만약 subclass 에서 superclass 에서의 designated initializer 를 override 하는 동시에

    protocol 에서 require 한 initializer 를 implement 할 때에는 'required override' 키워드를 같이 써주세요. 

     

    protocol SomeProtocol { 
        init()
    }
    
    class SomeSuperClass { 
        init() { 
            // initializer implementation goes here
        }
    }
    
    class SomeSubClass: SomeSuperClass, SomeProtocol { 
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
        required override init() { 
            // initializer implementation goes here
        }
    }

     

    // 나중에 설명.. Initializer 더 배우고 올게요 .. . 

     

     

     

     

    Protocol as Types

    Protocol 은 그 자체로는 어떠한 기능도 implement 하지 않아요. 하지만, Protocol 을 fully-fledged Type 으로 사용할 수 있어요.

    Protocol 을 Type 으로 이용하는 것을 existential type 이라고도 합니다. 

     “there exists a type T such that T conforms to the protocol”. 라는 문구에서 따왔다고 하네요. 

    (직역하자면 protocol 을 conform 하는 type T 가 있다.

    Protocol 을 Conform 하는  Type 을 하나의 Type 으로 쓸 수 있는 것 같네요.)

     

    Protocol Type 은 다른 Type 들 처럼 이런 곳들에서 사용될 수 있어요. 

     

    • function, method, initializer 에서의 parameter type 또는 return type
    • constant, variable, property 의 type .
    • Array, dictionary,  다른 container 내 item 의 type.
    protocol RandomNumberGenerator {
        func random() -> Double
    }
    class LinearCongruentialGenerator: RandomNumberGenerator {
        var lastRandom = 42.0
        let m = 139968.0
        let a = 3877.0
        let c = 29573.0
        func random() -> Double {
            lastRandom = ((lastRandom * a + c)
                .truncatingRemainder(dividingBy:m))
            return lastRandom / m
        }
    }

    (전에 만들었던 RandomNumberGenerator protocol 과 LinearCongruentialGenerator 이에요)

     

    class Dice {
        let sides: Int
        let generator: RandomNumberGenerator
        init(sides: Int, generator: RandomNumberGenerator) {
            self.sides = sides
            self.generator = generator
        }
        func roll() -> Int {
            return Int(generator.random() * Double(sides)) + 1
        }
    }

     

    여기서, generator 는 RandomNumberGenerator Type 으로 정의되었어요. (마치 Int, Sring 등 타입과 같이 쓰이고있죠 ?)

     

    var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
    for _ in 1...5 {
        print("Random dice roll is \(d6.roll())")
    }
    // Random dice roll is 3
    // Random dice roll is 5
    // Random dice roll is 4
    // Random dice roll is 5
    // Random dice roll is 4

     

    Delegation

    (나중에 다시 다룰게요)

     

     

     

    Adding Protocol Conformance with an Extension

     이미 존재하는 Type 에 대해  Type의 소스코드가 없어도 Protocol 을 conform 할 수 있어요 ! 

    Extension 을 이용해서 새로운 Properties, Methods, Subscripts 를 이미 있는 Type 에 대해 부여할 수 있고,

    따라서 어떠한 protocol 이 요구하는 조건에 대해서도 모두 만족시킬 수 있답니다.

    (새로운 protocol 에 conform 하게 될 때, 이미 존재하는 Instance 들에 대해서는 자동으로 conform 처리가 됩니다.)

     

    protocol TextRepresentable {
        var textualDescription: String { get }
    }

    TextRepresentable 이라는 protocol 이 있다고 할 때, 

    위 코드에서 Dice 는 해당 protocol 과 연결점이 없었지만 extension 을 통해 conform 할 수 있습니다. 

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

    이후, Dice Type 을 가지고 있는 모든 Instance 는 TextRepresentable Protocol 을 Conform 하고, 위에서 정의한 textualDescription property 또한 가지게 됩니다. 

    let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
    print(d12.textualDescription)
    // Prints "A 12-sided dice"

     

     

    Conditionally Conforming to a Protocol

     

    Generic Type 의 경우, protocol 의 requirements 를 어떠한 조건 내에서만 만족시킬 수 있는 경우가 있는데, Type 의 generic parameter 가 protocol 을 conform 할 때 그렇습니다.  Type 을 Extend 할 때 제한 조건들을 나열하는 방식으로 Generic Type 이 어떤 조건 내에서만 protocol 을 conform 하도록 만들 수 있습니다. Generic where clause 를 이용하여 conform 하는 protocol 이름 뒤에 제한 조건들을 써주세요. 아래 예시는, Array 내 instances 가 TextRepresentable protocol 을 conform 하는 elements 를 가질 때 TextRepresentable protocol 을 conform 하도록 만드는 Code 입니다. 

    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]"

     

    Declaring Protocol Adoption with an Extension

    이미 어떤 Type이 어떠한 protocol 을 이미 만족하지만 아직 adopt 한다고 정의되지 않은 경우에는,  다음과 같이 adopt 할 수 있어요 !

    struct Hamster {
        var name: String
        var textualDescription: String {
            return "A hamster named \(name)"
        }
    }
    
    extension Hamster: TextRepresentable {}

    이 후에는, 모든 Hamster Instance 들이 TextRepresentable Type 을 필요로 하는 곳에 어디든지 쓰일 수 있답니다. 

    let simonTheHamster = Hamster(name: "Simon")
    let somethingTextRepresentable: TextRepresentable = simonTheHamster
    print(somethingTextRepresentable.textualDescription)
    // Prints "A hamster named Simon"

     

     

     

    Adopting a Protocol Using a Synthesized Implementation

    Swift 는 많은 단순한 경우들에 대해 Equatable, Hashable, Comparable 에 대한 Conformance 를 자동으로 줄 수 있어요. 

    이러한 synthesized implementation 을 이용함으로서 반복적인 코드를 쓰는 행위를 하지 않을 수 있답니다 ! 

     

    Equtable

    Equatable 의 경우 Swift 는 다음 조건을 만족할 때 Synthesized Implementation 을 제공합니다.

     

    • Equatable protocol 을 따르는 stored property만 갖는 struct 
    • Equatable protoocl 을 따르는 associated type 만 갖는 enum
    • Associated type 을 갖지 않는 enum

     

    '==',  '!=' ( synthesized implementation ) 를 사용할 수 있게 하기 위해서는 Equatable protocol 을 conform 한다고 기존 파일에 선언해주시면 돼요 . (== 를 본인이 implement 하지 않으셔도 됩니다.. 본인이 하는 것을 원할 경우, Swift - AdvancedOperator  https://docs.swift.org/swift-book/LanguageGuide/AdvancedOperators.html 를 참조하시면 도움이 될거예요! )   

    struct Vector3D: Equatable {
        var x = 0.0, y = 0.0, z = 0.0
    }
    
    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("They are the same !")
    }

     

    Hashable

    Hashable protocol 을 따르는 stored property 만 갖는 struct

    Hashable protocol 을 따르는 associated type 만을 갖는 enum

    Associated type 을 갖지 않는 enum

    (Equatable 조건과 따라야 하는 protocol 만 달라요) 

     

     

    Comparable ( <=, <, >=, >)

    raw Value 를 갖지 않는 enum

    Associated type 을 가질 경우 모두 Comparable 을 따르는 enum

     

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

     

     

     

    Collections of Protocol Types

    Protocol 은 이전에 언급했듯이 type 으로 사용될 수 있어요.

    let things: [TextRepresentable] = [d12, simonTheHamster]

    따라서, 해당 array 에 대해 TextRepresentable 에 있던 requirements 을 이용하여 코드를 작성할 수 있어요.

     

    for thing in things { 
        print(thig.textualDescription)
    }
    // A 12-sided dice
    // A hamster named Simon

    위 코드에서 'thing' 은 'Dice', 또는 'Hamster' Type 이 아닌 'TextRepresentable' 타입이 됩니다. 

     

     

    Protocol Inheritance

    Protocol 은 다중의 다른 protocol 을 inherit 할 수 있고, inherit 한 protocol 에 대해 requirements 를 더 적용할 수 있어요. 

    Syntax 는 class 에서 inherit 할 때와 유사합니다.

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol { 
        // protocol definition goes here
    }

    아래는 TextRepresentable protocol 을 inherit 하는 protocol 의 예시입니다.

    protocol PrettyTextRepresentable: TextRepresentable { 
        var prettyTextualDescription: String { get }
    }

    PrettyTextRepresentable protocol 을 adopt 하는 type 은 반드시 TextRepresentable 의 Requirements 를 만족시켜야하고, 

    추가로 PrettyTextRepresentble protocol 의 requirements 또한 만족시켜야해요. 

     

    Snakes and laddles example // 나중에 다시 작성.. ( Protocol Delegation 작성 후 .)

     

    Class-Only Protocols 

    AnyObject protocol 을 추가하여, protocol 을 adopt 할 수 있는 type 을 class 로 제한시킬 수 있어요.

    protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol { 
        // class-only protocol definition goes here
    }

    위 예제코드에서, SomeClassOnlyProtocol 은 AnyObject Protocol 을 adopt 하므로 class type 에 대해서 해당 protocol 을 adopt 할 수 있어요.

    (enum , struct type 에서 SomeClassOnlyProtocol 을 adopt 하는 경우 compile error 가 발생하게 됩니다. ) 

    (reference semantics 이 필요한 경우 class만 adopt 할 수 있는 protocol 로 만들어준대요 . )

     

     

    Protocol Composition

    동시에 여러 protocols 을 conform 할 때, protocol composition 을 통해 하나의 requirement 로 만들어줄 수 있어요. 이 경우 마치  임시로 local 에서 새로운 protocol 을 정의한 것과 같이 작동합니다. (새로운 protocol 을 생성하는게 아니에요!)

    Syntax 는 SomeProtocol & AnotherProtocol 과 같이 & 를 이용해서 여러 protocol 을 원하는 만큼 연결시켜주시면 돼요.

    연결할 때 하나의 class type 을 포함할 수 있습니다. 

     

    protocol Named {
        var name: String { get }
    }
    
    protocol Aged {
        var age: Int { get }
    }
    
    struct Person: Named, Aged {
        var name: String
        var age: Int
    }
    
    func wishHappyBirthday(to celebrator: Named & Aged) {
        print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
    }
    
    let birthdayPerson = Person(name: "Malcolm", age: 21)
    wishHappyBirthday(to: birthdayPerson)

    위 예제 코드에서 함수에 넘겨지는 type은 Named, Aged protocol 만 만족한다면 어떤 type 이라도 사용될 수 있습니다.

     

     

    protocol Named {
        var name: String { get }
    }
    
    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)

     

     

    Checking for Protocol Conformance

    'is' , 'as' opereators 를 protocol의 requirements 를 만족하는 지, 그리고 casting 하는 데에 쓸 수 있어요 .

    일반적인 type 에 대해 check , cast 하는 것과 문법은 동일합니다.  

     

    protocol 을 conform 하는 경우 'is' operator 는 true , 반대의 경우 false 를 return 합니다.

    'as?' 를 사용하여 downcast 하는 경우 conform 할 때에는 protocol type 의 optional value 를, 반대의 경우 nil 을 return 합니다.

    'as!'를 사용하는 경우 conform 할 때는 protocol type을, 반대의 경우 runtime error 를 trigger 합니다. 

     

    protocol HasArea {
        var area: Double { get }
    }
    
    class Circle: HasArea {
        let pi = 3.1415927
        var radius: Double
        var area: Double { return pi * radius * radius}
        init(radius: Double) { self.radius = radius }
    }
    
    class Country: HasArea {
        var area: Double
        init(area: Double) { self.area = area }
    }
    
    
    class Animal {
        var legs: Int
        init(legs: Int) { self.legs = legs }
    }
    
    let objects: [AnyObject] = [
        Circle(radius: 2.0),
        Country(area: 243_610),
        Animal(legs: 4)
    ]
    
    for object in objects {
        if let objectWithArea = (object as? HasArea) {
            print("Area is \(objectWithArea.area)")
        } else {
            print("something that doesn't have an area")
        }
    }

    위 for statement 에서 objectWithArea 가 HasArea protocol 을 만족하는 경우 해당 objectWithArea 는 HasArea Type 으로 인식되고, 따라서 해당 constant 의 area property 만 접근 가능합니다.

     

    Optional Protocol Requirements (쓰이는걸 본적이 없어서 미루다가..  필요해서 작성하게되었습니다..^ )

     

    Optional Protocol Requirements

    Protocols 에 대해 optional requirements 를 정의할 수도 있습니다. Optional requirements 는 말그대로 'optional' 이기 때문에 protocol 을 conform 하더라도 implemented 되지 않아도 돼요. Optional requirements 를 선언하는 방법은, protocol definition 에서 optional modifier 를 앞에 써주시면 됩니다. Optional requirements 가 가능하기 때문에, Object-C 와 함께 쓰일 수 있는 code 를 작성할 수도 있습니다. protocol 과 optional requirement 에 @objc 를 반드시 써주셔야 합니다. @objc protocol 은 Objective-C classes 를 inherit 하는 classes, 또는 다른 @objc classes 들만 adopt 할 수 있어요. structures 또는 enumerations 는 adopt 할 수 없습니다. ( 일반 Swift class 도 사용할 수 있습니다. (사용해봄.. )) 

    Method 나 property 를 optional requirement 로 할때 그 type 은 자동으로 optional 이 됩니다. 예를 들어, (Int) -> String type 은 ((Int) -> String)? type 이 되는거죠. 

     

    Optional protocol requirement 는 implemented 되지 않았을 수도 있기 때문에 optional chaining 을 통해 호출될 수 있어요. 물음표 (?) 를 method 뒤에 붙이는 방식으로 사용됩니다. (someOptionalMethod?) 

    아래 예제는 increment amount 를 제공하기 위해 외부 data source 를  사용하는 'Counter' class 에 대해 정의합니다. 

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

    CounterDataSource protocol 은 optional method increment(forCount:) 와 optional property fixedIncrement 를 requirements 로 갖고있습니다. 두 requirements 는 data sources 에서 두가지 방식으로 적당한 increment amount 를 Counter instance 가 적용할 수 있게 해줍니다. 

     

    NOTE
    엄밀히 말하면, protocol 를 implement 하지 않고 custom class 가 CounterDataSource 를 conform 하게 만드는 방식으로 사용할 수도 있어요. 두가지 경우 모두 optional 로 사용하게 됩니다. 기술적으로 되긴 하지만, 이렇게 하면 좋은 data source 라고 하기는 어렵습니다. 

     

    Counter class 는 여기서 CounterDataSource? type 의 dataSource property 를 정의합니다. 

    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
            }
        }
    }

     

    Counter class 는 현재 값을 저장하는 'count'  variable 과, 호출될 때마다 count를 증가시키는 'increment' method 를 정의합니다. increment() method 는 먼저, increment(forCount:) 에서 increment amount 를 가져오는 것을 시도합니다. 이때, optional chaning 을 사용하고, 현재 count value 를 single argument 로 넘겨줍니다. 이때, Optional chaining이 두번 일어납니다. 첫번째는 dataSource 가 nil 인지 확인, increment(forCount:) 는 dataSource 가 nil 이 아닐 때에만 호출되어야 하기때문에 dataSource 뒤에 ? 가 붙었습니다. 두번째로, dataSource 가 있더라도 increment(forCount:) 는 없을 수 있기 때문에 (optional) 확인을 합니다.

    increment(forCount:) 의 호출은 이 method 가 존재할 때에만 발생합니다 (nil 이 아닌 경우).  그렇기 때문에 ? 가 이름 뒤에 붙었습니다. 

    한가지 경우라도 발생하게 되면 increment(forCount:) 호출은 실패하게 되고, 따라서 optional int 를 반환하게 됩니다(increment(forCount:) 의 반환 type 이 non-optional Int 이지만 ) . 두개의 optional chaining operations 가 붙어있지만 결과는 single optional 입니다.  

    increment(forCount:) 가 호출되고 나서 반환되는 optional Int 는 'amount' constant 로 optional binding 을 통해 unwrapped 됩니다.  Optional Int 가 값을 가지면 (delegate, method 가 모두 존재)  unwrapped 된 'amount' 는 'count' property 에 더해지고, 더해지는 과정은 종료됩니다.  만약 increment(forCount:) method 로부터 값을 가지고 올 수 없는 경우라면 (dataSource 가 nil 이거나 data source 에서 increment(forCount:) 를 implementation 하지 않는 ) increment() method 는 data source의 'fixedIncrement' property 에서 대신 값을 가져오는 것을 시도합니다. 'fixedIncrement' property 또한 optional requirement 이기 때문에, optional Int 값을 갖습니다. (CounterDataSource protocol definition 에서는 non-optional 이었지만 ! ) 

     

    아래는 간단한 CounterDataSource implementation 예제입니다. 여기서 optional fixedIncrement property 를 implementation 하면서 사용될 때마다 3 이 반환됩니다. 

     

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

    ThreeSource() instance 를 새로운 Counter instance 의 data source 로 사용할 수 있습니다.

     

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

    위 코드에서는 새로운 Counter instance 를 생성하고, data source 를 ThreeSource instance 로 설정했습니다. ThreeSource 에서 fixedIncrement 가 3 으로 정해져있기 때문에 increment() 가 호출될 때 마다 counter 의 count 가 3 씩 증가되는 것을 확인할 수 있습니다. 

     

    요기 조금 더 복잡한 TowardZeroSource 라는 data source 에서는 Counter instance 의 count 가 0 으로 하나씩 증가 / 감소하게 합니다. 

     

    class TowardsZeroSource: NSObject, CounterDataSource {
        func increment(forCount count: Int) -> Int {
            if count == 0 { return 0}
            else if count < 0 { return 1 }
            else { return -1 }
        }
    }

    TowardsZeroSource class 는 optional increment(forCount:) method 를 implement 하고, 'count' 를 사용해서 어떤 값을 더하거나 빼야할지 결정하는 데에 사용합니다. 만약 count 값이 이미 0 이라면, method 는 0 을 return 합니다. 

     

    ThreeSource 와 마찬가지로 TowardsZeroSource instance 를 이미 있는 Counter instance 가 사용할 수도 있습니다. 

    counter.dataSource = TowardsZeroSource()
    counter.increment()
    
    while (counter.count != 0) {
        counter.increment()
        print(counter.count)
    }
    
    // 11
    // 10
    
    // ...
    
    // 0

     

     

     

     

     

    Protocol Extensions

    Protocol 은 conforming 하는 type 들에게 method, initializer, subscript, 그리고 computed property 를 extension 을 통해서  implementation 할 수 있어요. 이렇게 함으로 protocol 자체에 behavior 을 정할 수 있습니다. 

    protocol RandomNumberGenerator {
        func random() -> Double
    }
    
    class LinearCongruentialGenerator: RandomNumberGenerator {
        var lastRandom = 42.0
        let m = 139968.0
        let a = 3877.0
        let c = 29573.0
        func random() -> Double {
            lastRandom = ((lastRandom * a + c)
                .truncatingRemainder(dividingBy:m))
            return lastRandom / m
        }
    }
    
    // newly added code
    extension RandomNumberGenerator { 
        func randomBool() -> Bool {
            return random() > 0.5
        }
    }
    let generator = LinearCongruentialGenerator()
    print("\(generator.randomBool())")

     

    위 코드에서 generator 변수는 LinearCongruentialGenerator() class 이고 이 class 는 RandomNumberGenerator protocol 을 adopt , 해당 protocol 에서 implementation 을 제공해주었으므로 자동으로 randomBool() method 호출이 가능해졌습니다. 

     

    Protocol extension 은 Conform 하는 type 들에게 implementation 을 제공할 수 있지만, 다른 protocol 을 inherit 하게 할 수는 없어요. Protocol Inheritance 는 항상 protocol 자체를 정의할 때 specified 됩니다. 

     

    Providing Default Implementations

     

     

    Protocol extension 을 이용해서 default implementation 를 method, computed property requirement 에 제공할 수 있습니다. 만약 comform 하는 type 이 그 type 만의 implementation 을 갖는다면, extension 으로 제공된 것 대신 사용됩니다.  

    Default implementations 가 extension 을 통해 제공된 protocol 의 requirements 는 optional protocol requirements 와는 다릅니다. Conform 하는 type 이 두 경우 모두 그 type 만의 implementation 을 만들 필요는 없는 점은 같지만, default implementation 이 있는 requirements 는 optional chaining 없이 사용할 수 있습니다.  

     

    예를 들어, PrettyTextRepresentable protocol 은 prettyTextualDescription 에 대하여 default implementation 을 제공할 수 있습니다.

     

    protocol PrettyTextRepresentable: TextRepresentable { 
        var prettyTextualDescription: String { get }
    }
    
    extension PrettyTextRepresentable  {
        var prettyTextualDescription: String {
            return textualDescription
        }
    }​

     

    Adding Constraints to Protocol Extensions

    Protocol extension 을 정의할 때,  conforming types 이 만족시켜야 하는 제한조건들을 정해줄 수 있습니다.(before the methods and properties of the extension are available.) Extending 하고 있는 protocol 의 이름 뒤에 generic where clause 를 통해 제한조건들을 적어주세요. 

     

    아래 쓰인 코드에서는 elements 가 'Equatable' protocol 을 conform 하는 Collection protocol 에 대해 extension 을 정의하고 있습니다. Elements 를 Equatable protocol 을 conform 하는 type 으로 제한하기 때문에 '==' 와 '!=' 을 사용할 수 있습니다. 

     

    extension Collection where Element: Equatable {
        func allEqual() -> Bool {
            for element in self {
                if element != self.first {
                    return false
                }
            }
            return true
        }
    }

    allEqual() methods 는 collection 내 모든 elements 가 같을 때만 true 를 return 합니다. 

     

     

    아래 두 array 를 예로 들어보면, arrays 가 Collection 을 conform 하고, integers 가 Equatable 을 conform 하기 때문에, 두 arrays 는 allEqual() method 를 사용할 수 있습니다. 

    let equalNumbers = [100, 100, 100, 100, 100]
    let differentNumbers = [100, 100, 200, 100, 200]
    print(equalNumbers.allEqual())
    // Prints "true"
    print(differentNumbers.allEqual())
    // Prints "false"

     

    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.

    만약 conforming type 이 같은 methods 나 property 에 대해 implementation 을 제공하는, constraints 여러개 있는 extensions 를 모두 만족하게되면 Swift 는 가장 specialized constraints 를 갖는 extension 의 implementation 을 해당 methods 나 property 에 대해 적용합니다. 

    출처: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html

    'Swift > Swift Language' 카테고리의 다른 글

    Swift Language ) Closure  (0) 2021.10.06
    Swift Language) ARC ( Automatic Reference Counting)  (0) 2021.10.04
    Swift Language ) Generics  (0) 2021.09.23
    Swift Language ) Access Control  (0) 2021.09.16
    Swift Language ) Extensions  (0) 2021.09.14
Designed by Tistory.