Swift Protocol Default Implementation

Swift에서는 Protocol을 좀 더 적극적으로 활용할 수 있다.
그 중, Extenstion을 이용하여 Protocol 의 기본 구현을 지정해 줄 수 있다.
예를 들어, 아래와 같은 프로토콜이 있다고 가정하자.

public protocol ListViewModel: class {
    var sections: [ListSectionModel] { get }
    var appearance: ListViewAppearance { get }
    weak var delegate: ListViewModelDelegate? { get set }

    func numberOfSectionModels() -> Int
    func numberOfMenus(in section: Int) -> Int
    func sectionModel(for section: Int) -> ListSectionModel?
    func menuModel(for indexPath: IndexPath) -> ListMenuModel?
}

ListViewModel 프로토콜은 Listing을 하는 View에 대응되는 ViewModel class라고 가정하자.
그러면, 애플의 UICollectionView혹은 UITableView의 DataSource를 구현해주는 부분이 반드시 필요하게 된다.
그런데 정의된 함수들을 잘 보면, 특정 비지니스 로직에 종속적인 함수들이라기보다는 굉장히 일반화시킬 수 있는 함수들로 이루어져 있다.

만약, 이러한 것들을 framework내지는 module로 만든다고 한다면, 이 프로토콜을 따르는 모든 class에서 중복의 코드가 발생할 수 있다.
이럴 때, Swift의 Protocol extension을 활용해서 기본 구현을 지정해 줄 수 있다.
방법은 아래와 같다.

// MARK: - Default Implementation
public extension ListViewModel {

    public func numberOfSectionModels() -> Int {
        return sections.count
    }

    public func numberOfMenus(in section: Int) -> Int {
        if section >= sections.count || section < 0 {
            return 0
        }

        return sections[section].numberOfMenuInSection()
    }

    public func sectionModel(for section: Int) -> ListSectionModel? {
        if section >= sections.count || section < 0 {
            return nil
        }

        return sections[section]
    }

    public func menuModel(for indexPath: IndexPath) -> ListMenuModel? {
        if indexPath.section >= sections.count || indexPath.section < 0 {
            return nil
        }

        return sections[indexPath.section].menuModel(for: indexPath.row)
    }
}

이렇게 지정해 주면, ListViewModel Protocol을 따르는 모든 객체에서 프로토콜 내 함수를 구현하지 않을 경우 Default Implementation을 따르도록 되어 있다.
굉장히 일반적인 코드의 중복을 해결할 수 있는 좋은 대안이 될 수 있을 것이라고 생각한다.

또한, 이러한 Default Implementation을 통해서 Objective-C Protocol의 @optional 함수를 비슷하게 구현할 수 있다.
물론 Swift에서도 optional func foo(bar) 의 형태로 정의할 수 있지만, optional 키워드 자체가 objc export가 되어야 하는 부분이므로, 제약사항이 많이 발생하게 된다.
이런 경우 아래와 같이 프로토콜을 선언하고 기본 구현을 지정해주면, Obj-C의 optional method와 굉장히 비슷하게 구현을 가져갈 수 있다.

public protocol ListViewModelDelegate: class {
    func menuModelValueChanged(_ menuModel: ListMenuModel)
    func sectionModelValueChanged(_ sectionModel: ListSectionModel)
    func viewModelValueChanged(_ viewModel: ListViewModel)
}


// MARK: - Default Implementation: It will work as optional protocol method in Obj-C
public extension ListViewModelDelegate {

    public func menuModelValueChanged(_ menuModel: ListMenuModel) {
        // Do nothing in this method
    }

    public func sectionModelValueChanged(_ sectionModel: ListSectionModel) {
        // Do nothing in this method
    }

    public func viewModelValueChanged(_ viewModel: ListViewModel) {
        // Do nothing in this method
    }
}

언제나 이 방법이 정답은 아니겠지만, 적재적소에 활용한다면 좀 더 코드를 Swift처럼 작성할 수 있을 것 같다.

댓글 남기기