본문 바로가기
iOS/SWIFT

[Swift] Chapter 21. 익스텐션

by 원만사 2021. 10. 11.
반응형

 

21.1 익스텐션이란


구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있다. 기능을 추가하려는 타입을 구현한 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 안다면 그 타입의 기능을 확장할 수도 있다. 스위프트의 익스텐션이 타입에 추가할 수 있는 기능은 다음과 같다.

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

익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없다. 클래스의 상속과 익스텐션을 비교해보자. 클래스의 상속은 클래스 타입에서만 가능하지만 익스텐션은 구조체, 클래스, 프로토콜 등에 적용이 가능하다. 또, 클래스의 상속은 특정 타입을 물려받아 하나의 새로운 타입을 정의하고 추가 기능을 구현하는 수직 확장이지만, 익스텐션은 기존의 타입에 기능을 추가하는 수평 확장이다. 상속을 받으면 기존 기능을 재정의할 수 있지만, 익스텐션은 재정의할 수 없다는 것도 큰 차이다.

 

 

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

익스텐션은 모든 타입에 적용할 수 있다. 즉, 익스텐션을 통해 모든 타입에 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트, 중첩 데이터 타입 등을 추가할 수 있다.

 

21.2 익스텐션 문법


extension 키워드를 사용하여 선언한다.

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

 

익스텐션은 기존에 존재하는 타입이 추가로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있다. 이런 경우에는 프로토콜 이름을 나열해 주면 된다.

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

 

스위프트 표준 라이브러리 타입의 기능은 대부분 익스텐션으로 구현되어 있다.

 

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


익스텐션을 통해 추가할 수 있는 기능에는 무엇이 있는지 알아보자

 

21.3.1 연산 프로퍼티

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

/*
	코드 21-3. 익스텐션을 통한 연산 프로퍼티 추가
*/

extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
    
    var isOdd: Bool {
        return self % 2 == 1
    }
}

print(1.isEven) // false
print(2.isEven) // true
print(1.isOdd) // true
print(2.isOdd) // false

var number: Int = 3
print(number.isEven) // false
print(number.isOdd) // true

number = 2
print(number.isEven) // true
print(number.isOdd) // false

 

[코드 21-3]의 익스텐션은 Int 타입에 두 개의 연산 프로퍼티를 추가한 것이다. Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool 타입으로 알려주는 연산 프로퍼티다. 이 연산 프로퍼티는 Int 타입의 어떤 인스턴스에서도 사용이 가능하다.

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

 

21.3.2 메서드

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

 

/*
	코드 21-4. 익스텐션을 통한 메서드 추가
*/

extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
    
    mutating func multiplySelf(by n: Int) {
        self = self.multiply(by: n)
    }
    
    static func isIntTypeInstance(_ instance: Any) -> Bool {
        return instance is Int
    }
}

print(3.multiply(by: 2)) // 6
print(4.multiply(by: 5)) // 20

var number: Int = 3

number.multiplySelf(by: 2)
print(number) // 6

Int.isIntTypeInstance(number) // true
Int.isIntTypeInstance(3) // true
Int.isIntTypeInstance(3.0) // false

prefix operator ++

struct Position {
    var x: Int
    var y: Int
}

extension Position {
    // + 중위 연산 구현
    static func + (left: Position, right: Position) -> Position {
        return Position(x: left.x + right.x, y: left.y + right.y)
    }
    
    // - 전위 연산 구현
    static prefix func - (vector: Position) -> Position {
        return Position(x: -vector.x, y: -vector.y)
    }
    
    // += 복합할당 연산자 구현
    static func += (left: inout Position, right: Position) {
        left = left + right
    }
}

extension Position {
    // == 비교 연산자 구현
    static func == (left: Position, right: Position) -> Bool {
        return (left.x == right.x) && (left.y == right.y)
    }
    
    // != 비교 연산자 구현
    static func != (left: Position, right: Position) -> Bool {
        return !(left == right)
    }
}

extension Position {
    // ++ 사용자 정의 연산자 구현
    static prefix func ++ (position: inout Position) -> Position {
        position.x += 1
        position.y += 1
        return position
    }
}

var myPosition: Position = Position(x: 10, y: 10)
var yourPosition: Position = Position(x: -5, y: -5)

print(myPosition + yourPosition) // Position(x: 5, y: 5)
print(-myPosition) // Position(x: -10, y: -10)

myPosition += yourPosition
print(myPosition) // Position(x: 5, y: 5)

print(myPosition == yourPosition) // false
print(myPosition != yourPosition) // true

print(++myPosition) // Position(x: 6, y: 6)

 

21.3.3 이니셜라이저

인스턴스를 초기화(이니셜라이즈)할 때 인스턴스 초기화에 필요한 다양한 데이터를 전달받을 수 있도록 여러 종류의 이니셜라이저를 만들 수 있다. 타입의 정의 부분에 이니셜라이저를 추가하지 않더라고 익스텐션을 통해 이니셜라이저를 추가할 수 있다.

하지만 익스텐션으로 클래스 타입에 편의 이니셜라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없다. 지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 한다.

 

/*
	코드 21-5. 익스텐션을 통한 이니셜라이저 추가
*/

extension String {
    init(intTypeNumber: Int) {
        self = "\(intTypeNumber)"
    }
    
    init(doubleTypeNumber: Double) {
        self = "\(doubleTypeNumber)"
    }
}

let stringFromInt: String = String(intTypeNumber: 100) // "100"
let stringFromDouble: String = String(doubleTypeNumber: 100.0) // "100.0"

class Person {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

extension Person {
    convenience init() {
        self.init(name: "Unknown")
    }
}

let someOne: Person = Person()
print(someOne.name) // "Unknown"

 

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

  • 모든 저장 프로퍼티에 기본값이 있다.
  • 타입에 기본 이니셜라이저와 멤버와이즈 이니셜라이저 외에 추가 사용자 정의 이니셜라이저가 ㅇ벗다.

 

익스텐션을 통해 추가하는 이니셜라이저는 타입의 기존 이니셜라이저가 갖는 책무를 동일하게 수행해야 한다. 즉, 이니셜라이저 호출이 종료되는 시점까지 인스턴스가 정상적으로 완벽하게 초기화되는 것을 책임져야 한다.

 

/*
	코드 21-6. 익스텐션을 통한 초기화 위임 이니셜라이저 추가
*/

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 origin: Point = Point()
    var size: Size = Size()
}

let defaultRect: Rect = Rect()
let memberwiseRect: Rect = Rect(origin: Point(x: 2.0, y: 2.0),
                                size: Size(width: 5.0, height: 5.0))

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

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

 

[코드 21-6]의 Size 구조체나 Point 구조체의 모든 저장 프로퍼티는 기본값을 가지며, 추가로 사용자 정의 이니셜라이저를 구현하지 않았기 때문에 기본 이니셜라이저와 멤버와이즈 이니셜라이저를 사용할 수 있다. 그렇기 때문에 익스텐션에서 추가해주는 새로운 이니셜라이저는 멤버와이즈 이니셜라이저에게 초기화를 위임해줄 수 있다.

 

21.3.4 서브스크립트

익스텐션을 통해 타입에 서브스크립트를 추가할 수 있다.

 

/*
	코드 21-7. 익스텐션을 통한 서브스크립트 추가
*/

extension String {
    subscript(appedValue: String) -> String {
        return self + appedValue
    }
    
    subscript(repeatCount: UInt) -> String {
        var str: String = ""
        
        for _ in 0..<repeatCount {
            str += self
        }
        
        return str
    }
}

print("abc"["def"]) // "abcdef"
print("abc"[3]) // "abcabcabc"

 

21.3.5 중첩 데이터 타입

익스텐션을 통해 타입에 중첩 데이터 타입(Nested Types)을 추가할 수 있다. 중첩 데이터 타입에 대해서는 타입 중첩(24장)에서 자세히 알아보자.

 

/*
	코드 21-8. 익스텐션을 통한 중첩 데이터 타입 추가
*/

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 +

 

 

 

스위프트 프로그래밍: Swift 5 - 교보문고

객체지향, 함수형, 프로토콜 지향 패러다임까지 한 번에! | 스위프트를 제대로 이해하고 싶은 개발자를 위한 책스위프트는 iOS와 macOS용 애플리케이션 개발에 주로 사용하는 프로그래밍 언어입니

www.kyobobook.co.kr

 

반응형

'iOS > SWIFT' 카테고리의 다른 글

[Swift] Chapter 23. 프로토콜 지향 프로그래밍  (0) 2021.10.13
[Swift] Chapter 22. 제네릭  (0) 2021.10.13
[Swift] Chapter 20. 프로토콜  (0) 2021.10.11
[Swift] Chapter 19. 타입캐스팅  (0) 2021.10.11
[Swift] Chapter 18. 상속  (0) 2021.10.10

댓글