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처럼 작성할 수 있을 것 같다.