티스토리 뷰

Swift

Swift ) Protocols (1)

Zedd0202 2017. 10. 16. 15:25
반응형

안녕하세요 :) Zedd입니다!!

오늘은 프로토콜에 대해서 알아볼거에요!

뭔가...번역하는거 진짜 힘든데 중독되네요.

뭔가 다 번역해버리고 싶은 그런 막 그런 충동이..

사실 associated type에 대해서 글 쓰려고 여기저기 알아보는 와중에, 일단 프로토콜(protocol)을 알아야 할 것 같더라구요 :)

생각해보니까, 저만의 프로토콜?을 만들어본적이 없는 것 같아서.. 일단 프로토콜을 완벽하게 공부해보려고 합니다.

일단 <The Swift Programming Language (Swift 4) - Protocols  >에 가시면 원본을 보실 수 있어요! 그리고 엄청 길어서 ㅠㅠㅠ 프로퍼티처럼 여러 시리즈로 갈 수 있을 것 같습니다 XD..

시작할게요 :)






Protocols




일단 "프로토콜"

제가 정말 예전에 < 왕초보를 위한 delegate 정리 >에서 설명드렸어요.

프로토콜은 약속, 규약이라고 :)


프로토콜은 특정 작업이나 기능에 적합한 메소드, 프로퍼티 및 기타 요구사항의 청사진(blue print)를 정의합니다.

그런 다음, 프로토콜을 클래스, 구조체 또는 열거형에서 채택(adopted)하여 이러한 요구사항을 실제로 구현할 수 있습니다. 

프로토콜의 요구사항을 충족시키는 모든 타입은 해당 프로토콜을 준수(conform)한다고 합니다.


추가적으로, 프로토콜은 구현해야할 요구사항을 지정하는 것 이외에도, 프로토콜을 확장하여 이러한 요구사항 중 일부를 구현하거나, conforming타입에서 활용할 수 있는 추가 기능을 구현할 수 있습니다. 


말로하니까 잘 모르겠네요 :)

그치만 이거 하나만 알아두세요!!! 프로토콜은 메소드, 프로퍼티등을 "정의"만 하고, 이 프로토콜을 채택한 곳에서 "구현"을 한다는 것을요 XD

일단 이 프로토콜을 어떻게 만드는지 부터 알아봅시다.


protocol SomeProtocol {

    // protocol definition goes here

}

클래스처럼 앞에 protocol이라는 키워드를 붙이고, 그 다음 이 프로토콜의 이름을 지어주세요 :)

그러면 우리는 SomeProtocol이라는 프로토콜을 지금 하나 만든거랍니다.


그러면 방금 우리가 만든 프로토콜을 다른 클래스, 구조체, 열거형에서 어떻게 "채택"할 수 있을까요?


protocol SomeProtocol {

    // protocol definition goes here

}

struct SomeStructure: SomeProtocol {

    // structure definition goes here

}


이렇게! 상속받는 것 처럼, :(콜론) 뒤에 프로토콜 이름을 넣어주시면 된답니다. 프로토콜 여러개를 채택할 수 도 있어요. ,(콤마)로 구분해서 넣어주세요 :)

그런데! 만약에 어떤 클래스가 있는데, 이 클래스는 슈퍼클래스(부모클래스)가 있어요. 지금 즉 슈퍼클래스를 상속받은 서브클래스(자식클래스)이죠.

이럴때는, 


class SomeClass: SomeSuperclass, SomeProtocol{

    // class definition goes here

}

이렇게 슈퍼클래스를 먼저! 써주고, 그 다음 프로토콜을 채택해주어야 한답니다. 








Property Requirements(프로퍼티 요구사항)




자..우리는 지금 프로토콜을 어떻게 정의하는지까지 배워봤어요. 채택도 조금..
그럼 프로토콜 안을 채워봅시다. 
그 전에, 이제부터 저장프로퍼티, 연산프로퍼티, getter, setter등을 이야기 할건데, 아직 이러한 개념에 대해서 잘 모르시는 분들은

위 세 글을 읽고오시는 것을 추천드립니다 :)...

제 글이 아니더라도 어디서든!!! 프로퍼티의 개념을 확실히 알고 글을 읽어나가셨으면 좋겠어요 XD..




자 계속할게요!!


프로토콜은 어떤 conforming type(해당 프로토콜을 준수하는 타입)에게 특정 이름과 타입인, 인스턴스 프로퍼티 또는 타입 프로퍼티를 요구할 수 있습니다. 

프로토콜은 이 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 명시하지 않습니다

오직 프로퍼티의 이름과 타입만이 요구됩니다. 

또한, 프로토콜은 각 프로퍼티에 gettable(읽기)인지 gettable/settable(읽기/쓰기)인지 명시해야만 합니다.


프로토콜이 gettable 및 settable 프로퍼티를 요구하면, 해당 프로퍼티 요구사항은 상수(constant) 저장 프로퍼티 또는 읽기 전용 연산 프로퍼티로 충족되서는 안됩니다. (밑에서 설명할 것 - 2)

프로토콜에서 gettable만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있으며, 만약 필요하다면 settable이 될 수도 있습니다. (밑에서 설명할 것 - 1)


프로퍼티 요구사항은 항상 변수(variable) 프로퍼티로 선언됩니다. (var키워드와 함께 선언.) 

gettable과 settable프로퍼티는 선언 다음에 {get set}을 쓰고, gettable프로퍼티는 {get}으로 써줍니다. 


자ㅏㅏㅏㅏ 무슨소리인지 잘 모르시겠죠 ㅠㅠ

그러니까 정리하면, 


1. 프로토콜은 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 명시하지 않는다.

2. 대신 읽기만 가능한지, 읽기/쓰기 모두가 가능한지 명시해야한다 ( setter만 있는건 없다고 그랬죠?)

3. 프로퍼티 요구사항은 항상 var로 선언되어야 한다.


가 되겠네요!

예제를 통해 더 설명드릴게요.


protocol SomeProtocol {

    var mustBeSettable: Int { get set }

    var doesNotNeedToBeSettable: Int { get }

}

SomeProtocol이라는 프로토콜을 정의하고, 프로퍼티 2개를 선언해주었어요. 물론 var로요!! 

mustBeSettable은 읽기/쓰기가 가능한 프로퍼티고, doesNotNeedToBeSettable은 읽기전용이네요.


그리고, 타입 프로퍼티! 기억나시나요?


protocol AnotherProtocol {

    static var someTypeProperty: Int { get set }

}

타입 프로퍼티 역시 프로토콜안에서 정의 할 수 있는데요, 항상 "static"이라는 키워드를 붙여서 정의해야한답니다.

이 규칙은 클래스를 구현할 때 타입 프로퍼티 요구사항에 "class" 또는  "static"키워드를 접두어로 사용 할 수 있는 경우에도 적용됩니다. 




자, 이제부터 예제를 하나 보여드릴건데요, 단일(single) 인스턴스 프로퍼티 요구사항이 있는 프로토콜의 예입니다. 

protocol FullyNamed {

    var fullName: String { get }

}

FullyNamed라는 이름의 프로토콜이고, 읽기전용으로 String타입인 fullName을 정의했네요.

프로토콜에서 fullName이라는 프로퍼티를 "요구"하고 있는거에요.


이때까지 프로토콜을 "정의"하는 부분을 봤는데요, 클래스나 구조체에서 이런 프로토콜들을 어떻게 "채택(adopted)"하고 "준수(conform)"하는지 봐봅시다. 


먼저 채택을 해야 준수하든 말든 하겠죠? 채택을 해봅시다. 사용할 프로토콜은 위에서 정의한 FullyNamed프로토콜이에요 :)


자, 위에서 그랬죠? 클래스나 구조체 이름 다음에 :(콜론)을 찍고 채택할 프로토콜의 이름을 써주면 된다구요.


protocol FullyNamed {

    var fullName: String { get }

}

struct Person: FullyNamed {

    //code

}

자, Person이라는 이름을 가진 구조체에 FullyNamed 프로토콜을 채택해주었어요.

하지만, 


error: type 'Person' does not conform to protocol 'FullyNamed'


지금은 이러한 에러가 나게 됩니다. 왜냐하면, FullyNamed 프로토콜에서 정의한 "프로퍼티 요구사항"을 Person이 준수하고 있지 않기 때문이에요. 

프로토콜은 약속, 규약이라고 그랬죠? Person은 그 약속을 지키고 있지 않아서 에러가 나게 됩니다. 

에러를 고치려면 어떻게 해야할까요?

네! FullyNamed이 요구하는 "요구사항"을 지켜주기만 하면 됩니다. 



protocol FullyNamed {

    var fullName: String { get }

}

struct Person: FullyNamed {

    var fullName: String

}

이렇게요! 

엥;; 그냥 이렇게만 해도되나요?;; 


네!! 이래도 됩니다. 제가 위에서 말씀드렸죠?



"프로토콜은 이 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 명시하지 않습니다

오직 프로퍼티의 이름과 타입만이 요구됩니다."


라구요! 

이제 이 말이 무슨뜻인지 아시겠나요?



자, 다음으로 넘어가기 전에, 알아야 할 것 들이 있습니다. 제가 위에서 말한 것들인데요. 

먼저, 



프로토콜에서 !!!!!!!!!!gettable!!!!!!!!!!만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있으며, 만약 필요하다면 settable이 될 수도 있습니다.



이 말이요. 

굉장히 어려워보이는 말이지만, 차근차근 읽어봅시다. 

프로토콜에서 / gettable(읽기전용)만 필요로 하는 경우 

protocol FullyNamed {

    var fullName: String { get }

}

바로 위와같은 상황이죠? 자 다음.


"모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있으며"


여기서 말하는 "모든 종류의 프로퍼티"라는 것은 저장프로퍼티나 연산프로퍼티를 말하는 거에요.

그러니까 

네가 프로토콜에서 gettable(읽기전용)만 요구하면, 이 요구사항을 저장프로퍼티로 선언하든, 연산프로퍼티로 선언하든 상관없다는 말입니다.




같이 볼까요? 


저장프로퍼티부터 봐봅시다. 

저장프로퍼티에는 "상수" 저장 프로퍼티와 "변수" 저장 프로퍼티가 있었으니, 두가지 경우를 모두 볼게요.



1. 상수(constant) 저장 프로퍼티

protocol FullyNamed {

    var fullName: String { get }

}


struct Person: FullyNamed {

    let fullName: String

}

var john = Person(fullName: "John Appleseed")

john.fullName = "Zedd"//error! 'fullName' is a 'let' constant

상수 저장 프로퍼티! 기억하시죠? 상수로 선언된, 즉 let으로 선언된 저장 프로퍼티는 처음 값을 할당하고 나서, 값을 바꾸지 못합니다.




2. 변수(variable) 저장 프로퍼티


protocol FullyNamed {

    var fullName: String { get }

}

struct Person: FullyNamed {

    var fullName: String

}

var john = Person(fullName: "John Appleseed")

john.fullName = "Zedd"//ok

변수. 즉 var로 선언된다면 그 값을 바꿀 수 있게 되죠.



자 이렇게 저장프로퍼티는 다 봤어요. 이제 연산프로퍼티를 봐야겠죠?





3. 연산프로퍼티 - gettable


연산프로퍼티의 특징 중 하나가 바로 값을 저장하고 리턴할 변수가 따로 있어야 한다는 점이었어요. 

기억하시죠!?

그리고 var로 선언되어야 합니다. 


protocol FullyNamed {

    var fullName: String { get }

}

struct Person: FullyNamed {

    var name: String

    var fullName: String {

        return name

    }

}

var john = Person(name: "John Appleseed")


john.fullName = "Zedd"//error! 'fullName' is a get-only property

자. 저장 할 곳이 필요하다고 그래서, name이라는 진짜 값이 저장 될 저장프로퍼티를 하나 선언해주고, 진짜 구현해야 할 fullName은 프로토콜에서 읽기전용이었으니 get만 구현해주었어요. 

하지만, fullName에 다른값을 넣을려고 하면, "읽기 전용"이기 때문에 다른 값을 넣어줄 수 없다고 나오게 됩니다. 


그리고 john이라는 Person 인스턴스를 초기화할 때를 잘 봐주세요. 파라미터로 fullName이 아닌, name이 가게 됐죠? 

연산프로퍼티는 값을 "저장"하는 것이 아닌, 연산을 통해서 값을 세팅하거나 리턴해준다고 그랬죠?

그러니까 인스턴스 초기화때는 연산프로퍼티인 fullName을 통해 초기화 할 수 없고 진짜 값이 저장되는 name을 사용해야한답니다 :)


자! 우리 다 본게 아니에요 :) 위에서 그랬죠?


프로토콜에서 gettable만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있으며

만약 필요하다면 settable이 될 수도 있습니다.


이게 무슨소리일까요? 필요하다면 settable(쓰기)가 될 수 있다는것이요.

먼저 settable만을 가질 수 없을테니, get다음에 추가할 수 있겠네요.





4. 연산프로퍼티 - gettable/settable


protocol FullyNamed {

    var fullName: String { get }

}

struct Person: FullyNamed {

    var name: String

    var fullName: String {

        get {

            return name

        }

        set {

            name = newValue

        }

    }

}

var john = Person(name: "John Appleseed")

john.fullName = "Zedd"//ok

분명!!!! 프로토콜에서는 { get }으로만 요구했는데, 

뭐~~~정~~~니가~~필요하다면~~~~settable로 할 수 있어~~~~~라는 뜻이죠.

이제는 fullName을 바꿔도 오류가 나지 않죠?






자...


프로토콜에서 gettable만 필요로 하는 경우, 모든 종류의 프로퍼티에서 요구사항을 충족시킬 수 있으며

만약 필요하다면 settable이 될 수도 있습니다.


이게 무슨소리인지 이제 이해가 가시나요?!?!?!!?





그럼 이제 다음으로...가지말고... 두번째로



프로토콜이 gettable 및 settable 프로퍼티를 요구하면, 해당 프로퍼티 요구사항은 상수(constant) 저장 프로퍼티 또는 읽기 전용 연산 프로퍼티로 충족되서는 안됩니다.


이게 무슨소리인지 알아봅시다. 

이번에도 무슨소리인지 잘 모르겠지만, 하나하나 알아봅시다.


프로토콜이  /  gettable 및 settable (읽기 및 쓰기) 프로퍼티를 요구하면, 


protocol FullyNamed {

    var fullName: String { get set }

}

바로 이렇게.



해당 프로퍼티 요구사항은 / 상수 저장 프로퍼티 (let으로 선언하는것 or 읽기 전용 연산 프로퍼티로 충족(선언)되어서는 안됩니다.

자 천천히 심호흡하고 읽어보세요. 굉~~장히 당연한 말입니다. settable(쓰기)가 요구되는데 만약 let으로 선언되거나 읽기전용이면....안되겠죠!?!??!

settable을 요구하는데, settable연산을 할 수 없게 선언한거니까요. 이해가 잘 안되시는 분들을 위해 예제를 가져왔습니다. 



protocol FullyNamed {

    var fullName: String { get set }

}

struct Person: FullyNamed {

    let fullName: String//error! error: type 'Person' does not conform to protocol 'FullyNamed'

}

var john = Person(fullName: "John Appleseed")

let으로 선언된 것. 보이시나요? 어~~ 위에서는 상수 저장 프로퍼티였는데도 됐자나요~~~

그 때는 프로토콜에 { get } 만 있었죠? 이번엔 { get set }이 있다는 것을 꼭!!!! 기억해주세요 :)


프로토콜에서 set을 "요구" 했다는 것은 이 fullName의 값을 다른 값으로 설정할 수 있다는 말인데, let으로 선언하면 값을 바꿀 수 없겠죠?? 그러니까 에러가 나게 되죠.

Person이라는 타입은 FullyNamed라는 프로토콜을 "준수"하고 있지 않다구요.


이제, 읽기 전용 연산 프로퍼티로도 한번 해봅시다. 



protocol FullyNamed {

    var fullName: String { get set }

}

struct Person: FullyNamed {

    var name: String

    var fullName: String {

        return name

    }

//error! error: type 'Person' does not conform to protocol 'FullyNamed'

}

var john = Person(name: "John Appleseed")

자. 역시나 에러가 난 것을 볼 수 있습니다. 프로토콜이 set을 요구했는데, 읽기전용이라니..말도 안되죠? 


이제 

프로토콜이 gettable 및 settable 프로퍼티를 요구하면, 해당 프로퍼티 요구사항은 상수(constant) 저장 프로퍼티 또는 읽기 전용 연산 프로퍼티로 충족되서는 안됩니다.

가 무슨 소리인지 아시겠나요!??!





오늘은 여기까지..수고하셨어요 :) Zedd도 수고했어

오늘은 "프로퍼티" 요구사항을 봤는데, 다음시간에는 "메소드" 요구사항을 볼거에요 ~.~

프로토콜을 이해하시는데 도움이 되었길 바라며!!!

이해가 안되시거나 궁금한 점 또는 지적할 점 등은 댓글이나 PC화면 오른쪽 하단에서 볼 수 있는 채널서비스를 이용해주세요 :)

안녕~

반응형