BOID

[swift] 확장(extensions)이란? 본문

swift 시작기

[swift] 확장(extensions)이란?

HoonIOS 2021. 3. 16. 17:20

안녕하세요 HoonIOS입니다 :)

이번은 swift의 extensions에 대해 공부를 해봤는데요, 제가 생각한 것보다 훨씬 포괄적이고 제가 모르는 부분이 많아서 포스팅을 했습니다. 그럼 한번 알아보겠습니다.

 

extensions 란?

* 애플 공식 문서에서 정의된 extensions

apple documentation에서 extensions 정의

- 공식문서에서 보면 새로운 함수적 기능을 class, structure, enumeration, or protocol type에 확장한다는 의미를 지니고 있습니다.

- 익스텐션은 스위프트의 강력한 기능 중 하나로 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있다.

- 스위프트의 익스텐션이 타입에 추가할 수 있는 기능은 다음과 같다.

  1. 연산 타입 프로퍼티/ 연산 인스턴스 프로퍼티
  2. 타입 메서드/ 인스턴스 메서드
  3. 이니셜라이저
  4. 서브스크립트
  5. 중첩 타입
  6. 특정 프로토콜을 준수할 수 있도록 기능 추가

- 익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존 새로운 기능을 재정의(override) 할수는 없다.

- 클래스의 상속과 익스텐션을 비교해 보겠습니다.

  상속 확장
타입 클래스 타입에서만 가능하다 구조체, 클래스, 프로토콜 등에 적용이 가능하다.
특징 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가기능을 구현하는 수직 확장이다. 기존 타입에 기능을 추가하는 수평 확장이다.
재정의(override) 기존 기능을 재정의 할수 있다. 재정의가 불가능 하다.

- 익스텐션을 쓰는 이유는 외부 라이브러리나 프레임워크를 가져다 사용했다면 원본 소스를 수정하지 못한다. 이 처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다.

 

익스텐션 문법

- 문법

extension 확장할 타입이름 {
     //타입에 추가될 새로운 기능 구현
}

 

-익스텐션은 기존에 존재하는 타입이 추가로 다른 프로토콜을 채택할 수 있도록 확장할 수 있는데, 이런 경우는 다음과 같이 구분해준다

 

- 프로토콜을 채택할 경우의 문법

extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3 {
     //프로토콜 요구사항 구현
}

익스텐션으로 추가할 수 있는 기능

연산 프로퍼티

- 익스텐션을 통해 타입에 연산 프로퍼티를 추가할 수 있습니다.

- 예시)

extension Int {
      var isEven: Bool { //짝수일때 true를 반환
           return self % 2 == 0
      }

      var isOdd: Bool { //홀수일때 true를 반환
          return self % 2 == 1

      }

- 위 코드의 익스텐션은 Int타입에 두 개의 연산 프로퍼티를 추가한 것이다. 한 개는 짝수인지를 판별하는 Bool타입 연산 프로퍼티고 다른 하나는 홀수인지를 판별하는 Bool타입 연산 프로퍼티입니다.

- 위 익스텐션으로 Int타입에 추가해준 연산 프로퍼티는 int타입의 어떤 인스턴스에서도 사용 가능합니다.

- Static키워드를 사용하면 타입 연산 프로퍼티도 추가를 할 수 있습니다.

*여기서 타입 연산 프로퍼티는 타입 프로퍼티는 모든 타입이 사용할 수 있는 상수 프로퍼티라고 합니다.

 

- 익스텐션으로 연산 프로퍼티는 추가할 수는 있지만, 저장 프로퍼티는 추가할수가 없습니다. 또, 타입에 정의되어있는 기존의 프로퍼티에 프로퍼티 감시자를 추가할수 없습니다.

 

메서드

- 익스텐션을 통해 타입 메서드를 추가할수 있습니다.

- 예시)

extension Int {
       func multiply(by n: Int) -> Int {

             return self * n
       }

       mutating func multyplySelf(by n: Int) {
            self = self.multiply(by: n)

       }

       

       static func isIntTypeInstance(_ instance: Any) -> Bool { //타입이 int인지 확인하는 Bool타입, 타입메서드이다.

              return instance is Int

       }

}

- 위 메서드는 Int타입에 여러 종류의 메서드를 추가해 주었습니다.

 

이니셜라이저

 

- 인스턴스를 초기화할 때는 여러 종류의 이니셜 라이저를 만들 수 있습니다.

- 타입의 정의 부분에 이니셜라이저를 추가하지 않더라도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다.

- 단, 익스텐션으로 클래스 타입에 편의 이니셜라이저를 추가 할수는 있지만, 지정 이니셜라이저는 추가를 해줄수 없습니다! (헷갈리지 않는게 중요합니다!)

- 익스텐션에 지정 이니셜라이저를 추가해줄수 없는 이유는 지정이니셜라이저와 디 이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 하기 때문입니다.

- 익스텐션으로 값 타입(열거형, 구조체 등)에 이니셜라이저를 추가했을때 해당 값 타입이 다음 조건을 모두 성립한다면 익스텐션으로 사용자 정의 이니셜라이저를 추가한 이후에도 해당 타입의 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출할수 있다.

  1. 모든 저장 프로퍼티에 기본값이 있다.
  2. 타입에 기본 이니셜라이저와 멤버와이즈 이니셜라이저 외에 추가 사용자 정의 이니셜라이저가 없다.
    *멤버와이즈 이니셜라이저란?
    - 이니셜라이저를 선언 안 했는데 자동으로 기본 프로퍼티가 포함되어있는 이니셜라이저가 생성되는 것을 멤버 와이즈 이니셜라이저라고 한다.
    (이해가 잘 안되시겠지만 다음 예제를 통해서 멤버와이즈 이니셜라이즈가 뭔지 설명해 드리겠습니다..ㅠㅠ)
  3. 익스텐션을 통해 추가하는 이니셜라이저는 타입의 기존 이니셜라이저가 갖는 책무를 동일하게 수행해야 한다.
    즉, 이니셜라이저 호출이 종료되는 시점까지 인스턴스가 정상적으로 완벽하게 초기화하는 것을 책임져야 한다.

- 위 조건을 통과하면 이제 익스텐션으로 추가한 이니셜라이저를 해당타입의 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 호출 할수 있습니다.

struct Size {
	var width: Double = 0.0
	var height: Double = 0.0
}

struct Point {
	var x: Double = 0.0
	var y: Double = 0.0
}

struct Rect {
	var orgin: Point = Point()
	var size: Size = Size()
}

let defaultRect: Rect = Rect()

let memberwiseRect: Rect = Rect(orgin: Point(x:2.0, y:2.0), size: Size(width: 5.0, height:5.0))
//이렇게 이니셜라이저를 따로 설정은 해주지 않았지만 자동으로 이니셜라이저가 되는것을 멤버와이즈 이니셜라이저라고 합니다.

extension Rect {
	init(center: Point, size: Size) {
    	let orginX: Double = center.x - (size.width / 2)
        let orginY: Double = center.y - (size.height / 2)
        
        self.init(orgin: Point(x: orginX, y: orginY), size: Size)
        }
}

let centerRect: Rect = Rect(center: Point(x:4.0, y:4.0), size: Size(width: 3.0, height: 3.0))

-위 코드에서 memberwiseRect 프로퍼티는 이렇게 이니셜라이저를 따로 설정해 주지 않았는데 자동으로 Size, Point, Rect구조체의 이니셜라이저가 자동으로 생겼는데요 이것을 멤버와이즈 이니셜라이저라고 합니다.

 

- 위 예시를 보면 Size 구조체와 Point구조체의 모든 저장 프로퍼티는 기본값을 가지면 추가로 사용자 정의 이니셜라이저를 구현하지 않았기 때문에 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 사용할 수 있는 것을 볼 수 있다.

- 이 이유 때문에 익스텐션에서 추가해주는 새로운 이니셜라이저는 멤버와이즈 이니셜라이저에게 초기화를 위임해줄 수도 있습니다. 

 

중첩 데이터 타입

- 익스텐션을 통해 타입에 중첩 데이터 타입을 추가해 줄수 있습니다.

- 예시)

extension Int {
    enum kind {
        case negative, zero, positive
    }
    
    var kind: kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

print(1.kind) //positive
print(0.kind) //zero
print((-1).kind) //negative

func printIntegerKinds(numbers: [Int]) {
    for number in numbers {
        switch number.kind {
        case .negative:
            print("-", terminator: "")
        case .zero:
            print("0 ", terminator: "")
        case .positive:
            print("+", terminator: "")
        }
    }
    print("")
}
printIntegerKinds(numbers: [3,19,-27,0,-6,0,7]) //+,+,-,0,-,0,+

- 위의 예시는 익스텐션을 통해 Int타입에 Kind라는 열거형 타입과 Kind타입의 연산 프로퍼티를 추가해 주었습니다.

- Kind프로퍼티는 인스턴스가 양수인지 음수인지 0인지 판별하여 Kind를 반환하는 연산 프로퍼티입니다.

- printIntegerKinds(numbers:) 함수는 Int 타입의 값의 배열을 전달받아 각 값의 부호를 kind연산 프로퍼티를 통해 해당 값을 print()해주는 함수이다.

 

- 이번 예제를 공부하다가 print 함수 뒤에 terminator 매개변수를 볼 수 있는데 이 매개변수 역할을 찾아보니깐 한 줄로 쭉 출력하기 위한 것으로 줄 바꿈을 하지 않기 위해 기본적으로 줄바꿈 문자로 지정되어있는 terminator에 빈 문자열을 전달해 준 것입니다.

만약 terminator에 빈 문자열로 지정해주지 않으면 출력은
+

+

-

0

-

0

+

로 출력이 되었겠지만 terminator에 빈 문자열로 지정해주고 출력을 하면 아래처럼 numbers에 수만큼 일자로 출력을 하게 될 것입니다.

++-0-0+

 

지금까지 extension을 공부해봤는데요 이걸 공부하기 전까지는 그냥 확장한다고만 알고 있었는데 이번에 공부하면서 추가할 수 있는 기능까지 공부할 수 있고 다방면으로 extension을 공부하게 되어서 좋았던 포스팅입니다 :)

 

그럼 오늘도 좋은 하루 보내세요 감사합니다 ^^

반응형
Comments