iOS/Swift

Swift No.21

ParkSeongGeun 2024. 1. 28. 17:48

프로토콜의 상속

  • 프로토콜도 상속이 가능 / 다중 상속이 가능
    • 여러가지 요구사항의 나열일뿐
import UIKit

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 SomeProtocol: AnyObject {      // AnyObject는 클래스 전용 프로토콜
    func doSomething()
}

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

// 클래스에서만 채택 가능
class AClass: SomeProtocol {
    func doSomething() {
        print("Do something")
    }
}
  • 프로토콜 합성(Protocol Composition) 문법
    • 프로토콜을 합성하여 임시타입으로 활용 가능
    • 프로토콜 두개를 병합해서 사용하는 문법(&로 연결)
import UIKit

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: 25)
wishHappyBirthday(to: birthdayPerson)

// 임시적인 타입으로 저장 (두개의 프로토콜을 모두 채택한 타입만 저장 가능)
let whoIsThis: Named & Aged = birthdayPerson

프로토콜의 선택적 요구사항의 구현

  • 채택한 곳에서 구현을 해도 되고, 안해도 되는 요구사항
  • Optional Protocol Requirements
  • [어트리뷰트(attribute)]
    • 컴파일러에게 알려주는 특별한 신호이자, 추가적인 정보를 제공(2가지 종류가 존재)
    • 선언에 대한 추가정보 제공
    • 타입에 대한 추가정보 제공
// 1) 선언에 대한 추가정보 제공
@available(iOS 10.0)
class MyClass {
        ...
}

// 2) 타입에 추가정보 제공
func doSomething(completion: @escaping () -> ()) {
        ...
}
  • 선택적인(구현해도 되고 안해도 되는) 멤버 선언하기
    • 프로토콜에서 요구사항 구현시, 선택적인 멤버로 구현가능 하도록 변형가능
      • 다만, 본 기능은 오브젝티브C에서 지원하는 기능임
    • @objc키워드를 프로토콜의 선언앞에 붙여서 추가적인 정보 제공
      • 오브젝티브C에서 읽을 수 있는 코드라는 의미
    • @ objc optional 을 멤버 앞에 선언 → 해당 멤버는 선택적 요구사항으로 바뀜
      • 클래스 전용 프로토콜이기 때문에 구조체 / 열거형에서는 채택하지 못함
@objc protocol MyProtocol {
        var name: String { get }
        @objc optional var is isOn: Bool { get set }
        ...
        @objc optional func doSomething()
}

프로토콜의 확장(Protocl Extension)

  • 프로토콜의 확장 - 프로토콜 지향 프로그래밍 🚌
  • 프로토콜을 채택한 모든 타입에서, 실제 구현을 계속적으로 반복해야하는 불편함을 덜기 위해 “확장” 을 제공해서 메서드의 디폴트 구현을 제공
    • 코드의 중복을 피할 수 있음
import UIKit

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

extension Remote {
    func turnOn() { print("리모콘 온") }
    func turnOff() { print("리모콘 오프") }

    func doAnotherAction() {
        print("리모콘 다른 동작")
    }
}
  • 요구사항의 메서드 우선순위 적용 - 프로토콜 메서드 테이블을 제작
    • turnOn, turnOff
      1. (채택) 구현시 해당 메서드
      2. 기본 메서드
    • doAnotherAction
      • 요구사항 메서드가 아님 → 테이블을 만들지 않음
      • 타입에 따른 선택 (Direct Dispatch)
  • 프로토콜의 확장을 통한 다형성 제공 - 프로토콜 지향 프로그래밍
    • 클래스, Data 영역에 (프로토콜을 위한) 테이블을 추가로 만듦0
import UIKit

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

extension Remote {
    func turnOn() { print("리모콘 온") }
    func turnOff() { print("리모콘 오프") }

    func doAnotherAction() {
        print("리모콘 다른 동작")
    }
}

// 클래스 ⭐️
class Ipad: Remote {
    func turnOn() { print("ipad on") }
    func doAnotherAction() { print("ipad another action") }
}

/** [Class Virtual Table]
 - func turnOn() : ipad on
 - func turnOff() : 리모콘 오프
 - func doAnotherAction : ipad another action
**/

let ipad: Ipad = Ipad()
ipad.turnOn() // 클래스 - VTable
ipad.turnOff() // 클래스 - VTable
ipad.doAnotherAction() // 클래스 - VTable

/**
 [Protocol Witness Table] - 요구사항
 - func turnOn() : ipad on
 - func turnOff() : 리모콘 오프
**/

let ipad2: Remote = Ipad()
ipad2.turnOn()             // 프로토콜 - WTable(Witness Table)
ipad2.turnOff()            // 프로토콜 - WTable
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 (직접 메서드 주소 삽입)

// 스마트폰 켜기
// 리모콘 오프
// 리모콘 다른 동작

프로토콜지향 프로그래밍

  • 상속의 관점에서 생각해보는 프로토콜

  • 클래스와 상속의 단점
    • 하나의 클래스만 상속가능 (다중 상속 불가능)
    • 기본적인 상위클래스의 메모리 구조를 따라갈 수 밖에 없음
      • 필요없는 속성, 메서드도 상속됨
    • 클래스(레퍼런스 타입)에서만 가능
  • 프로토콜 지향 프로그래밍
    • 여러개의 프로토콜 채택 가능 (다중 상속과 유사)
    • 메모리 구조에 대한 특정 요구사항 없음
      • 필요한 속성/메서드만 채택도 가능 - @optional
    • 모든 타입에서 채택 가능 (값 타입도 가능)
    • 확장에서 구체적정의(재정의 가능)
      • 채택하는 타입 제약도 가능
    • 타입으로 사용가능하므로 활용성⬆️
    • 조합의 장점을 잘 살려서, 보다 나은 구성,재사용성을 높일 수 있음
    • 프로토콜 지향 프로그래밍을 잘 사용하면, 애플이 이미 만들어 놓은 데이터타입에도 채택하여 활용 가능

프로토콜 확장의 적용 제한

  • 확장의 적용을 제한
    • 프로토콜 확장에서, where 절을 통해, 프로토콜 확장의 적용을 제한 가능
    • 즉, Remote 프로토콜을 채택한 타입에만 Bluetooth 프로토콜의 확장이 적용
    • Remote 프로토콜을 채택하지 않은 타입은 Bluetooth 프로토콜을 채택할 수 있지만, 확장(기본구현)은 제공이 안됨 (무조건 직접 구현 해야함)
protocol Bluetooth {
        func blueOn()
        func blueOff()
}

extension Bluetooth where Self: Remote {
        func blueOn() { print("bluetooth on") }
        func blueOff() { print("bluetooth off") }
}
반응형