|
| 1 | +/* |
| 2 | +익스텐션 |
| 3 | +익스텐션(Extension) 은 스위프트의 강력한 기능 중 하나입니다. |
| 4 | +익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 __기능을 추가__할 수 있는 기능입니다. |
| 5 | + |
| 6 | +기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있습니다. |
| 7 | + |
| 8 | +스위프트의 익스텐션이 타입에 추가할 수 있는 기능 |
| 9 | + |
| 10 | +연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티 |
| 11 | +타입 메서드 / 인스턴스 메서드 |
| 12 | +이니셜라이저 |
| 13 | +서브스크립트 |
| 14 | +중첩 타입 |
| 15 | +특정 프로토콜을 준수할 수 있도록 기능 추가 |
| 16 | +익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없습니다. |
| 17 | + |
| 18 | + |
| 19 | +클래스의 상속과 익스텐션을 비교해보겠습니다. 이 둘은 비슷해보이지만 실제 성격은 많이 다릅니다. |
| 20 | +클래스의 상속은 클래스 타입에서만 가능하지만 익스텐션은 구조체, 클래스, 프로토콜 등에 적용이 가능합니다. |
| 21 | +또 클래스의 상속은 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가 기능을 구현하는 수직 확장이지만, |
| 22 | +익스텐션은 기존의 타입에 기능을 추가하는 수평 확장입니다. 또, 상속을 받으면 기존 기능을 재정의할 수 있지만, |
| 23 | +익스텐션은 재정의할 수 없다는 것도 큰 차이 중 하나입니다. |
| 24 | +상황과 용도에 맞게 상속과 익스텐션을 선택하여 사용하면 됩니다. |
| 25 | + |
| 26 | + |
| 27 | + 상속 익스텐션 |
| 28 | +확장 수직 확장 수평 확장 |
| 29 | +사용 클래스 타입 클래스, 구조체, 프로토콜, 제네릭 등 모든 타입 |
| 30 | +재정의 가능 불가능 |
| 31 | + |
| 32 | +익스텐션을 사용하는 대신 원래 타입을 정의한 소스에 기능을 추가하는 방법도 있겠지만, |
| 33 | +외부 라이브러리나 프레임워크를 가져다 썼다면 원본 소스를 수정하지 못합니다. |
| 34 | +이처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다. |
| 35 | +따로 상속을 받지 않아도 되며, 구조체와 열거형에도 기능을 추가할 수 있으므로 익스텐션은 매우 편리한 기능입니다. |
| 36 | +익스텐션은 모든 타입에 적용할 수 있습니다. 모든 타입이라 함은 구조체, 열거형, 클래스, 프로토콜, 제네릭 타입 등을뜻합니다. |
| 37 | +즉, 익스텐션을 통해 모든 타입에 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트, 중첩 데이터 타입 등을 추가할 수 있습니다. |
| 38 | +더불어 익스텐션은 __프로토콜과 함께 사용__하면 굉장히 강력한 기능을 선사합니다. |
| 39 | +이 부분은 __프로토콜 중심 프로그래밍(Protocol Oriented Programming)__에 대해 더 알아보면 좋습니다. |
| 40 | + |
| 41 | +*/ |
| 42 | + |
| 43 | +/* |
| 44 | +정의 문법 |
| 45 | +extension 키워드를 사용하여 정의합니다. |
| 46 | + |
| 47 | +extension 확장할 타입 이름 { |
| 48 | + /* 타입에 추가될 새로운 기능 구현 */ |
| 49 | +} |
| 50 | + |
| 51 | + |
| 52 | +익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다. |
| 53 | +이런 경우에는 클래스나 구조체에서 사용하던 것과 똑같은 방법으로 프로토콜 이름을 나열해줍니다. |
| 54 | + |
| 55 | +extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3... { |
| 56 | + /* 프로토콜 요구사항 구현 */ |
| 57 | +} |
| 58 | + |
| 59 | +스위프트 라이브러리를 살펴보면 실제로 익스텐션이 굉장히 많이 사용되고 있음을 알 수 있습니다. |
| 60 | +Double 타입에는 수많은 프로퍼티와 메서드, 이니셜라이저가 정의되어 있으며 수많은 프로토콜을 채택하고 있을 것이라고 예상되지만, |
| 61 | +실제로 Double 타입의 정의를 살펴보면 그 모든것이 다 정의되어 있지는 않습니다. |
| 62 | +그러면 Double 타입이 채택하고 준수해야 하는 수많은 프로토콜은 어디로 갔을까요? 어디에서 채택하고 어디에서 준수하도록 |
| 63 | +정의되어 있을까요? 당연히 답은 익스텐션입니다. 이처럼 스위프트 표준 라이브러리 타입의 기능은 대부분 익스텐션으로 구현되어 있습니다. |
| 64 | +Double 외에도 다른 타입들의 정의와 익스텐션을 찾아보면 더 많은 예를 보실 수 있습니다. 꼭 한 번 찾아보세요! |
| 65 | +*/ |
| 66 | + |
| 67 | +//익스텐션 구현 |
| 68 | +//연산 프로퍼티 추가 |
| 69 | +extension Int { |
| 70 | + var isEven: Bool { |
| 71 | + return self % 2 == 0 |
| 72 | + } |
| 73 | + var isOdd: Bool { |
| 74 | + return self % 2 == 1 |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// 이렇게 정수 타입이면 다 사용 가능함 |
| 79 | +// 1 이놈은 정수타입에 인스턴스를나타네는 리터럴 문법이라고 생각해 볼수 있음 |
| 80 | +print(1.isEven) // false |
| 81 | +print(2.isEven) // true |
| 82 | +print(1.isOdd) // true |
| 83 | +print(2.isOdd) // false |
| 84 | + |
| 85 | +var number: Int = 3 |
| 86 | +print(number.isEven) // false |
| 87 | +print(number.isOdd) // true |
| 88 | + |
| 89 | +number = 2 |
| 90 | +print(number.isEven) // true |
| 91 | +print(number.isOdd) // false |
| 92 | + |
| 93 | +// 위 코드의 익스텐션은 Int 타입에 두 개의 연산 프로퍼티를 추가한 것입니다. |
| 94 | +// Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool 타입으로 알려주는 연산 프로퍼티입니다. |
| 95 | +// 익스텐션으로 Int 타입에 추가해준 연산 프로퍼티는 Int 타입의 어떤 인스턴스에도 사용이 가능합니다. |
| 96 | +// 위의 코드처럼 인스턴스 연산 프로퍼티를 추가할 수도 있으며, static 키워드를 사용하여 타입 연산 프로퍼티도 추가할 수 있습니다. |
| 97 | + |
| 98 | + |
| 99 | +//메서드 추가 |
| 100 | +extension Int { |
| 101 | + func multiply(by n: Int) -> Int { |
| 102 | + return self * n |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | + |
| 107 | +print(3.multiply(by: 2)) // 6 |
| 108 | +print(4.multiply(by: 5)) // 20 |
| 109 | + |
| 110 | +number = 3 |
| 111 | +print(number.multiply(by: 2)) // 6 |
| 112 | +print(number.multiply(by: 3)) // 9 |
| 113 | + |
| 114 | +//위 코드의 익스텐션을 통해 Int 타입에 인스턴스 메서드인 multiply(by:) 메서드를 추가했습니다. |
| 115 | +//여러 기능을 여러 익스텐션 블록으로 나눠서 구현해도 전혀 문제가 없습니다. |
| 116 | +//관련된 기능별로 하나의 익스텐션 블록에 묶어주는 것도 좋습니다. |
| 117 | + |
| 118 | +//이니셜라이저 추가 |
| 119 | +extension String { |
| 120 | + init(int: Int) { |
| 121 | + self = "\(int)" |
| 122 | + } |
| 123 | + |
| 124 | + init(double: Double) { |
| 125 | + self = "\(double)" |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +let stringFromInt: String = String(int: 100) |
| 130 | +// "100" |
| 131 | + |
| 132 | +let stringFromDouble: String = String(double: 100.0) |
| 133 | +// "100.0" |
| 134 | +//인스턴스를 초기화(이니셜라이즈)할 때 인스턴스 초기화에 필요한 다양한 데이터를 전달받을 수 있도록 여러 종류의 이니셜라이저를 |
| 135 | +//만들 수 있습니다. 타입의 정의부에 이니셜라이저를 추가하지 않더라도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다. |
| 136 | +//하지만 익스텐션으로 클래스 타입에 편의 이니셜라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없습니다. |
| 137 | +//지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 합니다(값 타입은 상관없습니다). |
0 commit comments