서론
이 포스팅은 프로토콜-1과 프로토콜-2를 실습하는 내용입니다.
자세한 개념은 위 두 개의 프로토콜 가이드 포스팅을 참고해 주세요!
참고로... 이번 포스팅에서는 정의와 채택하는 방법을 주로 실습합니다.
Delegation 내용이 한 번에 이해하기가 힘들더라고요.
내용도 많고 중요하기도 해서 이 부분은 따로 포스팅하기로 했습니다.
따라서 이번 포스팅은 간단히 어떻게 사용하는지를 알아보는...
초간단 실습 포스팅으로 생각해주시면 감사하겠습니다 ㅎㅎ;
프로토콜 정의와 채택
프로토콜은 메서드와 프로퍼티, 특정 Task나 일부 기능에 적합한
다른 요구사항들의 청사진(상세한 계획)을 정의합니다.
프로토콜 정의는 protocol 키워드를 이용해 가능합니다.
protocol SomeProtocol {
// protocol definition goes here
}
이렇게 하면 SomeProtocol 프로토콜이 만들어지는 것입니다.
어렵지 않죠?
이를 채택하려면 콜론(:)을 붙이고 프로토콜 이름을 작성하면 됩니다.
protocol SomeProtocol {
// protocol definition goes here
}
struct SomeStructure: SomeProtocol {
// structure definition goes here
}
프로토콜의 이름에는 보통 able,ible,ing의 접미사를 붙인다고 합니다.
만약 클래스가 상속도 하고 프로토콜 채택도 한다면
슈퍼 클래스를 제일 앞에 쓰고 프로토콜을 뒤에 나열해 주세요.
class SomeClass: SomeSuperclass, SomeProtocol{
// class definition goes here
}
단 하나의 클래스만 상속할 수 있으므로
두 번째부터는 무조건 프로토콜입니다.
프로퍼티 요구사항
먼저 프로퍼티에 대한 내용부터 살펴보겠습니다.
프로퍼티 이름과 타입, gettable한지 아니면 gettable/settable한지 명시합니다.
protocol Naming {
var fullName: String { get }
var nickname: String { get set }
}
gettable만 가능한 것은 { get }을, settable도 되는건 { get set }으로 정의하는데요.
프로퍼티에서 setter만 존재할 수 없는 것처럼 프로토콜에서도 { set }처럼 set만 있는 것은 컴파일 에러가 발생합니다.
프로토콜에는 저장 프로퍼티인지 연산 프로퍼티인지 명시하지 않습니다.
이 말의 뜻은
프로토콜을 채택하여 구현할 때 어떤 것으로든 구현할 수 있다는 의미입니다.
struct Person: Naming {
var fullName: String = "unknown"
var nickname: String {
get {
return "cute " + fullName
}
set {
self.fullName = newValue
}
}
}
이렇게 fullName은 저장 프로퍼티로, nickname은 연산 프로퍼티로 선언할 수 있습니다.
프로토콜에 gettable로 정의해도 아래 코드처럼 setter를 정의할 수 있습니다.
struct Person: Naming {
var fullName: String {
get {
return "fullName"
}
set {
self.nickname = ""
}
}
var nickname: String {
get {
return "cute " + fullName
}
set {
self.fullName = newValue
}
}
}
이렇게 코드를 작성해도 정상 동작을 합니다.
하지만 프로토콜의 의미를 해치므로 자제하는게 좋겠죠?
마지막으로 gettable/settable한 프로퍼티는 let으로 선언할 수 없습니다.
상수는 값이 변하면 안 되는데 set이 가능하다는 것은 모순이므로
컴파일 에러가 발생합니다. 주의해 주세요!
또한,
프로토콜에 프로퍼티는 항상 var로 선언되어야 합니다.
프로토콜에 대한 내용이 아니라 연산 프로퍼티에 대한 에러 메시지가 출력되네요.
에러 내용이 "연산 프로퍼티는 let으로 선언할 수 없다"고 나오는 것이 특이한 것 같습니다.
타입 프로퍼티도 프로토콜 안에 정의할 수 있습니다.
문법은 동일하게 static을 붙이면 됩니다.
protocol Naming {
static var fullName: String { get }
var nickname: String { get set }
}
struct Person: Naming {
static var fullName: String = "unknown"
...
}
프로토콜에 static으로 정의하면 채택한 곳에서도 static을 적어줘야 해요.
class 키워드도 동일한 규칙을 따릅니다.
메서드 요구사항
프로토콜에서 메서드를 정의하는 방법을 알아봅시다.
메서드는 중괄호와 메서드 본문(body)을 제외하고
정의부만 작성해야 합니다.
protocol Naming {
func someMethod()
}
이런식으로요!
매개변수나 반환 타입을 설정할 때도 정의부만 작성해야 합니다.
protocol Naming {
func someMethod()
func someParameterMethod(num: Int)
func someReturnMethod(number: Int) -> Int
func someReturnMethod2() -> Int
}
파라미터 설정과 반환 타입 설정을 해보았습니다.
static을 붙여 타입 메서드임을 나타낼 수 있습니다.
프로퍼티와 동일하게 static을 붙여 타입 메서드를 정의할 수 있습니다.
protocol SomeProtocol {
static func someMethod()
static func someClassMethod()
}
이를 채택하여 사용할 때도 static이나 class를 작성해줘야 합니다.
class SomeClass: SomeProtocol {
static func someMethod() {
print("someMethod")
}
class func someClassMethod() {
print("someClassMethod")
}
}
class 키워드는 서브클래스가 해당 메서드를 오버라이드할 수 있음을 나타냅니다.
그래서 오버라이드가 필요한 경우에는 프로토콜에는 static으로, 클래스에는 class로 작성하면 됩니다.
위 코드에서는
class 키워드를 사용하는 것을 보여드리고 싶어서 클래스로 코드를 작성했는데요.
구조체도 static은 동일하게 구현하면 됩니다.
Mutating Method
메서드 중 mutating 메서드는 프로토콜에 mutating을 붙이면 됩니다.
protocol SomeProtocol {
mutating func someMethod()
}
struct SomeClass: SomeProtocol {
var number: Int = 0
mutating func someMethod() {
self.number += 1
}
}
프로토콜과 구현부에 mutating을 이용해 mutating 메서드를 정의했습니다.
여기에서 참조 타입인 클래스로 구현을 할 때는
class SomeClass: SomeProtocol {
var number: Int = 0
func someMethod() {
self.number += 1
}
}
mutating이 없어도 자신의 프로퍼티를 수정할 수 있으므로
mutating이 없어도 됩니다.
오히려 있으면 컴파일 에러가 발생합니다!
이니셜라이저 요구사항
이니셜라이저 요구사항도 정의부만 작성합니다.
protocol SomeProtocol {
init(someParameter: Int)
}
이를 채택하여 구현하는 클래스에서는
designated 이니셜라이저와 convenience 이니셜라이저 모두
required 수식어를 붙여야 합니다.
class SomeClass: SomeProtocol {
var number: Int
required init(someParameter: Int) {
self.number = someParameter
}
}
만약 required를 붙이지 않는다면
컴파일 에러가 발생하는데요.
required는 해당 프로토콜을 준수하는 클래스의 모든 서브클래스 역시
요구사항을 준수하는 것을 보장해주는 역할이기 때문에 반드시 작성해줘야 합니다.
에러 내용에도 나와있듯이 만약 final class라면 상속이 불가능하므로 붙여주지 않아도 됩니다.
final class SomeClass: SomeProtocol {
var number: Int
init(someParameter: Int) {
self.number = someParameter
}
}
final을 붙이면 에러가 사라집니다 ㅎㅎ
구조체는 애초에 상속이 불가능하므로 이것도 역시 requied가 필요 없습니다.
만약 override와 required를 동시에 써야 하는 상황이면 어떤 순서로 작성할까요?
class SomeClass {
init() {
}
}
class SubClass: SomeClass, SomeProtocol {
required override init() {
}
}
SubClass는 SomeClass를 상속하고 SomeProtocol을 채택합니다.
required와 override를 동시에 작성하여 이니셜라이저를 정의해야 하는데요.
이런 상황에서는 required를 작성하고 override를 작성하면 됩니다.
Delegation
프로토콜하면 Delegate가 정말 대표적인데요.
iOS에서 정말 많이 사용되는 디자인 패턴으로,
Delegation은 클래스나 구조체가 책임을
일부 다른 타입의 인스턴스로 전달(또는 위임)할 수 있게 하는 디자인 패턴입니다.
이 프로토콜을 채택할 타입에게 위임할 기능을 캡슐화한 프로토콜을 정의함으로서 구현할 수 있다고 나와있습니다..
처음에는 무슨 말인지 이해를 못했고 사실 지금도 제대로 이해를 하지는 못했어요 ㅎㅎ;;
이에 대해서는 조금 더 공부하고 따로 포스팅을 작성하도록 하겠습니다.
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.
swift, iOS, 스위프트, 개발, 코딩