본문 바로가기

iOS-Study

iOS Study : 7주차 - 프로토콜 지향 프로그래밍(P.O.P)

프로토콜 지향 프로그래밍 - P.O.P

  • 애플에서 스위프트는 프로토콜 지향 언어(Protocol-Oriented Language)라고 했다.
  • 스위프트는 클래스로 구현된 타입이 없고 대부분 구조체로 기본 타입이 구현되어 있는데
    이는 프로토콜과 익스텐션의 조화를 통해 가능한 것이다.

프로토콜 초기 구현

익스텐션을 통한 프로토콜의 실제 구현

// 무언가를 수신할 수 있는 기능
protocol Receiveable {
    func received(data: Any, from: Sendable)
}

extension Receiveable {
    // 메시지를 수신합니다.
    func received(data: Any, from: Sendable) {
        print("\(self) received \(data) from \(from)") 
    }
}

// 무언가를 발신할 수 있는 기능 
protocol Sendable {
    var from: Sendable { get }
    var to: Receiveable? { get }
    func send(data: Any)
    static func isSendableInstance(_ instance: Any) -> Bool 
}

extension Sendable {
    // 발신은 발신 가능한 객체, 즉 Sendable 프로토콜을 준수하는 타입의 인스턴스여야 합니다. 
    var from: Sendable {
        return self 
    }

    // 메시지를 발신합니다. 
    func send(data: Any) {
        guard let receiver: Receiveable = self.to else { 
            print("Message has no receiver")
            return
        }

        // 수신 가능한 인스턴스의 received 메서드를 호출합니다.
        receiver.received(data: data, from: self.from) 
    }

    static func isSendableInstance(_ instance: Any) -> Bool {
        if let sendableInstance: Sendable = instance as? Sendable {
            return sendableInstance.to != nil 
        }
        return false
    }
}

// 수신, 발신이 가능한 Message 클래스 
class Message: Sendable, Receiveable {
    var to: Receiveable?
}

// 수신, 발신이 가능한 Mail 클래스 
class Mail: Sendable, Receiveable {
    var to: Receiveable?
}

// 두 Message 인스턴스를 생성합니다.
let myPhoneMessage: Message = Message() 
let yourPhoneMesssage: Message = Message()

// 아직 수신받을 인스턴스가 없습니다.
myPhoneMessage.send(data: "Hello") // Message has no receiver

// Message 인스턴스는 발신과 수신이 모두 가능하므로 메시지를 주고 받을 수 있습니다. 
myPhoneMessage.to = yourPhoneMesssage
myPhoneMessage.send(data: "Hello") // Message received Hello from Message

// Mail 인스턴스를 두 개 생성합니다.
let myMail: Mail = Mail()
let yourMail: Mail = Mail()

myMail.send(data: "Hi") // Mail has no receiver

// Message와 Mail 모두 Sendable과 Receiveable 프로토콜을 준수하므로 
// 서로 주고 받을 수 있습니다.
myMail.to = yourMail
myMail.send(data: "Hi") // Mail received Hi from Mail

myMail.to = myPhoneMessage
myMail.send(data: "Bye") // Message received Bye from Mail

// String은 Sendable 프로토콜을 준수하지 않습니다. 
Message.isSendableInstance("Hello") // false

// Message와 Mail은 Sendable 프로토콜을 준수합니다. 
Message.isSendableInstance(myPhoneMessage) // true

// yourPhoneMessage는 to 프로퍼티가 설정되지 않아서 보낼 수 없는 상태입니다.
Message.isSendableInstance(yourPhoneMesssage) // false
Mail.isSendableInstance(myPhoneMessage) // true
Mail.isSendableInstance(myMail) // true

위 코드를 보면 Message와 Mail 클래스는 Receiveable과 Sendable 프로토콜을 채택하고 있지만, 실제로 구현한 것은

저장 인스턴스 프로퍼티인 to 뿐이다.
이처럼 프로토콜의 요구사항을 익스텐션을 통해 구현하는 것을 프로토콜 초기구현(Protocol Default Implementations)
라고 한다.

 

기본 타입 확장

  • 프로토콜 초기구현을 통해 스위프트의 기본 타입을 확장하여 내가 원하는 기능을 공통적으로 추가해볼 수도 있다.
  • 스위프트 표준 라이브러리에 정의되어 있는 타입은 실제 구현코드를 보고 수정할 수 없기 때문에 
    익스텐션, 프로토콜, 프로토콜의 초기구현을 사용해 기본 타입에 기능을 추가해볼 수 있다.

SelfPrintable 프로토콜의 초기구현과 기본타입의 확장

protocol SelfPrintable {
    func printSelf()
}

extension SelfPrintable {
    func printSelf() {
        print(self)
    }
}

extension Int: SelfPrintable { }
extension String: SelfPrintable { }
extension Double: SelfPrintable { }

1024.printSelf() // 1024 
3.14.printSelf() // 3.14 
"hana".printSelf() // "hana"

위 코드는 코드를 수정할 수 없는 스위프트의 기본 타입인 Int, String, Double 에 SelfPrintable 프로토콜과 그 초기구현으로
공통 기능을 간단히 추가해본 것이다.