본문 바로가기
개린이 이야기

Protocol-Extension에 관하여

by iOS 개린이 2023. 6. 5.

Protocol-Extension

-프로토콜 확장은 프로토콜에 Extension을 사용하여 연산 프로퍼티나 메소드, 이니셜라이저 등을 추가할 수 있게한다.

그리고 이 프로토콜을 채택하는 타입은 요구사항을 구현하지 않고, 이 기능을 사용할 수 있다.

 

protocol Run {
    func run()
}

struct Dog: Run {
    func run() {
        print("Run!!")
    }
}

struct Cat: Run {
    func run() {
        print("Run!!")
    }
}

struct Tiger: Run {
    func run() {
        print("Run!!")
    }
}

 

위 코드를 보면 'Dog', 'Cat', 'Tiger' 모두 'Run' 프로토콜을 채택하고 있으며,

요구사항인 'run' 메서드를 모두 동일하게 구현하고 있다.

근데 이 동일하게 동작하는 내용을 각 타입마다 똑같이 작성하는 것은 비효율적이기 때문에 

프로토콜 익스텐션을 사용할 수 있다.

 

protocol Run {
    func run()
}

extension Run {
    func run() {
        print("Run!!")
    }
}

 

'Run' 프로토콜에 익스텐션을 하고, 'run' 메서드에 기본적으로 제공하는 기능을 작성하는 것이다.

그럼 'Run' 프로토콜을 채택하는 쪽에서는 원래 'run' 메서드의 바디부분을 구현해야 했지만,

프로토콜 익스텐션으로 'run' 메서드에 기본적 구현을 해놓았기 때문에 채택하는 쪽에서 요구사항을 구현하지 않아도 된다.

 

struct Dog: Run { }

Dog().run() //"Run!!"

 

이렇게 'Run' 프로토콜을 채택했지만, 요구사항을 구현하지 않고 바로 사용할 수 있다.

만약 'run' 메서드에 대한 구현사항을 직접 만들고 싶다면?

 

struct Dog: Run {
    func run() {
        print("개야 달려보자!!")
    }
}

Dog().run() //"개야 달려보자!!"

 

이렇게 구현사항을 원하는대로 만들고 싶다면,

기존에 프로토콜 요구사항을 구현했던 것처럼 내가 원하는 구현을 해주면 된다.

프로토콜 익스텐션에 정의되어 있는 기본 구현보다 직접 구현한 'run()' 메서드의 우선순위가 높기 때문이다.

 

 

프로토콜 익스텐션의 조건부 확장

-조건부 확장이란 Swift의 'where' 절을 이용하여, 프로토콜을 구현하는 타입이 특정 조건을 충족할 때만 메서드나 프로퍼티에 접근할 수 있도록 하는 것이다.

 

protocol Run {
    func run()
}

extension Run where Self: Animal {
    func run() {
        print("Run!!")
    }
}

struct Dog: Run, Animal { }

struct Human: Run { }  //컴파일 에러 : Type 'Human' does not conform to protocol 'Run'

Human().run()     //컴파일 에러: Referencing instance method 'run()' on 'Run' requires that 'Human' conform to 'Animal'

 

'Run' 프로토콜 익스텐션에 조건부 확장을 통하여 'Animal' 프로토콜을 채택하고 있는 타입들만 

'run' 메서드를 사용할 수 있도록 만들었다.

 

원래 프로토콜 익스텐션으로 기본적인 구현을 해둔 'run' 메서드는 해당 프로토콜을 채택한 다른 타입에서 기본적으로 구현할 필요가 없었다.

하지만 조건부 확장을 넣어주니, 해당 조건에 맞지 않는 타입은 'run' 메서드에 대한 기본 구현을 하라고 컴파일 에러가 발생한다. 또한, 조건에 맞지 않는 'Human' 구조체가 'run' 메서드를 사용하는 것 자체도 불 가능하다.

 

 

프로토콜 확장을 통한 프로퍼티 추가

-원래 extension을 통해 프로퍼티의 추가는 연산 프로퍼티만 가능한 것처럼

프로토콜 익스텐션을 통해 연산 프로퍼티를 추가하는 것이 가능하다.

 

protocol Run {
    var name: String {get}
}

extension Run {
    var name: String {
        return "이름"
    }
}

struct Dog: Run { }

print(Dog().name) //"이름"

 

Swift 라이브러리에서 사용되는 프로토콜 익스텐션

-Swift에서는 프로토콜과 프로토콜 익스텐션이 많이 사용되는데, 그 중 "Collection" 프로토콜에 대해서 보자.

 

"Collection" 프로토콜은 Array, Set, Dictionary 등과 같이 여러 요소를 모아놓은 타입이 구현해야 하는 요구 사항들을 정의한다.

예를 들어, 'first' 프로퍼티, 'isEmpty' 프로퍼티, 'count' 프로퍼티, 'filter' 메서드 등은 모두 "Collection" 프로토콜 익스텐션에서 제공된다.

따라서 Array, Set, Dictionary 등은 "Collection" 프로토콜 익스텐션에서 제공하고 있는 프로퍼티나 메서드를 직접 구현할 필요없이 사용할 수 있다.

 

Swift 라이브러리의 코드에서 Collection 프로토콜의 extension에는 'first' 프로퍼티, 'isEmpty' 프로퍼티가 다음과 같이 정의되어 있다.

extension Collection {

  @inlinable
  public var isEmpty: Bool {
    return startIndex == endIndex
  }

  @inlinable
  public var first: Element? {
    let start = startIndex
    if start != endIndex { return self[start] }
    else { return nil }
  } 
}

 

 

'isEmpty'

컬렉션이 비어있는지를 나타내는 'Bool' 타입을 반환한다. 

이 컬렉션의 'startIndex'와 'endIndex' 가 같은지를 비교함으로써 반환값을 결정한다.

즉, 'startIndex'와 'endIndex' 가 같다면 컬렉션은 비어있다고 판단한다.

 

'startIndex' 와 'endIndex' 는 컬렉션의 첫 번째 요소와 마지막 요소 다음의 위치를 가리키는 프로퍼티로, 프로토콜을 채택한 타입이 구현해야 하는 프로퍼티다.

 

'first'

컬렉션의 첫 요소를 반환한다. 이 프로퍼티는 옵셔널 타입으로, 컬렉션에 요소가 없으면 'nil' 을 반환한다.

'first' 프로퍼티는 'startIndex' 와 'endIndex' 를 비교하여 컬렉션이 비어있는지를 확인하고, 

비어있지 않다면 'startIndex' 위치의 요소를 반환한다.

 

두 가지 연산 프로퍼티는 'Collection' 프로토콜을 채택한 곳에서 사용할 수 있고,

이를 통해 컬렉션이 비어있는지 확인하거나 첫 번째 요소를 쉽게 가져올 수 있다.

 

 

 

프로토콜 익스텐션을 통해 가져올 수 있는 이점

1. 중복 제거

프로토콜 익스텐션을 사용하면, 프로토콜을 준수하는 모든 타입에 메서드나 프로퍼티의 기본적이고, 공통된 구현을 제공할 수 있다. 예제에서도 보았듯이 프로토콜 익스텐션을 사용하여 공통된 메서드에 대해 중복 코드를 피할 수 있었고, 유지보수 측면에서도 좋다.

 

2. 조건부를 활용한 이점

프로토콜의 요구사항을 채택하는 모든 곳에서 구현해야 한다면, 굉장히 불편하고 제한적이겠죠?

프로토콜의 익스텐션은 조건부 구현을 통해 제한적인 단점을 해소할 수 있다.

예제에서도 보았듯이 조건부 구현을 통해 원하는 타입만 해당 기능을 사용할 수 있도록 했다.

이것은 프로토콜에 따라 세부적으로 동작을 제어할 수 있고, TypeSafe 한 코드를 작성할 수 있다.

 

 

 

 

 

 

한계점

Object-C의 Protocol 과의 호환.

Swift의 Extension 기능을 Object-C의 Protocol 과 함께 사용할 수 없다.

코코아터치나 코코아프레임워크는 모두 Object-C 코드로 구현이 되어있다.

따라서 DataSource, Delegate 등 프레임워크 프로토콜에는 Extension 기능을 통한 기본 구현이 불 가능하다.

 

 

 

 

 

 

 

Reference:

https://jusung.gitbook.io/the-swift-language-guide/language-guide/21-protocols

https://babbab2.tistory.com/178

https://github.com/apple/swift/blob/main/stdlib/public/core/Collection.swift