-
Swift - Protocol Initializer , SubscriptProgramming/Swift 2021. 12. 28. 18:28
안녕하세요 BeePeach입니다 :)
이번 포스팅에서는 지난번 포스팅에 이어 Protocol에서 Initializer와 Subscript를 정의하는 방법에 대해서 공부해보도록 하겠습니다.
Initializer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol SomeProtocol { init(age: Int) } Init을 정의하는 방법도 마찬가지로 body를 제외하고 헤더만 적어주면 됩니다.
어려울 게 없으니 바로 예시를 보겠습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol Figure { var name: String { get } init(name: String) } struct Triangle: Figure { let name: String init(name: String) { self.name = name } } Figure라는 프로토콜에 name 프로퍼티와 init(name:)을 정의했습니다.
그리고 Triangle이라는 struct를 정의했는데요 init(name:)을 요구하고 있으니 init(name:)을 정의하고 내부 구현은 우리가 원하는 대로 할 수 있습니다.
이 코드에서 짚고 넘어갈 부분은 protocol에서 name 프로퍼티와 init(name:)을 요구하고 있고 Triangle은 구조체이므로 name프로퍼티만 선언해주면 Memberwise initiailizer로 init(name:)이 자동으로 제공되죠?? 그래서 init(name:)을 생략해도 protocol 요구사항을 만족하지 못했다는 에러가 발생하지 않습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersstruct Triangle: Figure { let name: String } let triangle: Triangle = Triangle(name: "Triangle") init(name:)을 생략해도 에러가 발생하지 않죠?? 이렇게 init이 자동으로 생성된것도 protocol 요구사항을 만족한 것으로 취급합니다.
Struct는 상속이 없으므로 init을 정의하는게 쉽고 Class는 상속으로 인해 init의 구현이 복잡했던 게 기억나시나요??
(기억이 안나신다면 여기를 참고해주세요.)
Protocol의 init 요구사항을 class에서 만족시키려면 어떻게 해야 할까요???
Designated, Convenience 등등 다 구별을 해줘야 할까요???
정답은 바로 그렇지 않습니다! 입니다.
Protocol의 요구사항을 만족시킬 init을 구현할 때 designated init인지 convenience init인지 전혀 중요하지 않습니다.
둘 중 무엇을 쓰던 상관없습니다. 그냥 Protocol에서 정의한 파라미터만 지켜주면 됩니다.
하지만 한 가지 추가해야 하는 게 있는데요.
바로 required 키워드입니다.
Required init은 반드시 subclass에서 구현을 해야 했죠??
Class에는 상속이 존재하고 protocol에서 요구하는 init을 구현하기 위해서는 required 키워드를 사용해서 혹시 존재하게 될 subclass에서도 반드시 protocol 요구 init을 구현하도록 명시해줘야 합니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersclass Circle: Figure { let name: String // ERROR!! init(name: String) { self.name = name } } 백문이불여일견 바로 작성해보면 되겠죠??
넵 바로 required를 붙이라는 에러가 나오는 것을 확인할 수 있습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersclass Circle: Figure { let name: String required init(name: String) { self.name = name } } class Elipse: Circle { let origin: CGPoint required init(name: String) { self.origin = CGPoint(x: 0, y: 0) super.init(name: "Elipse") } } 이렇게 requried만 붙여주면 해결이 됩니다.
그리고 Elipse class는 Circle을 상속하고 있습니다. Circle에서는 이미 Figure protocol을 채용하고 있죠??
그래서 Elipse에서는 Figure를 또 적어주지 않습니다. Circle을 상속하면 자동적으로 Figure를 채용하게 됩니다.
그렇기 때문에 Elipse에서 init(name:)을 또 구현해준 것을 확인할 수 있습니다. 이때도 required 키워드는 꼭 작성해야 합니다.
그럼 몇 가지 주의해야 할 예시를 또 보도록 하겠습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol SomeProtocol { init() } class SomeSuperClass { init() { } } class SomeSubClass: SomeSuperClass, SomeProtocol { required override init() { } } 만약 subClass에서 프로토콜의 요구 init을 구현하려고 했는데 superClass에서 먼저 작성이 되어있다면 override를 해야겠죠??
그때는 override와 required를 같이 적어줍니다. 둘의 순서는 상관없습니다. 두 키워드가 모두 들어가기만 하면 됩니다.
주의하실 점은 required init은 override 키워드를 작성하지 않는다면서?!?라고 생각하실 수 있는데 프로토콜을 채용할 때는 조금 달라집니다. override 키워드를 작성하지 않는 경우는 위의 Circler과 Elipse의 경우입니다.
두 경우를 헷갈리기 쉬운데 정확하게 기억하지 못해도 Error을 알아서 잘 알려주기 때문에 큰 걱정하실 필요는 없습니다.
Protocol의 요구 init을 구현하는데 required를 붙이지 않아도 되는 경우가 1가지 있습니다.
required를 요구하는 이유가 'Class에는 상속이 존재하고 protocol에서 요구하는 init을 구현하기 위해서는 required 키워드를 사용해서 혹시 존재하게 될 subclass에서도 반드시 protocol 요구 init을 구현하도록 명시해줘야 합니다.'라고 설명했었죠???
그럼 만약 subclass가 절대 존재할 이유가 없다면? 그럼 required가 필요 없겠죠??
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol SomeProtocol { init() } final class FinalClass: SomeProtocol { init() { } } required 키워드가 없어도 에러가 나지 않는 것을 확인할 수 있습니다.
Failable Initializer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol ProtocolName { init?(patameter: Type) init!(parameter: Type) } Protocol에서 init을 정의할 때 non-failable init만 정의가 가능한 게 아니라 failable init도 정의가 가능합니다.
구현 방식은 init과 똑같이 body는 생략해줍니다. 대신 ?과 !가 포함됩니다.
나머지는 다를 게 없습니다.
그럼 바로 예시를 보도록 하겠습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol Failable { var name: String { get } init?(name: String) } class Desk: Failable { var name: String required init?(name: String) { if name.isEmpty { return nil } else { self.name = name } } } class Chair: Failable { var name: String required init(name: String) { self.name = name } } let desk: Desk? = Desk(name: "") let chair: Chair? = Chair(name: "ChaCha") desk?.name // nil chair?.name // ChaCha Failable이라는 프로토콜에는 init?(name:)이 정의되어 있습니다.
그렇다면 이 프로토콜을 채용하는 type에서는 init?(name:)을 구현해야겠죠??
Desk에서는 그대로 init?(name:)을 구현했습니다.
그런데 Chair에서는 init(name:)을 구현했습니다. 그래도 에러가 나지 않았네요??
Protocol의 요구 failable init은 non-failable init 또는 failable init으로 만족시키면 됩니다.
Failable init은 성공할 수 도 실패할 수 도 있으므로 무조건 성공한다 해서 문제가 될 건 없잖아요??
하지만 반대의 경우는 다릅니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol NonFailable { var name: String { get } init(name: String) } class Person: NonFailable { let name: String // ERROR!! required init?(name: String) { if name.isEmpty { return nil } else { self.name = name } } } Protocl에 non-failable init으로 정의되어 있는 경우에는 반드시 non-failable init으로 구현을 해줘야 합니다.
Non-Failable init은 반드시 초기화가 완료돼야 하기 때문에 실패란 존재하면 안 되기 때문입니다.
Subscript
Protocol에서 Subscript를 정의할 때도 마찬가지로 구현 부분은 생략합니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol SubscriptProtocol { subscript(name: Type) -> ReturnType { get set } } Subscript를 구현할 때 getter setter를 구분해줬던 게 기억나시나요??
Protocol에서 정의할 때는 헤더 부분은 똑같지만 body에는 프로퍼티와 비슷하게 get과 set을 작성합니다.
간단한 예시를 보도록 하겠습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersprotocol Indexable { subscript(index: Int) -> Int { get } } struct CustomData: Indexable { var data: [Int] = [] subscript(index: Int) -> Int { get { return 0 } set { data.insert(newValue, at: index) } } } 크게 의미 있는 코드는 아닙니다.
Indexable이라는 Protocol에 subscript가 정의되어 있습니다.
Protocol에 정의가 된 것처럼 파라미터와 returnType을 지켜서 CustomData에 subscript를 구현했습니다.
그리고 요구사항은 { get }으로 돼있지만 최소 요구사항 이므로 setter를 추가시켜줘도 문제가 없는 것을 확인할 수 있습니다.
참고자료
여러분의 새로운 도전을 응원합니다 | KxCoding
Mastering SwiftUI 더 적은 코드로, 더 멋진 UI 만들기
kxcoding.com
https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
Protocols — The Swift Programming Language (Swift 5.6)
Protocols A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of tho
docs.swift.org
728x90'Programming > Swift' 카테고리의 다른 글
Swift - Protocol Inheritance (0) 2022.01.02 Swift - Protocol as Type (0) 2021.12.29 Swift - Protocol (0) 2021.12.22 Swift - Extension (0) 2021.12.20 Swift - Deinitializer (0) 2021.12.19