swift는 protocol을 "일급객체(First Class Citizen)"로 취급한다
"protocol은 타입이다"
protocol Remote {
func turnOn()
func turnOff()
}
class TV: Remote {
func turnOn() {
print("TV 켜기")
}
func turnOff() {
print("TV 끄기")
}
}
struct SetTopBox: Remote {
func turnOn() {
print("셋톱박스 켜기")
}
func turnOff() {
print("셋톱박스 끄기")
}
func doNetflix() {
print("넷플릭스 하기")
}
}
let tv2 = TV() // 얘는 TV 타입
let tv: Remote = TV() // 프로토콜 타입으로 저장이 가능하다
tv.turnOn() // 대신, 프로토콜에서 정의한 메서드만 실행할 수 있다.
tv.turnOff()
let sbox: Remote = SetTopBox()
sbox.turnOn()
sbox.turnOff() // 얘네들만 사용할 수 있다
//sbox.doNetflix() // 에러 (호출할 수가 없어) <- 프로토콜 타입으로 인식하고 있기 때문에
// 다운캐스팅을 통해 실행
(sbox as! SetTopBox).doNetflix()
장점
// 프로토콜의 타입 취급의 장점 - 1 ⭐️
//
let electronic: [Remote] = [tv, sbox] // 프로토콜의 형식으로 담겨있음
for item in electronic { // 켜기, 끄기 기능만 사용하니 타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
item.turnOn()
}
// 프로토콜의 타입 취급의 장점 - 2 ⭐️
func turnOnSomeElectronics(item: Remote) {
item.turnOn()
}
turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)
프로토콜 준수성 검사
// 1) is연산자 ==============================
// 양방향 가능
// 특정타입이 프로토콜을 채택하고 있는지 확인
tv is Remote
sbox is Remote
// 프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능
electronic[0] is TV
electronic[1] is SetTopBox
// 2) as연산자 ==============================
// 업캐스팅(as)
let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()
// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
//(electronic[1] as? SetTopBox)?.doNetflix()
protocol Remote {
func turnOn()
func turnOff()
}
protocol AirConRemote {
func Up()
func Down()
}
protocol SuperRemoteProtocol: Remote, AirConRemote { // 다중 상속 가능
// func turnOn()
// func turnOff()
// func Up()
// func Down()
func doSomething() // 추가적인 요구사항
}
// 그냥 다 구현해주면 된다.
class HomePot: SuperRemoteProtocol {
func turnOn() { }
func turnOff() { }
func Up() { }
func Down() { }
func doSomething() { }
}
// AnyObject라는 프로토콜을 상속
protocol SomeProtocol: AnyObject { // AnyObject는 "클래스 전용 프로토콜"
func doSomething()
}
// 구조체에서는 채택할 수 없게 됨 ⭐️
//struct AStruct: SomeProtocol {
//
//}
// 클래스에서만 채택 가능
class AClass: SomeProtocol {
func doSomething() {
print("Do something")
}
}
protocol Named {
var name: String { get}
}
protocol Aged {
var age: Int { get }
}
// 하나의 구조체에서 여러 프로토콜 채택 가능
struct Person: Named, Aged {
// 저장 속성으로 구현해줬다
var name: String
var age: Int
}
// 프로토콜의 합성
// 프로토콜 두 개를 병합해서 타입으로 사용
// "두 가지 프로토콜을 모두 채택한 타입"
func wishHappyBirthday(to celebrator: Named & Aged) { // 임시적인 타입(두 가지 프로토콜을 채택한 타입)으로 인식
print("생일축하해, \(celebrator.name), 넌 이제 \(celebrator.age)살이 되었구나!")
}
// 별건 없고, 그냥 이렇게도 타입으로 쓸 수 있구나~ 하고 이해하면 된다
let birthdayPerson = Person(name: "홍길동", age: 20)
wishHappyBirthday(to: birthdayPerson)
// 하나의 타입으로 사용이 가능하다
let whoIsThis: Named & Aged = birthdayPerson
@available, @objc, @escaping, ...
@available(iOS 10.0)
class MyClass {
func doSomething(completion: @escaping() -> () ) {
}
// @objc 추가
@objc protocol Remote {
// @objc optional 추가 -> 채택하는 곳에서 구현해도 되고 안해도 된다.
// -> 선택적 멤버로 선언한다는 뜻
@objc optional var isOn: Bool { get set }
func turnOn()
func turnOff()
@objc optional func doNeflix()
}
class TV: Remote {
var isOn = false // 지워도 상관 없다. (optional)
func turnOn() {}
func turnOff() {}
}
let tv1: TV = TV()
print(tv1.isOn) // 이거 하려면 isOn 구현해줘야 하겠지
/*@objc는 클래스 전용 프로토콜이기 때문에, 구조체나 열거형에서는 아예 채택이 불가능*/
protocol Remote {
func turnOn()
func turnOff()
}
// 채택 ===> 실제구현해야함(여러타입에서 채택한다면 반복적으로 구현해야하는 점이 불편)
// 동일한 구현 내용을 코드를 반복해서 쓰고 있다...
class TV1: Remote {
//func turnOn() { print("리모콘 켜기") }
//func turnOff() { print("리모콘 끄기") }
// 만약 구현을 한다면, 이걸로 작동
func turnOn() { print("티비 켠다아") }
// 요구사항 메서드가 아닌걸 여기서 구현한다면, 타입에 따른 선택 (우선순위x)
func doAnotherAction() {
print("TV의 또 다른 동작")
}
}
struct Aircon1: Remote {
//func turnOn() { print("리모콘 켜기") }
//func turnOff() { print("리모콘 끄기") }
}
// 동일한 구현 내용 (리모콘 켜기, 리모콘 끄기) -> 불편하다
// 아래처럼 확장하면, 위에 다 주석때려도 정상적으로 함수 실행 가능하다
// 프로토콜의 확장 -> 디폴트 구현을 제공한다
extension Remote { // (요구사항의 메서드 우선순위 적용 - 프로토콜 메서드 테이블 만듦)
func turnOn() { print("리모콘 켜기") } // 1. (채택)구현시 해당 메서드 2. 기본 메서드
func turnOff() { print("리모콘 끄기") }
func doAnotherAction() { // (요구사항 메서드 X - 테이블 만들지 않음)
print("리모콘 또 다른 동작") // 타입에 따른 선택 (Direct Dispatch)
}
}
// 프로토콜 테이블 : 한번 만들면 불변
// 우선순위 (처음부터 있던 메서드(반드시 요구)) : 실제로 구현한거 > 확장에서 (기본으로) 구현한거
// 우선순위x (확장에서 새로 만든 메서드(반드시 요구x))
// 해당 변수의 타입에 따라 달라짐
// 프로토콜 타입 -> 확장에서 만든 함수
// 클래스 타입 -> 클래스에서 만든 함수
// 반드시 요구하는 요구사항 메서드는 "우선순위" 고려해서 작동된다!! - Witness Table
// 반드시 요구하지 않는 요구사항 메서드는 타입에 따라 선택된다 - Direct Dispatch
var tv2: Remote = TV1() // 프로토콜로 저장하면 프로토콜로 작동
tv2.doAnotherAction()
var tv3: TV1 = TV1() // 원래 타입으로 저장하면 원래 타입에서 정의한 걸로 작동
var tv4 = TV1() // 원래 타입으로 저장하면 원래 타입에서 정의한 걸로 작동
tv3.doAnotherAction()
tv4.doAnotherAction()
// 위에서 extension한 프로토콜 Remote 이용한다
class Ipad: Remote {
func turnOn() { print("아이패드 켜기") }
func doAnotherAction() { print("아이패드 다른 동작") }
}
/*
=> 클래스 테이블 (Virtual table)
func turnOn() { print("아이패드 켜기") }
func turnOff() { print("리모콘 끄기") }
func doAnotherAction() { print("아이패드 다른 동작") }
*/
/*
=> 프로토콜 테이블 (Witness table)
func turnOn() { print("아이패드 켜기") }
func turnOff() { print("리모콘 끄기") }
*/
// 서로 다른 타입으로 변수 생성해보자
let ipad: Ipad = Ipad()
let ipad2: Remote = Ipad()
ipad.turnOn() // 클래스 - V테이블
ipad.turnOff() // 클래스 - V테이블
ipad.doAnotherAction() // 클래스 - V테이블
ipad2.turnOn() // 프로토콜 - W테이블
ipad2.turnOff() // 프로토콜 - W테이블
ipad2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
struct SmartPhone: Remote {
func turnOn() { print("스마트폰 켜기") }
func doAnotherAction() { print("스마트폰 다른 동작") }
}
// => 구조체는 따로 메서드 테이블이 없다. 원래부터 direct로 주소 삽입
/*
=> 프로토콜 테이블 (Witness table)
func turnOn() { print("스마트폰 켜기") }
func turnOff() { print("리모콘 끄기") }
*/
// 서로 다른 타입으로 변수 생성
var iphone: SmartPhone = SmartPhone()
iphone.turnOn() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.turnOff() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.doAnotherAction() // 구조체 - Direct (직접 메서드 주소 삽입)
var iphone2: Remote = SmartPhone()
iphone2.turnOn() // 프로토콜 - W테이블
iphone2.turnOff() // 프로토콜 - W테이블
iphone2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
protocol Remote {
func turnOn()
func turnOff()
}
extension Remote {
func turnOn() { print("리모콘 켜기") }
func turnOff() { print("리모콘 끄기") }
}
protocol Bluetooth {
func blueOn()
func blueOff()
}
// 대문자 Self : 해당 타입을 의미한다 (타입 자신을 의미) (Bluetooth를 채택한 SmartPhone)
// 소문자 self : 타입 내에서 인스턴스 자신을 의미
// Remote 프로토콜을 채택 하였는지 여부. (채택한 경우에만, Bluetooth 프로토콜 확장이 적용된다.)
extension Blutooth where Self: Remote {
func blueOn() { print("블루투스 켜기") }
func blueOff() { print("블루투스 끄기") }
}
// 이렇게 하면, 기본 제공 다 먹을 수 있다
class SmartPhone: Remote, Bluetooth {
}
// 이거만 하려면, 직접적으로 blueOn, blueOff를 구현해주어야 한다!!
// 즉, Bluetooth extension이 적용되지 않는 것
class Ipad: Bluetooth {
func blueOn() {
print("hih")
}
func blueOff() {
print("ihi")
}
}