extension

이원희·2021년 3월 7일
2

 🐧 Swift

목록 보기
25/32
post-thumbnail

오랜만에 알고리즘 포스팅이 아닌 포스팅을 들고 왔다..ㅋㅋㅋ
extension에 대해서 포스팅할 생각은 없었는데 관련 리뷰를 받아서 내 생각을 정리할 겸 포스팅한다!
따라서 extension에 대해서는 길게 설명하진 않을거고 내가 받은 리뷰와 내 생각 그리고 내가 생각하는 extension의 장단점을 정리해보려 한다.

extension은 뭐지?

우선 공식 문서을 살펴보자.

extension은 기존 class, struct, enum, protocol새로운 기능을 추가한다.
여기에는 original source code(원본 소스 코드)에 접근할 수 없는 경우에 확장하는 기능이 포함된다.

오호... extension은 여러 타입에 새로운 기능을 추가할 수 있다.
그리고 원본 소스 코드에 접근할 수 없는 경우에도 기능을 확장할 수 있다.

그렇다면 extension에 추가할 수 있는 유형?들을 알아보자.

  • 연산 프로퍼티 (인스턴스여도 타입여도 모두 가능!)
  • 메서드 (인스턴스여도 타입여도 모두 가능!)
  • 이니셜라이저
  • 서브스크립트
  • 중첩 타입
  • 프로토콜을 준수하도록 할 수 있음

extension에서 굉장히 많은걸 할 수 있다.
여기서 몇 가지만 살펴보자.


연산 프로퍼티

extension String {
    var isNotEmpty: Bool {
        return !self.isEmpty
    }
    
    var isBlank: Bool {
        get {
            self.replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\n", with: "").isEmpty
        }
    }
    
    var isNotBlank: Bool {
        return !isBlank
    }
}

이런 식으로 extension에 연산 프로퍼티를 추가하여 사용할 수 있다.

  • isNotEmpty: 기존에 String Method인 isEmpty를 반전한 결과를 반환한다.
    즉, isNotEmpty = !isEmpyt이다.
    이전에 !isEmpty보다 isNotEmpty가독성이나 의도를 명확하게 할 수 있다는 리뷰를 받았어서 그 점에 유의해서 사용하는 중이다.
  • isBlank: 텍스트에 공백 혹은 줄바꿈만 있는 경우는 isNotEmpty로 확인이 불가하다.
    따라서 공백과 줄바꿈을 ""로 치환한 후 isEmpty한 결과를 반환한다.
  • isNotBlank: isBlank를 반전한 결과를 반환한다.

위에서 extension의 특징으로 원본 소스 코드에 접근할 수 없는 경우에도 기능을 확장할 수 있다고 했다.
String이 내부적으로 어떤 코드를 가지고 있는지 모르더라도 isEmpty를 사용할 수 있고, 이를 가공?할 수 있다.
isBlank도 마찬가지이다.


메서드

extension UIViewController {
    func showError(_ error: Error, okHandler: ((UIAlertAction) -> Void)?) {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: okHandler)
        let cancleAction = UIAlertAction(title: "확인", style: .cancel, handler: nil)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        self.present(alertController, animated: true, completion: nil)
    }
}

이런 식으로 extension에 메서드를 추가하여 사용할 수 있다.

showError()는 입력받은 error로 alert를 생성하고 view에 띄워주는 메서드이다.
이 부분은 extension에 이런 식으로 메서드를 확장할 수 있구나만 알아두고 넘어가자.
(이게 리뷰 받았던 코드이기 때문에... 뒤에서 다시 봐보자)


프로토콜을 준수하도록..!

extension ViewController: UITableViewDataSource {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return UITableViewCell()
    }
}

이런식으로 ViewController가 프로토콜을 준수하도록 할 수 있다.


코드 리뷰

(도미닉 감사합니다!🙇‍♂️)
그럼 위에서 봤던 error alert을 생성해주는 코드로 돌아와보자.

extension UIViewController {
    func showError(_ error: Error, okHandler: ((UIAlertAction) -> Void)?) {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: okHandler)
        let cancleAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        self.present(alertController, animated: true, completion: nil)
    }
}

뷰컨트롤러에 extension 으로 추가하는 것보다 에러를 표시하는 것을 담당하는 객체를 생성하는 것은 어떨까요?

showError()를 extension으로 뽑았던 이유는 UIViewController 어디서든 error alert이 뜰 가능성이 있어야 한다고 생각했고 extension으로 UIViewController의 기능을 확장했다.

그렇다면 왜 위와 같은 리뷰를 받았을까?

  • showError()는 UIViewController와 다른 일을 하고 있다.
    다른 역할을 하는 코드는 다른 객체를 생성하고 static method로 선언해 가져다 쓰는 것이 역할을 나누는 좋은 방법일 수 있다.
  • extension으로 showError()를 구현한다면 모든 UIViewController에 해당 메서드가 추가된다.
    showError()를 필요로하지 않는 UIViewController 입장에서 본다면 비효율적이고 혼돈을 줄 수 있다.

하나씩 살펴보자.


역할 구분

extension으로 구현된 showError() 코드를 봐보자.
UIViewController에 대한 extension이니까 showError()가 가지고 있는 역할을 UIViewController의 역할에 포함된다.

showError()의 역할을 생각해보자.
UIAlertController를 생성하고, present()로 view에 띄워주고 있다.
그렇다면 이 역할은 UIViewController가 가지고 있는게 맞을까?

present()로 view에 띄어주는 부분은 UIViewController가 충분히 가지고 있을 수 있는 역할이라고 생각한다.
하지만 UIAlertController를 생성하는 부분에 대해서는 의문이 든다.

struct ErrorAlert {
    static func makeErrorAlert(_ error: Error) -> UIAlertController {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
        let cancleAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        return alertController
    }
}

따라서, ErrorAlert struct의 타입 메서드 makeErrorAlert(_:)로 필요한 UIAlertController를 생성해서 반환하도록 변경했다.
이를 통해 UIViewController는 UIAlertController를 생성하는 역할에서 빠져나올 수 있다.

class ViewController: UIViewController {
    self.showAlert(ErrorAlert.makeErrorAlert(error))
}

extension UIViewController {
    func showAlert(_ alert: UIAlertController) {
        self.present(alert)
    }
}

효율과 의사표현

showError()를 필요로하지 않는 UIViewController 입장에서 본다면 비효율적이고 혼돈을 줄 수 있다.

흠... 현재 코드에서는 비효율이나 혼돈에 대해서 생각하기 어렵다.
그렇다면 아래의 코드를 보자.

extension UIViewController {
    func showError(_ error: Error) {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: nil)
        let cancleAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        self.present(alertController, animated: true, completion: nil)
    }
    
    func showError(_ error: Error, okHandler: ((UIAlertAction) -> Void)?) {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: okHandler)
        let cancleAction = UIAlertAction(title: "취소", style: .cancel, handler: nil)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        self.present(alertController, animated: true, completion: nil)
    }
    
    func showError(_ error: Error, okHandler: ((UIAlertAction) -> Void)?, cancelHandler: ((UIAlertAction) -> Void)?) {
        let alertController = UIAlertController(title: "오류", message: error.localizedDescription, preferredStyle: .alert)
        let okAction = UIAlertAction(title: "확인", style: .default, handler: okHandler)
        let cancleAction = UIAlertAction(title: "취소", style: .cancel, handler: cancelHandler)
        
        alertController.addAction(okAction)
        alertController.addAction(cancleAction)
        self.present(alertController, animated: true, completion: nil)
    }
}

위에서 정의한 showError()와 다른 parameters를 받는 showError()를 추가했다.
위의 3가지 showError()를 extension으로 확장했으므로 모든 UIViewController는 3가지 showError()를 사용할 수 있고, 해당 메서드들의 역할도 가지게 된다.

일단 코드가 생기기도 혼란스럽게 생김..ㅋㅋㅋㅋ
모든 UIViewController에서 showError()를 사용할 수 있다는 말은 모든 UIViewController에서 Error가 발생할 수 있다는 의미를 내포하고 있다.
하지만, Error가 발생하지 않는 UIViewController도 존재할 수 있고 그런 UIViewController의 입장에서 위의 코드는 혼돈 그 자체다.

Error가 발생할 수 있는 UIViewController의 입장에서 봐보자.
UIViewController는 핸들러가 필요 없는 error alert을 화면에 띄우기를 원한다.
그렇다면 필요한 메서드는 첫번째 showError() 뿐이다.
UIViewController 입장에서는 사용하지도 않는 2개의 showError() 메서드는 비효율적이다.
또한, 언제든지 다른 2개의 showError() 메서드를 사용할 여지가 남아 있으므로 UIViewController에게 혼란한 코드이다.


extension의 장단점

내 나름대로 생각해본 장단점이다.

장점

  • 원본 코드에 접근하지 않고 기능을 확장할 수 있다.

이는 기존 Swift에 구현되어 있는 Basic한 타입이 어떻게 구현되어 있는지 몰라도 기능을 확장할 수 있다.
또한, 외부 라이브러리를 사용할때에도 내부 코드를 수정할 수 없으므로 extension을 통해 기능을 확장할 수 있다.

  • 프로토콜을 준수하도록 한다.

이는 코드에서 프로토콜 채택 부분을 분리할 수 있도록 한다.
분리를 통해 코드의 책임을 분리할 수 있다고 생각한다.

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {}

보단

class ViewController: UIViewController {}
extension ViewController: UITableViewDataSource {}
extension ViewController: UITableViewDelegate {}

를 통해 코드의 책임을 분리하고 정리할 수 있다.

단점

  • extension으로 확장한 기능들은 해당 타입을 따르는 모든 타입에서 사용할 수 있으므로 범용성과 효율성에 대해서 생각해봐야한다.

extension으로 구현한 기능들은 범용성을 갖지만 타입이 해당 기능의 역할과 책임을 갖게 된다.
(항상 하는 생각이지만 역할과 책임에 대해서 생각하고 나누는게 가장 어려운거 같다...)

  • extension을 통해 기능들을 분리할 수 있지만 무분별하게 사용한다면 가독성 측면에서 좋지 않다.

프로젝트가 커지면 하나의 타입이 여러 프로토콜을 채택할 때가 있다.
이런 부분들은 extension으로 분리해가다보면 가독성이 안 좋아지는걸 느낀적이 있다...
하지만 이 부분을 어떻게 개선해야할지는 아직 모르겠다..ㅋㅋ
나중에 알게되면 추가해봐야지


마무리

오늘은 extension에 대해서 간단하게 알아보고 이전에 받았던 코드 리뷰를 정리해봤다.
그동안 extension을 사용할 때에 역할과 책임에 대해서 깊게 생각해보지 않고 그냥 extension 짱짱맨 했는데 이번 기회에 좀 돌아보게되었다..ㅋㅋㅋㅋ
역시... 코딩은 항상 선택의 연속이고 저울질을 잘해야한다는 생각이 들었다.
그럼 이만👋

0개의 댓글