Swift | Protocol - 2
Protocol ?
Protocol - 1에서 다뤘던 것들은
기본적으로 사용하는 이유와
전반적으로 기본적으로 속성과 메서드들을 정의하고 사용하는 방법과
자주 사용하진 않는다고 하는 Protocol의 생성자와 실패 가능 생성자에 대해서 다루었다
우선 저번에도 말했다 시피 공통된 함수를 만들어 활용할 수 있는 효율 좋은 녀석이라고 말했다
이번에 엘런 강의를 들으며 정리하는 부분은 프로토콜은 1급 객체라고 알 수 있었으며
타입 캐스팅을 사용할 수 있는 부분과
신기하게도 반드시 구현해야하는 걸로 알고 있었던 프로토콜 메서드를
@objc를 선언하면 선택적으로 구현해도 되고 안해도 되는 걸로 변경이 된다고 하는 부분을 알게되었다
마지막으로 프로토콜을 확장에 적용하여 클래스에 적용하는 것도 다루었는데
이 부분에 대해서 정리하고자 한다
Protocol은 타입이다
First Class Citizen(일급객체)이기 때문에 타입(형식)으로 사용할 수 있음
여기서 1급객체를 Protocol - 1 에서 말했지만
복습차원에서 다시 말하자면
변수에 저장을 할 수 있어야 하며 함수를 인자로 전달 할 수 있어야하며
함수의 리턴값으로 사용할 수 있어야하는데
프로토콜은 이와 부합한다
// 프로토콜은 ===> First Class Citizen(일급객체)이기 때문에, 타입(형식)으로 사용할 수 있음
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 tv = TV()
tv.turnOn()
tv.turnOff()
let sbox = SetTopBox()
sbox.turnOn()
sbox.turnOff()
//sbox.doNetflix()
class IPad: Product {
var id: String { "아이패드123453" }
var name: String { get { "ipad" } set { newValue }}
static var type: String = "아이패드"
func playIpad() {
print("funny ipad")
}
}
Protocol 타입의 장점
타입의 장점은 타입캐스팅을 사용하지 않아도 된다
그리고 1급 객채기 때문에 그냥 매개변수에 집어넣어서 활용도 가능하다
// 프로토콜의 타입 취급의 장점 - 1 ⭐️
// 프로토콜의 형식으로 담겨있음
let electronic: [Product] = [imac, ipad]
for item in electronic {
//타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
item.id
}
// 프로토콜의 타입 취급의 장점 - 2 ⭐️
func idSomeElectronics(item:Product) {
item.id
}
idSomeElectronics(item: imac)
idSomeElectronics(item: ipad)
Protocol 준수성 검사
is, as 연산자를 사용하자
is 연산자 : 특정 타입이 프로토콜을 채택하고 있는지 확인 (참 또는 거짓)
as 연산자 : 타입 캐스팅(특정 인스턴스를 프로토콜로 변환하거나, 프로토콜을 인스턴스 실제형식으로 캐스팅)
//is 연산자
//is 연산자를 사용해서 프로토콜을 채택하고 있는지 확인
ipad is Product
imac is Product
electronic[0] is imac
electronic[1] is ipad
//as 연산자
//up casting
let newIpad = ipad as Product
newIpad.id
let newImac = imac as Product
newImac.id
//down casting
let ipad2: IPad? = electronic[1] as? IPad
ipad2?.playIpad()
is 연산자를 사용해서 프로토콜을 채택하고 있는지 유무를 확인할 수 있다
채택을 하고 있다면 반드시 true를 반환할 것이고
그렇지 않다면 false를 반환될 것이다
위 예제에서는 반드시 true가 나오는 예제를 작성해보았다
다음으로
as 연산자를 사용해
업캐스팅과 다운캐스팅이 가능한데
업 캐스팅은 당연히 위로 올라가는 거라고 생각하면 반드시 사용가능하며
다운캐스팅은 혹시나 사용못할 가능성이 농후하기 때문에(값이 없는 가능성을 배재할 수 없지)
옵셔널로 사용하여 코드를 짠 형태이다
프로토콜도 상속과 다중상속이 가능
프로토콜도 클래스 처럼 상속이 가능하다
프로토콜끼리 상속구조를 만드는것이 가능한데
상속 구조를 만들게 되면 상속한 프로토콜에서 다 구현을 하지 않아도 된다
프로토콜을 채택한 구조체나 클래스에서 채택하면 된다
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
클래스 전용 프로토콜을 선언할 수 있다
키워드는 'AnyObject'이다
단순하게 우리는 여태까지 protocol 프로토콜 명 {} 이렇게 사용했지만
클래스 전용 프로토콜을 사용 하고 싶다면
protocol 프로토콜명: AnyObject{} 로 작성하면 클래스 전용 프로토콜이 만들어진다
// 클래스 전용 프로토콜로 선언
// (AnyObject프로토콜을 상속)
protocol SomeProtocol: AnyObject { // AnyObject는 클래스 전용 프로토콜
func doSomething()
}
// 구조체에서는 채택할 수 없게 됨 ⭐️
//struct AStruct: SomeProtocol {
//
//}
// 클래스에서만 채택 가능
class AClass: SomeProtocol {
func doSomething() {
print("Do something")
}
}
프로토콜의 합성
프로토콜을 구조체나 클래스 뒤에 여러개 붙일 수 있다
class 클래스명: 프로토콜, 프로토콜 이런형식으로 작성할 수 있다
그리고 프로토콜 두개 병합하는 문법도 존재하고 있다
사용하려면 &를 사용하여 연결시키면 된다고 한다
// 프로토콜을 합성하여 임시타입으로 활용 가능 ⭐️
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 // 임시적인 타입으로 저장 (두개의 프로토콜을 모두 채택한 타입만 저장 가능)
프로토콜의 선택적인 사용
지금까지 배웠던 프로토콜은 선택적이 아닌 반드시 구현해야한다고 배웠다
이번시간에 @objc를 앞에 붙이게 되면 선택적으로 구현할 수 있다는 걸 알게되었다
사용방법
1. 프로토콜 앞에 @objc 추가
2.멤버 앞에 @objc optional을 추가
@objc protocol Remote {
@objc optional var isOn: Bool { get set }
func turnOn()
func turnOff()
@objc optional func doNeflix()
}
class TV: Remote {
var isOn = false
func turnOn() {}
func turnOff() {}
}
여기서 구현시
함수값이 없을 수도 있기 때문에
옵셔널 체이닝이 필요하다
let tv1: TV = TV()
print(tv1.isOn) // Bool타입
let tv2: Remote = TV()
print(tv2.isOn) // Bool? 타입 (선택적 구현 사항이기 때문에 해당 멤버가 없으면 ===> nil로 반환)
tv2.doNeflix?() // (선택적으로 선언했기 때문에, 함수가 없을 수도 있음 ===> 옵셔널체이닝 필요)
Protocol + Extension
= 프로토콜 지향 프로그래밍
protocol Remote {
func turnOn()
func turnOff()
}
// 채택 ===> 실제구현해야함(여러타입에서 채택한다면 반복적으로 구현해야하는 점이 불편)
class TV1: Remote {
//func turnOn() { print("리모콘 켜기") }
//func turnOff() { print("리모콘 끄기") }
}
// 채택 ===> 실제구현해야함(여러타입에서 채택한다면 반복적으로 구현해야하는 점이 불편)
struct Aircon1: Remote {
//func turnOn() { print("리모콘 켜기") }
//func turnOff() { print("리모콘 끄기") }
}
extension Remote { // (요구사항의 메서드 우선순위 적용 - 프로토콜 메서드 테이블 만듦)
func turnOn() { print("리모콘 켜기") } // 1. (채택)구현시 해당 메서드 2. 기본 메서드
func turnOff() { print("리모콘 끄기") } // 1. (채택)구현시 해당 메서드 2. 기본 메서드
func doAnotherAction() { // (요구사항 메서드 X - 테이블 만들지 않음)
print("리모콘 또 다른 동작") // 타입에 따른 선택 (Direct Dispatch)
}
}
// 클래스 ⭐️ ==================================================
class Ipad: Remote {
func turnOn() { print("아이패드 켜기") }
func doAnotherAction() { print("아이패드 다른 동작") }
}
/**=================================================
[Class Virtual 테이블]
- func turnOn() { print("아이패드 켜기") }
- func turnOff() { print("리모콘 끄기") }
- func doAnotherAction() { print("아이패드 다른 동작") }
====================================================**/
let ipad: Ipad = Ipad()
ipad.turnOn() // 클래스 - V테이블
ipad.turnOff() // 클래스 - V테이블
ipad.doAnotherAction() // 클래스 - V테이블
// 아이패드 켜기
// 리모콘 끄기
// 아이패드 다른 동작
/**=====================================
[Protocol Witness 테이블] - 요구사항
- func turnOn() { print("아이패드 켜기") }
- func turnOff() { print("리모콘 끄기") }
========================================**/
let ipad2: Remote = Ipad()
ipad2.turnOn() // 프로토콜 - W테이블
ipad2.turnOff() // 프로토콜 - W테이블
ipad2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 아이패드 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작
// 구조체 ⭐️ ==================================================
struct SmartPhone: Remote {
func turnOn() { print("스마트폰 켜기") }
func doAnotherAction() { print("스마트폰 다른 동작") }
}
/**=====================================
[구조체] - 메서드 테이블이 없음
========================================**/
// 본래의 타입으로 인식했을때
var iphone: SmartPhone = SmartPhone()
iphone.turnOn() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.turnOff() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.doAnotherAction() // 구조체 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 스마트폰 다른 동작
/**=====================================
[Protocol Witness 테이블] - 요구사항
- func turnOn() { print("스마트폰 켜기") }
- func turnOff() { print("리모콘 끄기") }
========================================**/
// 프로토콜의 타입으로 인식했을때
var iphone2: Remote = SmartPhone()
iphone2.turnOn() // 프로토콜 - W테이블
iphone2.turnOff() // 프로토콜 - W테이블
iphone2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작
Protocol Extension 적용 제한
- 프로토콜 확장에서 where절을 통해, 프로토콜의 확장의 적용을 제한 가능
- "특정 프로토콜"을 채택한 타입에만 프로토콜 확장이 적용되도록 제한
where Self: 특정프로토콜
- 특정 프로토콜을 채택하지 않으면, 프로토콜의 확장이 적용되지 않기 때문에
(확장이 없는 것과 동일하게) 메서드를 직접구현 해야함
protocol Remote {
func turnOn() // 요구사항
func turnOff() // 요구사항
}
extension Remote {
func turnOn() { print("리모콘 켜기") }
func turnOff() { print("리모콘 끄기") }
}
protocol Bluetooth {
func blueOn()
func blueOff()
}
// 특정 프로토콜을 채택한 타입에만 프로토콜 확장이 적용되도록 제한
// (Remote 프로토콜을 채택한 타입만 확장 적용 가능)
extension Bluetooth where Self: Remote { // 본 확장의 적용을 제한시키는 것 가능 (구체적 구현의 적용범위를 제한)
func blueOn() { print("블루투스 켜기") }
func blueOff() { print("블루투스 끄기") }
}
// Remote프로토콜을 채택한 타입만 Bluetooth 확장이 적용됨
// Remote프로토콜을 채택하지 않으면 ===> 확장이 적용되지 않기 때문에 직접 구현 해야함
class SmartPhone: Remote, Bluetooth {
}
let sphone = SmartPhone()
sphone.turnOn() // 리모콘 켜기
sphone.turnOff() // 리모콘 끄기
sphone.blueOn() // 블루투스 켜기
sphone.blueOff() // 블루투스 끄기
위에서 본다며 스마트폰 클래스에서 Remote를 채택하지 않으면 에러가 나진 않지만
Bluetooth만 작성시 직접 메서드를 구현해야한다
이를 통해서 특정한 상황에마 채택하여 사용할 수 있는 확장 제한 을 둘 수있는
extension protocol where Self:protocol1 에 대해서
배울 수 있었다
엘런강의 참조