확장
기존 클래스, 구조체, 열거형 타입에 새로운 Property, Method, Initializer등을 추가하는 것으로,
원본(소스코드)에 접근하지 못하는 타입들도 확장해서 사용할 수 있다. extension이란 키워드를 사용하여 확장한다.
상속은 기존 클래스, 구조체, 열거형 타입을 받아 추가적으로 작성하는 수직형 확장이라면,
확장은 기존 클래스, 구조체, 열거형 타입 자체에 Property, Method, Initializer를 추가하는 수평형 확장.
상속은 저장프로퍼티를 추가
확장은 메서드를 추가
타입을 확장하는 문법.
extension SomeType{
}
extension SomeType: SomeProtocol, AnotuerProtocol{
}
extension을 적고 확장하고자 하는 타입을 적는다.
그리고 뒤에 채택하고자 하는 Protocol을 추가할 수 있다.
예를 들어,
CGPoint라는 구조체는 코어 그래픽에 포함된 구조체이다.
let point: CGPoint = .init(x: 10, y: 20)
만약 point라는 변수를 아래처럼 print로 출력하고싶다면...
x는 10 이구요 y는 20입니다
이렇게 출력해주는 함수는 없다.
CGPoint는 프레임워크에서 지원해주는 미리 정의된 구조체라 개발자가 저런 방식의 프린트를 하는 메서드를 추가할 수 없다.
CGPoint의 원본 코드를 건들 방법이 없는 것이다.
이럴 때 사용하는게 extension이다.
원본 코드는 그대로 두고, 내가 원하는 기능만 해당 타입에 확장하는것이다.
원본 코드는 건들 수 없지만 CGPoint원본의 기능을 확장하는 개념.
extension CGPoint {
func printPoint() {
print("x는 \(self.x) 이구요 y는 \(self.y)입니다.")
}
}
확장의 이론
- 메서드 형태만 정의할 수 있다.
- 인스턴스 저장 프로퍼티는 추가할 수 없다.
- 인스턴스의 저장속성의 메모리 확정은 지정생성자를 통해서 일어난다. 확장에서 생성한 저장 프로퍼티는 본체의 지정생성자에서 초기화될 수 없기 때문에 확장에서의 저장프로퍼티 추가는 불가능하다.
- 타입 저장 프로퍼티는 추가할 수 있다.
- 메모리적 관점: static 프로퍼티는 타입 자체에 속하며 인스턴스에는 해당되지 않는다. 따라서 이러한 프로퍼티를 추가하더라도 인스턴스의 메모리에는 아무런 영향을 미치지 않는다.
- 접근성: static 프로퍼티는 해당 타입 이름을 통해 직접 접근된다 예를들어 Int.two와 같이 사용되기 때문에 인스턴스를 통해 접근하지 않는다. 인스턴스의 메모리 할당 여부에 관계없기 때문에 확장에서 추가할 수 있다.
- 인스턴스/타입 연산 프로퍼티
- 메서드를 프로퍼티형식으로 작성한 것이 연산프로퍼티므로 메모리를 차지하지 않기때문에 확장이 가능하다.
- 인스턴스/타입 메서드
- 생성자: 클래스의 경우 편의생성자만 추가가능( 지정생성자 및 소멸자는 반드시 본체에 구현)
- 서브스크립트
- 새로운 중첩타입 정의 및 사용
- 프로토콜 채택 및 프로토콜 관련 메서드
- 본체를 재정의할 순 없다.
1. 연산프로퍼티(계산속성)의 확장
extension Double{
static var zero: Double{ return 0.0 }
}
extension Int{
var squared: Int{
return self * self
}
}
2. 메서드의 확장
// 타입 메서드
extension Int {
static func printNumbersFrom1to5(){
for i in 1...5{
print(i)
}
}
}
// 인스턴스 메서드
extension String{
func printHelloRepeat(of times: Int:){
for _ in 0..<times{
print("Hello \(self)!")
}
}
}
확장한 타입을 상속받을 경우 확장에서 구현한 것들에대한 재정의는 불가능하다
3. 생성자⭐️⭐️⭐️
// UIColor의 기본생성자
var color = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)
extension UIColor{
convenience init(color: CGFloat){
self.init(red: color/255, green: color/255, blue: color/255, alpha: 1)
}
}
// 기본생성자로 불편.
var color1 = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)
//UIColor를 확장해서 편의생성자로 편안
var color2 = UIColor.init(color: 1)
✅ 클래스: 편의생성자만 구현가능 ➡️ 본체의 지정생성자를 호출하는 방법으로만 구현가능
✅ 구조체등: 지정생성자의 형태로도 생성자 구현가능 (상속과 관련 없기 때문)
구조체에서 생성자를 구현하게되면 memberwise initializer를 사용할 수 없게 되는데,
구조체를 확장할 때 extension에 생성자를 구현한다면 memberwise initializer를 보존하고 생성자를 구현할 수 있다.
4. 서브스크립트
extension Int{
subscript(num: Int) -> Int{
var decimalBase = 1
for _ in 0..<num{
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
123456789[0] // (123456789 / 1) ==> 123456789 % 10 ==> 나머지 9
123456789[1] // (123456789 / 10) ==> 12345678 % 10 ==> 나머지 8
123456789[2] // (123456789 / 100) ==> 1234567 % 10 ==> 나머지 7
123456789[3] // (123456789 / 1000) ==> 123456 % 10 ==> 나머지 6
5. 중첩타입
extension Int {
enum Kind { // 음수인지, 0인지, 양수인지
case negative, zero, positive
}
var kind: Kind { // 계산 속성으로 구현
switch self {
case 0: // 0인 경우
return Kind.zero
case let x where x > 0: // 0보다 큰경우
return Kind.positive
default: // 나머지 (0보다 작은 경우)
return Kind.negative
}
}
}
6. 프로토콜과 프로토콜 관련 메서드.
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
extension Person: Equatable {
static func ==(lhs: Person, rhs: Person) -> Bool { // 특별한 이유가 없다면 모든 속성에 대해, 비교 구현
return lhs.name == rhs.name && lhs.age == rhs.age
}
}