본문 바로가기
문법관련/Swift

익스텐션 (꼼꼼한 재은 씨의 Swift : 문법편)

by print_soo 2022. 5. 19.

익스익스텐션(Extensions)은 이미 존재하는 클래스나 구조체, 열거형 등의 객체에 새로운 기능을 추가하여 확장해주는 구문이다.

 

익스텐션을 통해서 구현할 수 있는 것들은 대표적으로 아래와 같다.

1. 새로운 연산 프로퍼티를 추가할 수 있다.
2. 새로운 메소드를 정의할 수 있다.
3. 새로윤 초기화 구문을 추가할 수 있다.
4. 기존 객체를 수정하지 않고 프로토콜을 구현할 수있다. (프로토콜은 다음편에서...)

 

이번편에서는 1 ~ 3까지만 다루어볼 예정이다. (4는 다음 편에)

 

익스텐션을 사용할 때는 아래와 같이 사용한다.

extension <확장할 기존 객체명> {
    //code
}

위의 형태에서 볼 수 있듯이, 익스텐션을 extension 키워드를 사용할 뿐 독립적인 객체를 생성하는 구문이 아니다. 따라서 익스텐션을 객체가 아니며 타입으로 사용될 수도 없다.

 

[익스텐션과 연산 프로퍼티]

익스텐션을 사용하면 기존 객체에 프로퍼티를 추가할 수 있다. 단, 추가할 수 있는 것은 연산 프로퍼티로 제한된다.

(대신, 인스턴스 프로퍼티든 타입 프로퍼티든 연산 프로퍼티라면 모두 추가가능하다.)

 

extension Double {
    var km: Double { return self * 1000.0}
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1000.0 }
    var description: String {
        return "\(self)km는 \(self.km)m, \(self)cm는 \(self.cm)m, \(self)mm는 \(self.mm)m입니다."
    }
}

위는 Double 자료형의 기능(단위변경)을 확장시킨 것이다. 

 

 

2.km // 2000
5.5.cm // 0.005
125.mm // 0.125
7.0.description // "7.0km는 7000.0m, 7.0cm는 0.07m, 7.0mm는 0.007m입니다."
let distance = 42.0.km + 195.m // 42195.0
print("마라톤의 총 거리는 \(distance)m입니다.") // 마라톤의 총 거리는 42195.0m입니다.\n"

익스텐션을 통해서 위처럼 활용할 수 있다. 

 

 

[익스텐션과 메소드]

엑스텐션을 이용하면 기존객체에 새로운 인스턴스 메소드나 타입 메소드를 정의할 수 있다. 매개변수 타입을 다르게하면 서로 다른 메소드가 되는 메소드 오버로딩 특성을 이용해서 새로운 메소드를 정의할 수 있고, 매개변수명을 변경하여 새로운 메소드를 작성할 수도 있다.

 

 

하지만 기존 객체에서 사용된 같은 메소드를 익스텐션에서 재정의하는 것은 불가하다.

 

extension Int {
    func repeatRun(task: () -> Void) {
        for _ in 0 ..< self {
            task()
        }
    }
}

let d = 3
d.repeatRun(task: {
    print("안녕")
})

//안녕
//안녕
//안녕

Double에 이어서 이번에는 Int 구조체를 확장하였다. repeatRun(task: )라는 메소드는 입력 받은 task를 주어진 Int만큼 반복 실행하는 메소드이다. 

 

 

인스턴스 메소드는 익스텐션에서도 mutating 키워드를 사용하여 인스턴스 자신을 수정하도록 허용할 수 있다. 

(mutating: 구조체나 열거형의 인스턴스 메소드 내부에서 프로퍼티의 값을 수정할 때 필요한 키워드)

 

extension Int {
    mutating func square() {
        self = self * self
    }
}

var value = 3
value.square()

위의 과정에서 주의할 점은 위 메소드는 반환값 없이 갑 자체를 제곱값으로 변경하기 때문에 값을 상수에 할당해서는 안된다.

 

 

익스텐션으로 확장할 수 있는 기능은 직접 소스 코드를 수정할 수 없는 라이브러리나 스위프트 언어 기반을 이루는 객체들까지 모두 확장할 수 있다는 점에서 매우 매력적인 기능임은 확실하다. 

 

하지만 익스텐션을 남용하면 객체의 정의를 모호하게 만들거나 각 실행 위치에 따라 서로 다른 정의로 구성된 객체를 사용하게 만드는 결과를 가져올 수도 있다. 이외에 다른 단점들이 있기 때문에 익스텐션은 필요한 곳에서는 충분히 사용하되 남용하지 않고 여기저기 분산해서 작성하기 보다는 전체적인 정의와 구조를 파악할 수 있는 위치에서 작성하는 것이 좋다. 

 

 

 

[익스텐션과 델리게이트 패턴]

 

ios에서 인터페이스를 구현하다 보면 델리게이트 패턴을 많이 사용하게 됩니다. 그런데 델리게이트 패턴 구현에는 프로토콜이 사용되기 때문에, 화면 다수 요소에 델리게이트 패턴을 작용할 경우 클래스 소스 코드는 금세 프로토콜 관련 코드들로 엉망이 되곤한다. 

 

예를 들어 테이블 뷰를 이용해서 화면을 구성한다면 ViewController는 다음과 같이 될 것이다.

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
	// ...
}

 

하지만 익스텐션을 써본다면 아래와 같이 깔끔한 코드를 만들 수 있다.

class ViewController: UIViewController {
	// ...
}

//MARK: - 테이블 뷰를 위한 프로토콜 델리게이트 구현

extension SettingViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
          
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        
    }

}

 

코드를 잘 보면 MARK: - 주석이 있다. 이 주석은 기존 메소드 및 프로퍼티 목록과 익스텐션을 구분해주는 수평선을 처리해주기 때문에 잘 사용하면 보기 쉬운 코드를 만드는데 도움이 된다.