티스토리 뷰
[프로토콜이란]
- 프로토콜 Protocol : 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진
- 구조체, 클래스, 열거형은 프로토콜을 채택하여 특정 기능을 실행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있음
: 해당 프로토콜을 준수Conform함
- 프로토콜은 정의를 하고 제시를 할 뿐 스스로 기능을 구현하지 않음
- Java의 인터페이스와 비슷한 개념?
[프로토콜 정의]
- protocol 키워드를 사용
1 2 3 | protocol 프로토콜 이름{ 프로토콜 정의 } | cs |
- 프로토콜을 채택하려면 타입 이름 뒤에 콜론을 붙여준 후 채택할 프로토콜 이름을 쉼표로 구분하여 명시해줌
- 클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 다음부터 채택할 프로토콜을 나열해줌
[프로토콜 요구사항 - 프로퍼티 요구]
- 프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있음
- 프로퍼티를 읽기 전용으로 할지 읽고 쓰기가 가능하게 할지는 프로토콜에서 정해야 함
- 항상 var 키워드를 사용한 변수 프로퍼티로 정의
- 읽고 쓰기가 모두 가능한 프로퍼티는 프로퍼티 정의 뒤에 { get set }
- 읽기 전용 프로퍼티는 프로퍼티 정의 뒤에 { get }
- 쓰기 전용 프로퍼티는 원래 없음
- 타입 프로퍼티를 요구하려면 static 키워드를 사용하여 정의
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | protocol Sendable{ var from: String { get } var to: String { get } } class Message: Sendable{ var sender: String var from: String{ return self.sender } var to: String init(sender: String, receiver: String) { self.sender = sender self.to = receiver } } class Mail: Sendable{ var from: String var to: String init(sender: String, receiver: String) { self.from = sender self.to = receiver } } | cs |
위의 코드에서 from 프로퍼티는 프로토콜에서 읽기 전용으로 정의되었지만 실제로 프로토콜을 채택한 클래스에서 구현할 때는 읽고 쓰기가 가능한 프로퍼티로 구현해도 문제가 없음
[프로토콜 요구사항 - 메서드 요구]
- 프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수 있음
- 메서드의 실제 구현부는 제외하고 메서드의 이름, 매개변수, 반환 타입 등만 작성, 가변 매개변수 허용
- 매개변수 기본값을 지정할 수 없음
- 타입 메서드를 요구하려면 static 키워드를 사용하여 정의, 실제 구현할 때는 static, class 어느 쪽을 사용해도 무방함
1 2 3 4 5 6 7 8 9 10 | protocol Receivable{ func received(data: Any, from: Sendable) } protocol Sendable { var from: Sendable { get } var to: Receivable? { get } func send(data: Any) static func isSendableInstance(_ instance: Any) -> Bool } | cs |
프로토콜 타입의 인스턴스는 해당 프로토콜을 준수하는 타입의 인스턴스라고 생각할 수 있음
[프로토콜 요구사항 - 가변 메서드 요구]
- 프로토콜이 어떤 타입이든 간에 인스턴스 내부의 값을 변경해야 하는 메서드를 요구하려면 mutating 키워드를 명시해야 함
- 참조 타입(클래스)에서 해당 메서드를 구현할 때는 mutating 키워드를 사용하지 않아도 됨
- 값 타입(구조체, 열거형)에서 해당 메서드를 구현할 때는 mutating 키워드를 명시해야 함
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | protocol Resetable{ mutating func reset() } class Person: Resetable{ var name: String? var age: Int? func reset() { self.name = nil self.age = nil } } struct Point: Resetable{ var x: Int = 0 var y: Int = 0 mutating func reset() { self.x = 0 self.y = 0 } } enum Direction: Resetable{ case east, west, north, south, unknown mutating func reset() { self = Direction.unknown } } | cs |
[프로토콜 요구사항 - 이니셜라이저 요구]
- 클래스의 경우 생성자 요구에 부합하는 생성자를 구현할 때는 required 키워드를 붙인 요구 생성자로 구현해야 함
: 해당 클래스를 다른 클래스가 상속받는 경우 해당 생성자를 구현해야 함
- 클래스가 상속받을 수 없는 final 클래스라면 상속이 불가능하므로 required를 붙여줄 필요가 없음
- 특정 클래스에 프로토콜이 요구하는 생성자가 이미 구현되어 있는 상황에서 그 클래스를 상속받은 클래스가 있다면 required와 override를 모두 명시함
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | protocol Named{ var name: String { get } init(name: String) } struct Pet: Named{ var name: String init(name: String) { self.name = name } } class Person: Named{ var name: String required init(name: String) { self.name = name } } class School{ var name: String init(name: String) { self.name = name } } class MiddleSchool: School, Named{ override required init(name: String) { super.init(name: name) } } | cs |
[프로토콜의 상속과 클래스 전용 프로토콜]
- 프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있음
- 프로토콜의 상속 리스트에 class 키워드를 추가하여 프로토콜이 클래스 타입에만 채택될 수 있게 할 수 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | protocol Readable{ func read() } protocol Writeable{ func write() } protocol ReadSpeakable: Readable{ func speak() } protocol ReadWriteSpeakable: Readable, Writeable{ func speak() } class SomeClass: ReadWriteSpeakable{ func speak() { print("speak") } func read() { print("read") } func write() { print("write") } } | cs |
[프로토콜 조합과 프로토콜 준수 확인]
- 하나의 매개변수가 여러 프로토콜을 모두 준수하는 타입이어야 한다면 하나의 매개변수에 여러 프로토콜을 한번에 조합하여 요구할 수 있음
- AProtocol & BProtocol과 같이 표현 : 프로토콜 이름 사이에 &를 써줌
- 구조체나 열거형 타입은 조합할 수 없음
- 클래스 타입은 한 타입만 조합할 수 있음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | protocol Named{ var name: String { get } } protocol Aged{ var age: Int { get } } struct Person: Named, Aged{ var name: String var age: Int } class Car: Named{ var name: String init(name: String) { self.name = name } } class Truck: Car, Aged{ var age: Int init(name: String, age: Int) { self.age = age super.init(name: name) } } func function(to celebrator: Named & Aged){ print("\(celebrator.name), \(celebrator.age)") } let me = Person(name: "Me", age: 24) function(to: me) let variable: Car & Aged = Truck(name: "Truck", age: 13) | cs |
위의 코드에서 상수 me에 Person 타입의 인스턴스가 할당되었고 이는 Named와 Aged 프로토콜을 모두 준수하므로 함수의 매개변수가 될 수 있음
상수 variable에 Car를 상속받고 Aged 프로토콜을 준수하는 Truck의 인스턴스가 할당됨
- is 연산자를 사용하여 대상이 프로토콜을 준수하는지 확인 가능
- as? 연산자를 사용하여 다른 프로토콜로 다운캐스팅을 시도할 수 있음
- as! 연산자를 사용하여 다른 프로토콜로 강제 다운캐스팅을 할 수 있음
[프로토콜의 선택적 요구]
- 프로토콜의 요구사항 중 일부를 선택적 요구사항을 지정할 수 있음
- @objc 속성이 부여된 프로토콜이어야 함
: @objc : 해당 프로토콜을 Objective-C 코드에서 사용할 수 있도록 만듦
: Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있음 -> 열거형이나 구조체에서는 @objc 속성이 부여된 프로토콜은 채택할 수 없음
- optional 식별자를 요구사항의 정의 앞에 명시함
: 해당 요구사항의 타입은 옵셔널이 됨
: 메서드 자체의 타입이 옵셔널이 된 것임
- 선택적 요구사항은 그 프로토콜을 준수하는 타입에 구현되어 있지 않을 수 있으므로 옵셔널 체이닝을 통해 호출함
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import Foundation @objc protocol Moveable{ func walk() @objc optional func fly() } class Tiger: NSObject, Moveable{ func walk() { print("Tiger Walks") } } class Bird: NSObject, Moveable{ func walk() { print("Bird Walks") } func fly() { print("Bird Flys") } } let tiger = Tiger() let bird = Bird() tiger.walk() bird.walk() bird.fly() | cs |
[프로토콜 변수와 상수]
- 프로토콜 이름을 타입으로 갖는 변수나 상수에는 그 프로토콜을 준수하는 타입의 어떠한 인스턴스라도 할당할 수 있음
- 프로토콜 이름만으로 자기 스스로 인스턴스를 생성하고 초기화할 수는 없음
[위임을 위한 프로토콜]
- 위임Delegation : 클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 디자인 패턴
- 책무를 위임하기 위해 정의한 프로토콜을 준수하는 타입은 자신에게 위임될 일정 책무를 할 수 있다는 것을 보장함
- 위임은 사용자의 특정 행동에 반응하기 위해 사용되기도 하며, 비동기 처리에도 많이 사용함
- 위임 패턴은 애플의 프레임워크에서 사용하는 주요한 패턴 중 하나
- [~~]Delegate와 같은 이름의 프로토콜은 위임 패턴을 위해 정의된 프로토콜임