티스토리 뷰

Swift

Swift ) Protocols (3)

Zedd0202 2017. 12. 15. 19:05
반응형


프로토콜........잠깐 뜸했는데요,  프로토콜 마스터를 위해......키키

옛날..?에 프로토콜 (1), (2)를 썼는데요.아직.....1/3도 안했답니다!!!!!!!(충격) 끝까지 따라와주시길 바래요 XD...




Protocols as Types


자. 타입으로서의 " 프로토콜"이라고 하네요.
이전글들에서 볼 수 있듯이, 프로토콜은 기능을 구현하지 않습니다. 
그럼에도 불구하고, 만드는 프로토콜은 코드에서 사용하는 완전한 형태입니다. 
왜냐하면, 프로토콜은 "타입"이니까요.
프로토콜은 다른 타입이 허용되는 여러 곳에서 다음과 같은 프로토콜을 사용할 수 있습니다.

● 함수, 메소드 또는 이니셜라이저에서의 매개변수 타입 또는 리턴타입
● 상수(constant), 변수(variable) 또는 프로퍼티로서의 타입
● 배열(array)또는 사전(Dictionary) 또는 다른 컨테이너의 항목으로서의 타입

프로토콜은 타입이기 때문에, 대문자(예 : FullyNamed 및 RandomNumberGenerator)로 이름을 시작하여(첫번째 문자를 대문자로) Swift에서 다른 타입의 이름(예 : Int, String 및 Double)과 일치시킵니다. 

잘 이해가 안가시죠? 프로토콜이 "타입"? 이게 무슨 소리지? 
한번 예제를 살펴봅시다. 

protocol RandomNumberGenerator {

    func random() -> Double

}

저번글에서 RandomNumberGenerator라는 프로토콜을 정의했었죠 :)

굳이 저번글 가셔서 찾아보실 건 없고!!!

우리가 배운 걸 토대로 RandomNumberGenerator라는 프로토콜을 분석해봅시다.

별거 없긴 하지만요 :)......


어떤 특징을 가지고 있죠?

그렇습니다. 딱 하나. 

"메소드"를 요구했네요. 즉, 이 프로토콜을 채택하는 타입들은 반드시 저 random이라는 메소드를 구현해야합니다. 


잘하셨어요!!

그럼 이제 "타입으로서의 프로토콜"을 봅시다. 


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

    }

}

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

    }

}

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())

for _ in 1...5 {

    print("Random dice roll is \(d6.roll())")

}

Dice(주사위)라는 클래스를 정의했네요. 저번과 다르게 이 클래스에서 RandomNumberGenerator라는 프로토콜을 "채택"하진 않은것을 볼 수 있습니다.

대신 generator라는 상수(constant) 저장 프로퍼티를 선언했는데!!!!!!!!!!!
타입을 RandomNumberGenerator라고 줬네요!!!
그럼 generator는 RandomNumberGenerator타입인거죠?

그리고 클래스 저장프로퍼티들에 기본값이 없으니, init을 만들어줬어요!
그리고 generators는 RandomNumberGenerator타입이기 때문에, random()이라는 
메소드는 구현해줘야 할 의무는 없어요. 그냥 random()에 접근할 수 있게 되는것이죠. 



 

Delegation

프로토콜...하면..역시 Delegate..........기대되네요.
우리 iOS하면서 Delegate많이 봤었죠? 
Delegation이는 클래스나 구조체가 책임을 일부 다른 타입의 인스턴스로 전달(또는 위임) 할 수 있게 하는 디자인 패턴입니다.
이 디자인패턴은 이 프로토콜을 채택할 타입(conforming type)에게 위임할 기능을 캡슐화한 프로토콜을 정의함으로서 구현될 수 있습니다.
Delegation을 사용하면, 특정 동작에 응답하거나 외부 소스에서 해당 소스의 기본 데이터 타입을 알 필요없이 데이터를 검색할 수 있습니다. 
예제를 같이 볼게요...라고 할라고 그랬는데
Apple예제가 상당히 읽기 어렵게 되어있네요. 차근차근보면 이해가 되긴하는데 너무 장황하게 되어있네요;..................왜 이런 예제를 넣은거지?
다른챕터에 있는 예제도 막 갖다 쓰고 그래서 zzz정말 아무것도 모르는 사람이 보면 1도 이해가 안가는 설명이랄까? 정말 프로토콜에서 가장 중요한 Delegation부분인데 ㅠㅠㅠ 이부분은 제가 따로 간단한 예제를 만들던가 해서 따로 쓸게요. 나는 예제를 봐야겠다!! 하시는 분들은  Swift  - Protocols에 가셔서 Delegation부분 예제 보시면 됩니다!! 


Adding Protocol Conformance with an Extension

기존 타입의 소스코드에 접근할 수 없는 경우에도, 기존 프로토콜을 확장(Extension)하여 새 프로토콜을 채택하고 준수 할 수 있습니다. Extension 기능은 기존 타입에 새로운 속성, 메소드 및 하위 스크립트를 추가할 수 있으므로, 프로토콜에서 요구할 수 있는 모든 요구사항을 추가 할 수 있습니다. 

자..Extension에서 프로토콜을 채택하고 준수할 수 있다는데요.무슨소리지?!
자, 다시 읽어봅시다. Extension 기능은 기존 타입에 새로운 속성, 메소드 및 하위 스크립트를 추가할 수 있으므로, 프로토콜에서 요구할 수 있는 모든 요구사항을 추가 할 수 있습니다. 
이미 있는 타입을 확장(Extension)해서 어떠한 프로토콜을 채택하고 준수할 수 있다는 말이네요.
그럼 일단 프로토콜을 만들어야겠죠!!!!!! Swift문서에 나와있는 예제도 너무 좋은데, 막 옛날거를 알아야해서 (Protocol에 안나오고 다른 챕터에 나왔던 코드)
일단 제가 만들어봤습니다....XD 

  1.  protocol AddString {

        var addHello : String { get  }

    }


자...프로토콜을 하나 만들었어요. get속성만 요구하므로, 모든 종류의 프로퍼티에서 만족시킬 수 있겠네요. 

이름에서 보실 수 있겠지만, Hello를 String에 추가시켜주는 프로퍼티입니다.

이제 Extension을 사용해볼텐데요. 이미 원래 있는 타입을 Extension 시켜봅시다. 


  1. extension String : AddString{

    }


이미 원래 있는 타입인 String을 Extension시켜주고, 우리가 방금 만든 AddString 프로토콜을 채택하게 해주었습니다.

프로토콜을 채택했으니..준수를 해야겠죠? 지금까지는 준수는 하지 않은 상태라

지금 AddString이라는 프로토콜이 addHello라는 프로퍼티를 요구하고있어 ㅎ; 만들어줄래? ㅎ;;;;;; 

라고 하죠. 그럼 요구사항을 구현해줘봅시다. 그 전에 주의하셔야 할게 있는데요,

프로토콜 (1)글에서, get만 요구한다. == 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있다라고 배웠지만 

 

  1. extension String: AddString{

        var addHello: String//error

    }


저장 프로퍼티로 만들어버리면 에러가 발생하게 됩니다. 

이유는

extensions may not contain stored properties

extension안에 저장프로퍼티를 만들면 안된다고 하네요. 
그럼 연산프로퍼티로 만듭시다.

  1. extension String: AddString{

        var addHello: String {

            return "Hello \(self)"

        }

    }


self는 이 addHello를 호출하는 String을 의미합니다.

get이니 리턴만 하면 되겠죠?

이제 사용해볼까요? String타입이기만 하면, 이제 addHello라는 연산프로퍼티를 호출 할 수 있습니다.



짠!!!!

어떤가요 :)

이게 프로토콜의 Extension의 예제로 적절했을지는 모르지만.....아무튼 어떤 느낌??인지는 아시겠죠?!??!

하나만 더 해볼까요?

아 프로토콜 이름...을 어떻게 해야할지 모르겠어서..걍 Zedd프로토콜이라고 만듬 ㅎ


  1. protocol ZeddProtocol {

        var returnDouble: Doubleget  }

        mutating func add(n: Int)

    }


자.. ZeddProtocol에는 뭐가 있나 봅시다.

먼저 returnDobule이라는 get프로퍼티가 있네요. Double형인것을 보니 Double을 리턴하나봅니다. 

아 이번엔 set도 넣어주지 ㅎ; 왜 또 get이에요 ㅎ;;;;;;;

==> 먼저, "연산프로퍼티"에 대해서 아셔야합니다. 연산프로퍼티에서 자기자신에 값을 업데이트하고 자기자신을 리턴할 수 없기 때문에, 저장소를 하나 만들어서 그 저장소의 값을 조작하거나 리턴했죠? 즉 저장소인 저장프로퍼티가 필요합니다.

하지만 지금 extension예제를 하고 있죠? 위에서 잠깐 말씀드렸지만, extension안에서 저장프로퍼티를 만들면 에러가 납니다!! 

그러므로 Protocol extension예제에서는 get만 하는걸로 XD


아무튼 계속하겠습니다.

자...그리고 mutating이 나왔네요!!!!!!!mutating은 <Method>에서 배웠죠? 아신다고 가정하고 넘어가겠습니다. 이름이 add이고 파라미터를 받는 걸 보니, 덧셈을 하는 메소드!

그럼 이제 채택해볼까요? Int를 extension해서 ZeddProtocol을 채택해보겠습니다. 


  1. extension Int: ZeddProtocol{

        var returnDouble: Double{

                return Double(self)

            }

        mutating func add(n: Int) {

                self += n

        }

    }

자..ZeddProtocol이 returnDouble이라는 프로퍼티와 add라는 메소드를 요구하기 때문에 ZeddProtocol을 채택하게된 Int는 returnDouble이라는 프로퍼티와 add라는 메소드를 구현해줘야합니다.

returnDouble은 해당 Int를 Double형으로 리턴하고, add는 지금 자기자신, 즉 현재 Int에 파라미터로 받은 값을 더해주네요.

여기서 mutating을 제거하면 절대 안된다는 것 아시죠? 왜냐하면 Int는 Struct로 구현되어있기 때문입니다 == Value Type이기 때문입니다. 

Class와 다르게 말이죠.

자..한번 사용해봅시다. 



  1. var number = 10

    number.returnDouble//10.0

    number.add(n: 20)

    number.returnDouble//30.0


짠 XD Protocol의 Extension에 대해서 조금 이해가 가셨나요?

자..그럼 신기한?거 하나만 더 보겠습니다. 


  1. struct Zedd { 

        var myDouble = 0.0

        var returnDouble: Double {

          return myDouble

        }

        mutating func add(n: Int) {

            myDouble += Double(n)

        }

    }


자..Zedd라는 Struct를 하나 만들었어요.

그런데 우연히 제가 ZeddProtocol이 요구하는 것들을 이미 다 구현을 했습니다. 하지만, 프로토콜을 채택 안한것. 보이시죠?

그럼 이 Zedd를 extension해서 ZeddProtocol을 채택하도록 하면?

즉,

  1. struct Zedd {

        var myDouble = 0.0

        var returnDouble: Double {

            return myDouble

        }

        mutating func add(n: Int) {

            myDouble += Double(n)

        }

    }

    extension Zedd: ZeddProtocol{


    }

이렇게요. 

이렇게 하면 에러가 안난답니다!!! 왜냐면 이미 Zedd에서 구현되어 있기때문이죠. 당연하죠???

타입이 이미 프로토콜의 모든 요구사항을 준수하지만, 아직 해당 프로토콜을 채택한다고 명시하지 않은 경우, 빈 extension으로 프로토콜을 채택할 수 있습니다. (you can make it adopt the protocol with an empty extension)

그렇다고 타입이 모든 요구사항을 충족시켰다고 자동으로 프로토콜을 채택하진 않습니다. 항상 프로토콜의 채택을 명시적으로 선언해야합니다.

ㅇㅋㅇㅋ



Collections of Protocol Types

위에서 타입으로서의 프로토콜을 봤었죠? 거기에서 언급한 것처럼, 배열이나 사전과 같은 콜렉션에 저장될 타입으로 프로토콜을 사용할 수 있습니다.

헉 신기..

즉.. 내 프로토콜 타입의 배열 또는 사전을 만들 수 있다는 거에요!!!


  1. var zeddArr = [ZeddProtocol]()


 이게 가능하다는 것. 

그럼 뭘 넣을 수 있을까요? 위에서 했던 예제들을 생각해봅시다.

  1. var zeddArr = [ZeddProtocol]()

    zeddArr.append(0)//[0]

    zeddArr.append(1)//[0, 1]

자..이게 가능해지는 것이죠. 

헉 왜 Int인 0이랑 1이 들어갈 수 있는거죠? 

  1. extension Int: ZeddProtocol {

        var returnDouble: Double {

               return Double(self)

            }

        mutating func add(n: Int) {

            self += n

        }

    }


위에서 Int가 ZeddProtocol을 채택하고 준수했으니까 ㅇㅇ


  1. struct Zedd {

        var myDouble = 0.0

        var returnDouble: Double {

            return myDouble

        }

        mutating func add(n: Int) {

            myDouble += Double(n)

        }

    }


아까 만든 Zedd Struct.


  1. var zedd = Zedd()


이렇게하면 zedd라는 Zedd구조체의 인스턴스가 만들어졌죠? 

어 근데 Zedd구조체가 ZeddProtocol을 준수한다? 


  1. var zeddArr = [ZeddProtocol]()

    zeddArr.append(zedd)

이게 가능해지는 것입니다. 

zedd는 Zedd구조체의 프로퍼티에 접근할 수 있죠? 이제?


이렇게요. 그렇다면..



  1. var zeddArr = [ZeddProtocol]()

    zeddArr.append(zedd.myDouble)


도 가능할까요? 

아쉽게도 아닙니다. 왜냐면 myDouble이라는 애는 Double타입이거든요.

Double이 ZeddProtocol을 따르도록 하지는 않았죠? Zedd라는 구조체가 ZeddProtocol을 따르기때문에, Zedd구조체의 인스턴스만 들어갈 수 있습니다. 

"배열이나 사전과 같은 콜렉션에 저장될 타입으로 프로토콜을 사용할 수 있습니다." 이제 이 말이 무슨 소리인지 아시겠죠?

하나만 더 볼게요. 콜렉션에 저장될 수 있다는 것은..................바로바로


  1. var zeddArr = [ZeddProtocol]()

    zeddArr.append(0)

    zeddArr.append(1)

    zeddArr.append(zedd)

    for index in zeddArr {

        print(index.returnDouble)//0.0 1.0 0.0

    }

이렇게 for문을 사용할 수 있게됩니다!!!!! ZeddProtocol타입인 zeddArr에 들어가있다는 말은 ZeddProtocol이 요구하는 returnDouble 프로퍼티를 가진것을 확신할 수 있습니다. 왜 0.0, 1.0, 0.0이 나오는지는 위에서 했던것들을 보시면 아실거에요 :)



Protocol Inheritance

프로토콜은 하나 이상의 다른 프로토콜을 상속할 수 있으며, 상속 된 요구사항에 추가로 요구사항을 추가할 수 있습니다. 프로토콜 상속 구문은 클래스 상속구문과 비슷하지만, 여러개의 상속된 프로토콜을 쉼표로 구분하여 나열하는 옵션이 있습니다.

네..그렇습니다. 프로토콜이 다른 프로토콜을 상속할 수 있다네요!!!!


  1. protocol InheritingProtocol: SomeProtocol, AnotherProtocol {

        // protocol definition goes here

    }


구문은 이렇게 작성하시면 됩니다. 그럼 우리도 한번 사용해봅시다..


  1. protocol ZeddProtocol {

        var returnDouble: Doubleget }

        mutating func add(n: Int)

    }

    protocol AnotherProtocol { }

    protocol InheritingProtocol: ZeddProtocol, AnotherProtocol {

        // protocol definition goes here

    }

위에서 만든 ZeddProtocol 그냥 쓰겠습니다.

아무튼 InheritingProtocol은 ZeddProtocol과 AnotherProtocol을 상속받고 있습니다. 

InheritingProtocol에서 ZeddProtocol이 요구하는거 구현해줘야 하는거 아니에여?

==> InheritingProtocol는 "채택"이 아니라 상속을 받고 있으므로 요구사항 구현 이런거는 안해도 됩니다. 


자..위에서 그랬죠? 상속 된 요구사항에 추가로 요구사항을 추가할 수 있습니다. 라고. 

그럼 InheritingProtocol에도 뭔가 요구사항을 만들어줘볼게요. 


  1. protocol InheritingProtocol: ZeddProtocol, AnotherProtocol {

        mutating func sub(n: Int)

    }


간단한게 최고. add는 ZeddProtocol에 있으니 빼주는 메소드를 하나 요구해보겠습니다. 

자 이제 채택하러 가야겠죠? 



  1. protocol InheritingProtocol: ZeddProtocol, AnotherProtocol {

        mutating func sub(n: Int)

    }

    struct Zedd: InheritingProtocol{

        //error!

    }

저~~기 위에 있던 Zedd 구조체는 잊어버리시고 지금 새로 만드는 겁니다. InheritingProtocol을 채택한 구조체로요. 

지금 이상태면 당연히 에러가 날텐데요, 이제 어떻게 에러를 없애야 하는지 감이 오시죠? 

네. AnotherProtocol은 아무것도 요구하는 것이 없으니까 그냥 넘어가고, 

ZeddProtocol은 returnDouble과 add메소드를 요구하니 이 두개를 구현해줘야겠죠?

그리고!!! InheritingProtocol에서 요구하는 sub도 잊으면 안됩니다. 

Zedd 구조체의 코드는 


  1. struct Zedd: InheritingProtocol {

        var myDouble = 0.0

        var returnDouble: Double{

            return myDouble

        }

        mutating func add(n: Int){

            myDouble += Double(n)

        }

        mutating func sub(n: Int) {

            myDouble -= Double(n)

        }

    }

이것이 되겠네요. 

뭔가 이제 당연하죠?

오 이제 딱 2/3정도 한거 같아요 :) 다음글이 프로토콜의 마지막 글이 되겠네요!

나름 예제 쉽게 생각한다고 해본건데..XD... 만약 예제에서 틀리거나 지적할 부분이 있다면 꼭!!! 댓글이나 PC화면 오른쪽 하단에 있는 채널 서비스를 이용해서 메세지 주세요 :)

도움이 되었길 바랍니다~~~


반응형

'Swift' 카테고리의 다른 글

Swift4 ) Swap / Law of Exclusivity  (0) 2017.12.21
Swift ) Protocols (4)  (3) 2017.12.17
Swift ) Unicode to Int/Int to Unicode  (2) 2017.12.14
Swift ) Nested Types  (0) 2017.12.06
Swift) dynamic이란? / Realm의 dynamic var는?  (8) 2017.11.17