컴퓨터언어/Swift

[Swift] Extension : 원래 있던 class/struct에 커스텀 기능추가하기

bbanpro 2020. 4. 13. 17:33
728x90
반응형

Extension이 뭔데?

 

Extension은 이름 그대로 확장을 해준다는 것이다.

 

무엇을 확장하고 왜 필요한데?

 

Apple은 기본적으로 버튼, Label, 자료형들을 Class의 형태로 제공하고 있고, 각 Class는 내부에 많은 함수(기능)들을 가지고 있다.

 

하지만 개발자마다 취향이 다르고, 또한 프로젝트마다 해결해야 할 과제는 제각각이기 때문에,

 

Apple은 그 모든 경우의 수를 다 고려해서 Xcode를 배포할 수 없다.

 

이러한 이유로 개발자 각각 처한 상황에 맞게 스스로 코드들을 수정, 커스터마이징 할 수 있도록 하기 위하여,

 

Extension이 등장한 것이다.

 

어떻게 쓰는지 반올림 예제를 통해 알아보자.

 


 

실수 자료형인 Double 클래스에는 반올림을 담당하는 round() 함수가 있다.

 

하지만 round() 함수는 안타깝게도 소수 몇째자리까지 반올림을 하는지는 정할 수 없고,

 

쿨하게 소수 첫째 자리에서만 동작하는 함수이다.

그래서 우리는 소수 x째 자리까지 반올림을 하도록 이 round 함수를 Extend 해보겠다.

 

문법은 다음과 같다.

extension 기존에존재하는클래스(스트럭트)이름 {
	func 기존에존재하는클래스(스트럭트)안에있는함수이름 (인수필요하면넣어주기) {
    	// 이하 내맘대로 커스터마이징
    }
}

 

여기서 기존에 존재하는 클래스 또는 스트럭트는 지금 다루는 예제에서처럼 Apple Xcode에 내장된 Double Class 뿐만 아니라,

 

내가 직접 만들었던 다른 클래스(스트럭트)도 당연히 가능하다.

 

지금은 빠른 학습을 위해 기존의 Double 클래스를 요리해보자.

 

위에 기술한 문법에 의해, 우리가 수정(확장)할 Class는 Double이며, 그 안의 함수는 round이다. 따라서 아래와 같이 준비를 해준다.

extension Double {
    func round() {
    }
}

 

그리고 지금 우리가 수정할 방향은 "소수 몇째 자리까지 반올림할 것인지"를 정하는 것이기 때문에, User로부터 "몇"째자리에 해당하는 그 "몇"을 인수로 받아야 한다.

 

그래야 그 받아온 값을 가지고 함수 내에서 처리할 수 있을 것이다. 따라서 아래와 같이 인수부분을 추가한다.

extension Double {
    func round(to place: Int) {
    }
}

 


 

참고로 여기서 to도 있고 place도 있는 이유는,

 

함수를 만들 때, 이후에 어떤 값을 입력해야 한다고 미리 정의해놓은 Parameter

그 함수를 호출할 때 실제로 넣어주는 값인 Argument

 

이 2개 모두 "place"라는 이름을 가지고 있는 것이 비교적 가독성이 낮기 때문에,

 

함수를 호출할 때만은 자동완성으로 place가 아닌 to가 나오도록 설정하여,

 

함수 이용자로 하여금 친절한 선생님이 설명해주듯이 가독성을 높이기 위한 것이다.

 

다시 말해, 아래와 같이 to 없이 그냥 place: Int만 인수로 설정한다면,

extension Double {
    func round(place: Int) {
    }
}

함수를 호출할 때도 place라는 이름으로 자동완성이 되기 때문에,

다른 사람과 협업하는 상황이거나, 코드량이 엄청난 프로젝트이거나, 아니면 미래의 나 자신이 다시 이것을 보거나...

 

했을 때 중복된 이름때문에 읽는 데 잠시 헷갈릴 수 있다.

 

이게 coding이라는게 영어기반이어서 잘 안 와닿을 수 있는데,

 

우리말로 따져보면 왜 이렇게 Label을 굳이 덧붙이는지 조금 더 이해될 것이다.

 

#1. Parameter와 Argument의 Label이 같은 경우

extension Double {
    func 반올림하기(자리: Int) {
    }
}

number.반올림하기(자리: Int)

#2. Parameter와 Argument의 Label이 다른 경우

extension Double {
    func 반올림하기(자리: Int) {
    }
}

number.반올림하기(몇째자리까지: )

 

물론 지금은 코딩이 짧고 간단해서 네이밍을 굳이 안해도 되지만,

 

로직이 복잡해진다면 충분히 매력적인 도구임에 분명하다.

 

아직 잘 모르겠다면 아래 포스팅도 참고하라.

2020/04/12 - [컴퓨터언어/Swift] - [Swift] 함수에 인수가 뭐이리 많아? _ 는 무엇? (feat. Parameter vs Argument)

 

[Swift] 함수에 인수가 뭐이리 많아? _ 는 무엇? (feat. Parameter vs Argument)

#1. 함수에 인수가 뭐이리 많아 보이는 경우 // 함수 정의부 func 함수이름(함수호출시사용할인수별명 실제인수이름: 인수데이터타입) { } // 함수 호출부 함수이름(함수호출시사용할인수별명: 인수데이터타입) #2..

an-onymous.tistory.com


다시 돌아와서, 우리는 func round()의 Body 부분을 완성해야 한다.

 

한번 생각해보자.

 

3.141592라는 수를 소수 3째자리까지 반올림하려면 어떻게 해야 할까?

 

답은 아래 3가지 과정으로 간단하게 구할 수 있다.

 

3.141592 -> 3141.592 -> 3141 -> 3.141

3.141592에 10을 3번 곱해서 소수점을 오른쪽으로 3개 이동시킨다.

소수점 아래를 모두 절삭시킨다.

다시 소수점을 왼쪽으로 3개 복구시킨다.

 

이러한 논리로, 우리는 이제 "3" 대신 함수의 Parameter인 place를 사용하면 된다.

 

그리고 Double 자료형인 숫자들은 모두 Double 클래스 소속이기 때문에, round() 함수에 접근할 수 있다.

 

3.141592라는 숫자는 Double클래스 소속이기 때문에, round() 함수를 사용할 수 있다는 뜻이다.

 

그래서 우리는 아래와 같이 3.141592를 변수에 담은 후, 그 뒤에 .round()를 붙여서 사용하곤 했다.

 

 

그런데 여기서 "number.round()"에 가장 중요한 개념이 숨어있는데, 그것은 바로 self이다.

 

즉 .round()가 기존에 다른 인수를 받지 않으면서도 숫자 뒤에 바짝 붙어서 동작할 수 있었던 것에는,

 

그 앞에 있는 숫자와 .round() 함수가 동시에 Double 클래스 소속이기 때문에 서로를 참조할 수 있고,

 

이는 round()가 동작할 때 self라는 키워드로 앞의 숫자를 참조한다는 것을 나타낸다.

 

따라서 우리는 이 개념을 수식화해서 업그레이드 된 round() 함수의 Body를 완성할 수 있다.

 

extension Double {
    // 몇째자리까지 반올림할지 입력값을 받은 것이 place에 저장된다.
    func round(to place: Int) {
        // 10에 place만큼 거듭제곱하면 그만큼 오른쪽으로 소수점이 이동된다. 얼만큼 이동할지는 함수를 호출할 때 고정되므로, let으로 지정한다.
        let modifiedNumber = pow(10, place)
        // 원래 앞에 있던 수가 바로 self다. 앞에 있던 수는 소수점변경 작업을 해야하므로 var로 지정한다.
        var n = self
        // 소수점을 요청받은 수만큼 오른쪽 이동
        n = n * modifiedNumber
        // 요청받은 수 이하는 모두 반올림해서 없앤다. 이때는 원래 내장된 "round()"를 이용
        n.round()
        // 다시 옮긴만큼 빠꾸해서 기존 자릿수로 복구
        n = n / modifiedNumber
    }
}

 

이제 남은 작업은 2가지다.

 

1. 반올림을 하면 그 결과값으로 다시 Double을 내보내야 하기 때문에, 반환값으로 Double을 지정해주고, return문도 작성한다.

extension Double {
    func round(to place: Int) -> Double {
        let modifiedNumber = pow(10, place)
        var n = self
        n = n * modifiedNumber
        n.round()
        n = n / modifiedNumber
        return n
    }
}

 

2. 앞에 있던 기존 숫자와 그 숫자를 반올림한 출력값은 Double인 것이 당연한데, 실제 우리가 입력값으로 받은 place는 "몇"째자리에 해당하는 정수 Int이다. 

 

서로 다른 자료형은 연산이 불가능하기 때문에, 입력받은 값 place를 거듭제곱하는 식 안에서 Double로 형변환해준다.

extension Double {
    func round(to place: Int) -> Double {
        let modifiedNumber = pow(10, Double(place))
        var n = self
        n = n * modifiedNumber
        n.round()
        n = n / modifiedNumber
        return n
    }
}

 

그럼 드디어 우리가 Custom으로 만든 "소수 몇번째 자리까지 반올림하는 함수"를 기존 round() 함수를 Extend시켜 완성하였다!

 

 

이처럼 Extension은 Class 뿐만 아니라 Struct는 물론, Protocol까지 확장 가능하다!

 


심화

 

그리고 Extension의 엄청난 기능은 Protocol에서 나타난다!

 

원래 Protocol은,

 

따로 protocol{}로 만들어놓고 다른 Class나 Struct에서 이를 채택하면,

 

해당 Class(Struct)에서는 반드시 그 Protocol 내에서 이름과 소괄호()로만 선언해 놓았던 함수들의 Body를 모두 구현했어야 했다.

 

하지만 Extension을 활용하면 굳이 다 구현하지 않아도 Extension이 사전에 다 처리해놓는다.

 

말하자면 다음과 같다.

<저를 채택해주신 Class/Struct 분들께>

 

안녕하세요 <protocol A>입니다.

 

doSomething1(), doSomething2(), ... doSomething100() 함수를 사용하고자 저희 protocolA를 채택해주셔서 감사합니다!

 

이번에 대대적인 업데이트를 했습니다.

 

기존에는 여러분들의 { } 안에 100가지 함수 모두 구체적으로 어떤 일을 할 것인지 스스로 정의내리셔야 했습니다.

 

{ } 안에 한 개라도 빼놓았다면 에러를 면할 수 없었기에 코드가 매우 뚱뚱했었죠.

 

하지만 이번에는 Extension 기능을 탑재하여 저희 측에서 미리 등록을 다 해놓았습니다.

 

여러분들은 100개 중에서 수정이 필요하신 것만 { } 안에 적고 커스터마이징하시면 되며,

 

{ } 안에 따로 적지 않으신 것은 저희가 가진 기본값으로 자동 등록됩니다.

 

혹시 기본값을 수정하고 싶으시면, 언제든 저희 extension A를 찾아주세요!

 

감사합니다!

 

진짜 필요한 기능만을 골라쓸 수 있게 된다!

 

즉 Extension의 이러한 능력 때문에, 우리는 그동안 Apple이 제공하는 수많은 내장된 기능들을 다 쓰지 않아도 됐던 것이다!

 

Protocol1은 extension으로 분리해서 따로 관리하는 사례 vs Protocol2는 기존처럼 채택한 클래스에서 구체화하는 방식.

 

(사실 이것이 진짜 Extension의 정수)

 


 

그런데!

 

채택하는 것도 좋고, extension도 좋다.

 

그런데 만약 위 사진에서처럼 Protocol들을 채택한 LongCode 클래스의 코드가 채택이 쌓여서 계속 길어지고 더러워진다면?

 

개발을 하다보면 아무래도 ViewController 안의 코드가 길어지게 마련이다.

 

특히 Protocol을 채택하거나 Class를 상속받을 때는 : (콜론) 뒤에도 너저분해질 뿐 아니라, { } 안의 코드량도 엄청 길어진다.

 

이럴 때도 역시 Extension이 도와준다!

 

이때는 Protocol이 아니라, 채택을 하는 메인 Class를 Extend 시킴으로써,

 

각각의 Protocol이나 Class를 따로 관리할 수 있게 된다!

 

밑의 사진을 한번 보자.

 

ViewController 등 Controller는 최대한 깔끔한 중간 관리자 역할을 해주어야 한다.

 

우리는 Extension의 기능을 ViewController에도 이용할 수 있을 것이다.

 

원래 1~13번 줄의 Protocol 정의 부분은 Model로 따로 빼는 것이 맞지만, 여기서는 코드진행의 이해를 위해 포함시켰을 뿐이다.

 

다음 포스팅에서는 이 상태에서 한발짝 더 나아가, 코드들을 한 파일에서 어떻게 더 보기좋게 나누는지 알아보자.

728x90
반응형