프로토콜

프로토콜은 타입이다.

  1. 함수를 호출할 때, “프로토콜”을 파라미터로 전달할 수 있음.
  2. 함수에서 “프로토콜”을 반환할 수 있음.
  3. “프로토콜”을 변수에 할당할 수 있음.

구조체, 열거형, 클래스랑 비슷한 타입이라는 뜻이다. → 일급객체로 취급한다.(타입으로 사용하는 조건)

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()
//Remote프로토콜을 지정하면 달라짐.

let sbox: Remote = SetTopBox()
sboxtv.turnOn()
sbox.turnOff()
sbox.doNetflix()       //이건 안됨.

인스턴스를 찍어낼 때 프로토콜로 지정해서 찍어내면, 프로토콜 선언 메서드들만 사용할 수 있다. 그 외에 해당 함수의 프로토콜에서 지정된 함수 이외에 다른 메서드를 지정하고 싶다면, 다운캐스팅을 하면 된다.

(sbox as? SetTopBox)?.doNetflix()

프로토콜 타입 취급의 장점

  1. 배열로 담을 수 있음.
let electronic: [Remote] = [tv, sbox]   

for item in electronic {  
    item.turnOn()
}
  1. 파라미터로 사용할 수 있음.
func turnOnSomeElectronics(item: Remote) {
    item.turnOn()
}

turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)
  1. 프로토콜의 준수성 검사

특정타입이 프로토콜을 채택하고 있는지 확인

tv is Remote
sbox is Remote

프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능

electronic[0] is TV
electronic[1] is SetTopBox

업캐스팅(as) 무조건 성공

let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()

다운캐스팅(as?/as!)

let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()

프로토콜의 상속

실제로 프로토콜을 이용해서 상속을 하는 경우는 많이 없지만, 애플이 swift내에서 많은 것들을 프로토콜의 상속을 바탕으로 구조화해놓음. single

다중상속도 가능하다.

various

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() { }
}

프로토콜의 채택 및 전용 프로토콜 타입

protocol SomeProtocol: AnyObject {      // AnyObject는 클래스 전용 프로토콜
    func doSomething()
}

// 구조체에서는 채택할 수 없게 됨 ⭐️
struct AStruct: SomeProtocol {

}


// 클래스에서만 채택 가능
class AClass: SomeProtocol {
    func doSomething() {
        print("Do something")
    }
}

AnyObject는 프로토콜임. 그렇기 때문에 AnyObject를 범용적 타입으로 사용할 수 있었던 것이고 다운캐스팅(as? / as!)해서 구체적인 실제타입으로 사용할 수 있던 것.

프로토콜의 합성 문법

// 프로토콜을 합성하여 임시타입으로 활용 가능
protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}


// 하나의 타입에서 여러개의 프로토콜을 채택하는 것 당연히 가능 (다중 상속과 비슷한 역할)

struct Person: Named, Aged {
    var name: String
    var age: Int
}

프로토콜을 &로 연결해서, 프로토콜 두개를 병합해서 타입으로 사용하는 것 가능

@어트리뷰트 키워드

(1) 선언에 대한 추가정보 제공

@available(iOS 10.0, macOS 10.12, *)
class SomeType {      // "SomeType" 선언은 iOS 10.0 버전이상에서만 읽을 수 있음
    
}

(2) 타입에 추가정보 제공

func doSomething(completion :@escaping ()->()){

}
//나중에 배움

선택적인(구현해도 안해도 되는) 멤버 선언하기

  • 프로토콜에서 요구사항 구현시, 선택적인 멤버로 구현가능 하도록 변형가능 (다만, 본 기능은 오브젝티브C에서 지원하는 기능임)
  • @objc키워드를 프로토콜의 선언앞에 붙여서, 추가적인 정보 제공
 (오브젝티브C에서 읽을 수 있는 코드라는 의미)
  • @objc optional 을 멤버 앞에 선언 ➡ 해당 멤버는 선택적 요구사항으로 바뀜
 (클래스 전용 프로토콜이기 때문에, 구조체 / 열거형에서는 채택하지 못함)

@objc protocol Remote {
    @objc optional var isOn: Bool { get set }
    func turnOn()
    func turnOff()
    @objc optional func doNeflix()
}
//프로토콜 앞에는 "@objc"추가
//멤버 앞에는 "@objc optional"을 모두 추가

프로토콜의 확장 ➡ 디폴트 선언

프로토콜을 채택한 타입에서 실제 메서드 구현을 반복(코드 중복 구현)해야하는 불편함 제거하고 단순히 기본구현(default)을 제공하는 개념

protocol Remote {
    func turnOn()
    func turnOff()
}

class ~~

struct ~~

extension Remote {                         
    func turnOn() { print("리모콘 켜기") }    
    func turnOff() { print("리모콘 끄기") }   
    
    func doAnotherAction() {               
        print("리모콘 또 다른 동작")           
    }
}


var tv7 = TV1()
tv7.turnOn()
tv7.turnOff()
tv7.doAnotherAction()
// 따로 선언하지 않아도 기본값으로 작동됨.

여기에도 우선순위가 있다.

요구사항의 메서드 우선순위가 적용되는데, 이 때 프로토콜 메서드 테이블을 만들게 됨.

  1. (채택) 구현시 해당 메서드를 반환하고,
  2. 구현되지 않는다면 extenstion에서 기본으로 지정된 메서드가 나오게 됨.

vtable

class Ipad: Remote {    // Remote프로토콜을 채택한 클래스
    func turnOn() { print("아이패드 켜기") }

    func doAnotherAction() { print("아이패드 다른 동작") }
}   


//====================================

let ipad: Ipad = Ipad()
ipad.turnOn()           // 클래스 - V테이블
ipad.turnOff()          // 클래스 - V테이블
ipad.doAnotherAction()  // 클래스 - V테이블

//====================================

let ipad2: Remote = Ipad()
ipad2.turnOn()           // 프로토콜 - W테이블
ipad2.turnOff()          // 프로토콜 - W테이블
ipad2.doAnotherAction()  // 프로토콜 - Direct (직접 메서드 주소 삽입)

기본 요구사항은 Remote 프로토콜에 있는 turnOn()과 tuenOff()를 무조건 구현해야함.

[Class Virtual 테이블]

  • func turnOn() { print(“아이패드 켜기”) }
  • func turnOff() { print(“리모콘 끄기”) }
  • func doAnotherAction() { print(“아이패드 다른 동작”) }

[Protocol Witness 테이블] - 요구사항

  • func turnOn() { print(“아이패드 켜기”) }
  • func turnOff() { print(“리모콘 끄기”) }

프로토콜-프로토콜 확장-클래스-인스턴스 이 과정을 이해해보자.

때떄로 프로토콜의 용량이 클때는 Heap 영역에 저장하기도 한다. 기본적으로는 Stack영역에서 처리. 예를 들면, str타입은 애플이 구조체로 지정되어있고, 우리가 문자열 인스턴스를 찍어내면 stack에서 보통 처리가 되지만, 아주 긴 문자열의 경우는 Heap에서 처리된다고 함.


프로토콜 지향 프로그래밍

상속의 단점

  • 하나의 클라스만 상속가능하지, 다중 상속은 불가능.
  • 기본적인 상위클래스의 메모리 구조를 따라갈 수 밖에 없음. 필요하지 않은 속성이나 메서드도 다 받아야만 함.
  • 구조체, 열거형등은 클래스의 상속을 받지 못함.

connect

  • 여러 개의 프로토콜의 다중 상속이 가능함.
  • 메모리 구조에 대한 특정 요구사항이 없음. 필요한 속성/메서드만 채택이 가능함. -@obj Optional
  • 모든 타입에서 채택이 가능 (벨류 타입도 가능)
  • 확장을 통해 재정의가 가능하고, 채택을 통해 타입 제약도 가능.
  • 조합의 장점을 잘 살려서 재사용성과/구성을 높일 수 있다.