-Today's Learning Content-

  • 프로토콜과 접근제한자

1. 프로토콜과 접근제한자

개념 정리

프로토콜 (protocol) 은 메서드, 프로퍼티, 그리고 특정 작업이나 기능의 부분이 적합한 다른 요구사항의 청사진을 정의합니다. 프로토콜은 요구사항의 구현을 제공하기 위해 클래스, 구조체, 또는 열거형에 의해 채택될 수 있습니다. 프로토콜의 요구사항에 충족하는 모든 타입은 프로토콜에 준수 (conform) 한다고 합니다.
Swift.org Swift Korean DocC

1) 사건의 발단

과제를 진행하던 중 프로토콜로 만든 메소드에 접근제한자가 필요할 것 같다는 생각이 들었다. 그래서 아무 생각없이 메소드에 private 접근제한자를 달았는데...

이런 에러가 발생했다.
에러의 내용을 살펴보면 이 메소드는 채택된 프로토콜의 메소드와 요구사항이 일치하기 때문에 internal로 선언해야 한다 라는 뜻이다.

아 혹시 프로토콜을 만들 때 접근제한자를 설정 안해서 그런걸까? 그래서 프로토콜에서 메소드에 접근제한자를 달아보았다.

안 된다.
에러의 내용은 프로토콜에서는 private를 사용할 수 없다 라는 뜻이다. 그래서 아래 힌트로 private를 지우라고 나와있는 걸 볼 수 있다.

하지만 이대로 끝낼 수는 없다.
extension을 사용해서 private를 구현하면 괜찮지 않을까??

private extension ViewController {
	func compulsoryTermination(second: Double) {
    	// 생략...
    }
}

이렇게 하니까 함수 자체에는 에러가 발생하지 않았다. 드디어 성공인가? 싶었는데...

또 에러인가...

이쯤되니 오기가 생겨서 내가 아는 모든 방법을 동원해서 정말로 프로토콜의 메소드에는 private를 사용할 수 없는 것인지 실험해보기로 했다.

그러나 더는 방법이 안 떠올랐고... 혹시나 프로토콜 자체를 extension으로 확장하면 괜찮지 않을까? 싶었는데

private extension FatalErrorTerminate {
    func compulsoryTermination(second: Double) {
    	// 생략...
    }
}

이렇게 해도

extension FatalErrorTerminate {
    private func compulsoryTermination(second: Double) {
    	// 생략...
    }
}

이렇게 해도

프로토콜을 준수하지 않았다며 에러를 보여준다...
더이상 내가 아는 방법이 없었기 때문에 정말로 프로토콜의 메소드는 접근제한자를 사용할 수 없는 것인지 궁금하여 서치를 해보았다.

결과는...

할 수 없다! 이다.

2) Why?

Swift에서 protocol은 공개된 계약으로, 특정 기능이 구현되어 있다고 외부에 보장하기 위한 인터페이드이다. 프로토콜을 채택한 객체는 해당 프로토콜의 요구하상(프로퍼티, 메소드 등)을 반드시 구현해야 하며, 이 구현은 프로토콜 계약의 일환으로 외부에서도 접근 가능해야 한다.

정리하자면,

  1. 프로토콜은 공개 계약이므로 구현도 공개적이어야 한다
    • 프로토콜은 객체 간 상호작용을 정의하기 위해 설계된다. 이를 통해 외부에서 프로토콜을 준수하는 객체와 상호작용할 수 있다.
    • 만약 프로토콜의 구현을 private로 설정하면, 외부에서 해당 계약을 준수하는 객체와 상호작용할 수 없으므로 프로토콜의 목적 자체를 훼손한다.
  1. 호출 가능성이 보장되어야 한다
    • 프로토콜은 특정 동작을 보장하기 위해 사용된다. 예를 들어, UITableViewDataSource 프로토콜을 준수한다는 것은 테이블 뷰가 데이터 소스와 상호작용할 수 있도록 모든 요구사항을 외부에서 호출 가능하게 구현했음을 의미한다.
    • 만약 private로 제한하면, 이러한 호출 가능성을 차단하게 되어 프로토콜의 본질적 기능을 깨트린다.

때문에 프로토콜은 접근제한자를 이용하여 특정한 프로퍼티나 메소드 등을 내부에서만 사용할 수 있도록 막을 수 없다고 한다.

3) 객체지향과 프로토콜

객체지향 프로그래밍(OOP)은 다형성캡슐화가 핵심 원리이다. Swift 역시 다형성을 보장하는 객체지향형 프로그래밍 방식을 사용하고 있고, 여기서 protocol은 다형성을 지원하기 위한 도구로 설계되었다.

즉 객체지향의 다형성 측면에서 볼 때, 프로토콜은 특정 타입이 외부에서 예측 가능한 방식으로 행동하도록 보장하는 역할이다. 그런데 프로토콜을 private로 제한하게 되면, 다형성의 이점을 상실하게 된다. 외부에서는 해당 객체가 프로토콜 요구사항을 구현하고 있는지 알 수 없고, 상호작용도 불가능하게 된다.

또, 객체지향에서는 내부 구현을 숨기고 인터페이스만 외부에 제공하는 캡슐화를 중요시 여기는데, 프로토콜을 통해 공개된 인터페이스는 외부와 상호작용하기 위해 열려있어야 한다.

그렇다면 OOP가 지향하는 캡슐화와 충돌하는 것이 아닐까?
그러나 프로토콜은 캡슐화와 충돌하지 않는다.

왜냐하면, 프로토콜은 외부와 상호작용할 수 있는 계약(인터페이스)을 정의하며, 실제 세부 구현은 여전히 숨겨질 수 있다. 즉 protocol의 요구사항을 공개적으로 구현하되 내부 로직은 비공개로 유지할 수 있기 때문이다.

코드를 예시로 보도록 하자.

protocol SomeProtocol {
	func performAction()
}

class SomeClass: SomeProtocol {
	// 프로토콜 요구사항
    func performAction() {
    	helperMethod() // 클래스 내부구현 메소드
    }
    
    // 세부 구현은 감춤
    private func helperMethod() {
    	print("Action performed")
    }
}

위 코드에서 protocolperformAction이라는 메소드를 구현하도록 요구하고 있다. 때문에 SomeClass라는 클래스는 내부적으로 해당 메소드를 구현해야 하지만, 세부적인 로직은 다른 메소드를 호출하는 방식을 사용하여 감추고 있다.

이는 외부에서 봤을 때 SomeClassSomeProtocol의 요구사항을 준수하는 것은 확인할 수 있지만, 그 내부로직이 어떻게 구현되었는지는 확인할 수가 없다.

다른 방법도 있다.

protocol SomeProtocol {
    func fetchData()
}

class SomeClass: SomeProtocol {
    func fetchData() {
        print("Fetching data...")
    }
}

class Controller {
    private let service: SomeProtocol

    init(service: SomeProtocol) {
        self.service = service
    }

    func execute() {
        service.fetchData()
    }
}

let someClass = SomeClass()
let controller = Controller(service: someClass)
controller.execute()
// 출력: Fetching data...

위 코드는 프로토콜을 구현한 객체를 직접적으로 노출하지 않고, 이를 포함하는 다른 객체를 만들어 캡슐화한 예제이다.

이처럼 프로토콜은 외부와의 상호작용을 위해 인터페이스가 항상 열려있지만, 세부적인 로직을 감출 수 있기 때문에 객체지향의 캡슐화와 충돌하지 않는 것이다.

3) 결론

결국 프로토콜은 private로 은닉화할 수 없다는 것이다.
사실 객체지향 프로그래밍에서 프로토콜의 역할은 서로 다른 객체가 서로 상호작용할 수 있도록 하고, 유연성을 부여하는 것이다.

그런데 이런 프로토콜을 private로 은닉화 해버리면 객체와 객체가 서로 상호작용할 수 없게 되고, 이는 프로토콜을 사용하는 목적과 멀어지게 되는 것이다.

결국 내가 했던 시도들은 프로토콜의 존재 이유에 대해 생각해보지 않았던 탓에 될 수 없었던 일들을 실행하려고 했던 것이다.

끝.


-Today's Lesson Review-

오늘은 프로토콜의 은닉화에 대해 공부하였다.
과제를 진행하다가 우연히 발견한 소재이지만...
새삼 공부가 부족함을 느꼈다.
그래도 이렇게 하나하나 알아가는 과정이 재밌기 때문에
또 에러가 발생하기를 기다린다.
profile
이유있는 코드를 쓰자!!

0개의 댓글