본문 바로가기

iOS/Swift

Swift) 확장(extension) 정복하기

 

 

 

안녕하세요 :) 소들입니다 호호홋

오늘은 Swift 문법 중에 확장!!! 엄청 많이 쓰는 extensionㅇㅔ 대해 알아볼 거예요!

Objective-C를 아시는 분이라면 익명 Category라고 생각하면 될 것 같아요!!

 

그럼 시작해봅시닷

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. 확장(extension)이란?

 

기존 클래스, 구조체, 열거형 타입에 새로운 Property, Method, Initializer 등을 추가하는 것으로,

원본 타입(소스 코드)에 접근하지 못하는 타입들도 확장해서 사용할 수 있다

extension이란 키워드를 사용하여 확장한다

 

ㅇㅖ..뭐.. 정의는.. 그렇습니다.. 

타입을 확장하는 문법은 다음과 같은데

 

 

 

extension SomeType {
}
 

 

extension SomeType: SomeProtocol, AnotherProtocol {
}
 

 

 

이렇게 extension을 쓰고 확장하고자 하는 타입을 쓰는 것임!!

그리고 그 뒤에 추가로 채택하고자 하는 Protocol을 추가할 수도 있음 :)

 

말로만 봐서는 .. 이해가 .. 잘.. 안가는 것...

원본 타입에 접근하지 못하는 타입들을 확장한다??? 예제를 보잣

 

 

let point: CGPoint = .init(x: 10, y: 20)

 

 

자 CGPoint라는 구조체는 코어 그래픽에 포함되어 있는 구조체임

근데 만약 내가 point란 변수를 print로 출력하고 싶엉.. 다음과 같이..

 

 

x: 10, y: 20

 

 

이렇게!! 근데 CGPoint란 구조체는 위처럼 예쁘게 값을 출력해주는 기능(함수)가 없음ㅎㅎㅋ

따라서 만약 출력하고 싶다면, 내가 필요할 때마다 직접 print하는 함수를 구현 했어야 함

 

 

print("x: \(point.x), y: \(point.y)")

 

 

이렇게!!! 

아~~ 만약 CGPoint가 저렇게 출력해주는 함수를 자체적으로 가지고 있음 을매나 좋아~~

하지만 CGPoint는 이미 프레임워크단에서 지원해주는 미리 정의된 구조체라서,

내가 어떻게 CGPoint 원본 코드를 건들 방법이 없음!!!

 

자, 이럴 때 사용하는 것이 바로 extension!!!

원본은 코드는 그대로 두고, 말 그대로 내가 원하는 기능만 해당 타입에 확장하는 것임

 

 

extension CGPoint {
    func printPoint() {
        print("x: \(self.x), y: \(self.y)")
    }
}
 

 

 

이렇게!!! extension을 쓰고 그 뒤에 확장할 타입의 이름을 쓰는 것임!!

그리고 그 안에 내가 원하는 printPoint라는 출력 메서드를 구현하면,

 

 

 

 

마치 CGPoint 함수에 원래 printPoint 함수가 있었던 마냥 사용할 수 있돠

 

 

 

 

 

실제로 출력도 잘 됨 :)

 

자, 근ㄷ ㅔ 여기서 헷갈릴 수 있는 거!!!

우리가 CGPoint 원본을 건들지는 않았지만, 위처럼 확장할 경우

앞으로 CGPoint를 선언하는 모~~~~~든 인스턴스는 CGPoint + extension 으로 구현됨!!!

(물론 where절로 제한을 줄 수 있지만 이건 좀이따 봅시다)

 

 

 

 

2. 확장에 프로퍼티 추가하기

 

저장 프로퍼티는 추가할 수 없으며, 오로지 "연산 프로퍼티"만 추가 가능하다

 

제한 사항임..

extension을 이용해 확장할 경우, 연산 프로퍼티만 추가할 수 있음

만약 저장 프로퍼티를 추가하려고 하면,

 

 

 

 

extension에 저장 프로퍼티가 포함되면 안된다며 에러 작렬

따라서 다음과 같이 연산 프로퍼티만 가능함!

 

 

extension Int {
    var half: Int {
        return self / 2
    }
}

 

 

이렇게 선언된 연산 프로퍼티는, 

 

 

let num = 100
print(num.half)           // 50

 

 

 

이제 모~~~~~든 Int 타입에서 이렇게 사용이 가능함!!

프로퍼티에 대해 잘 이해가 안 간다면 이 포스팅을 먼저 보시오~~!!

 

 

 

 

3. 확장에 메서드 추가하기

 

인스턴스 메서드, 타입 메서드 모두 추가 가능하다

 

어려울 것 없으니 예제로만 후딱 보고 넘어가잣 :)

 

 

// 타입 메서드
extension Int {
    static func printZero() {
        print(0)
    }
}
 
Int.printZero()             // 0

 

// 인스턴스 메서드
extension Int {
    func printDouble() {
        print(self * 2)
    }
}
 
let num = 100
num.printDouble()           //200

 

 

쨔쟌~~ 타입 메서드와 인스턴스 메서드를 모른다면

이 포스팅을 보시오~~!!

 

 

 

 

4. 확장에 생성자(initializer) 추가하기

 

기존 타입에 새로운 이니셜라이저를 추가할 수 있다

 

 

 

4-1. Class에서의 생성자 추가

 

Designated initializer는 추가할 수 없고 Convenience initializer만 추가할 수있으며,

deinitializer를 추가할 수 없다

 

갑자기 Designated.. Convenience.. 생성자 ㅇ ㅓ쩌고;;

Designated Initializer는 클래스의 모든 속성(슈퍼 포함)을 초기화 하는 생성자고,

Convenienct Initializer 편의 초기화로 약간 유틸리티 성격의 생성자인데...

 

이 둘에 대해 다루려면 내용이 깊고 많고 어려워지기 때문에,

이 둘은 나중에 생성자 포스팅 때 쓸려고 아껴둘 거니까, 쓰고 나면 여기에 추가 하겠음

 

지금은 아 그렇고만.. 클래스에서는

 

 

 

 

Deinitializer(소멸자)는 extension으론 구현 못하고만... (원본 클래스에서만 구현해야 함)

 

 

 

 

init 메서드도 구현 못하는고만.. (위처럼 생긴 지금껏 써왔던 것이 Designated Initializer)

 

 

 

 

 

뭔진 모르겠지만 convenience init 메서드는 구현 할 수 있고만...

(Convenience initializer는 최종적으로 Designated Initializer를 호출해야 함)

정도만 알고 넘어가길 :)

 

 

 

4-2. Struct에서 생성자 추가

 

extension으로 생성자를 추가할 경우,

Memberwise Initializer를 보존하며 새로운 생성자를 추가할 수 있다

 

아;;;  Memberwise Initializer는 또 먼데염..;;

이것도 생성자 편에서 다룰라 했는데, 쉬우니까 대충 설명 하자면

구조체는 클래스와 달리 "기본 생성자(memberwise Initializer)"를 자동으로 제공

 

원래 구조체건 클래스건 인스턴스 생성자 호출이 끝나는 시점엔 모~든 프로퍼티가

초기화 되어 있어야 하잖음??

때문에 프로퍼티는 기본값을 지니지 않을 경우 무족권 생성자에서 초기화 해야 했음

 

에?? 근데

 

 

struct PointStruct {
    let x: Int
    let y: Int
}
 
class PointClass {          // error! Class 'HumanClass' has no initializers
    let x: Int
    let y: Int
}
 

 

 

구조체는 init 함수 없어도 에러 안 나는데, 클래스만 생성자(initializer)가 없다고 에러남;; 

이거슨 클래스가 개복치라서가 아니라, 클래스와 달리 구조체가

모든 프로퍼티를 초기화 할 수 있게 하는 Memberwise라는 생성자

"따로 생성자를 구현하지 않았을 경우"에 한해서 자동으로 제공함

 

위처럼 HumanStruct는 생성자를 따로 구현하지 않았기 떄문에,

자동으로 Memberwise Initializer가 제공됨

 

 

 

 

이렇게! 이게 Memberwise Initializer임!!!

근데, 이 Memberwise Initializer는 매우 독단적인 놈이라, 만약 내가 생성자를 "직접" 구현 한다면,

 

 

struct PointStruct {
    let x: Int
    let y: Int
 
    init(value: Int) {
        self.x = value
        self.y = value
    }
}

 

 

이렇게 직접 구현해 버리면, 

 

 

 

 

Memberwise Initializer는 더이상 제공되지 않음

 

근데 뭐~라~고~~~~~~~~ extension을 사용하면 Memberwise Initializer를 보존하면서

새로운 생성자를 추가할 수 있다거~~~~~~~~?

 

 

struct PointStruct {
    let x: Int
    let y: Int
}
 
extension PointStruct {
    init(value: Int) {
        self.x = value
        self.y = value
    }
}

 

 

extension을 통해 (구조체 한정) 생성자를 추가하면!!!!!!1

 

 

 

 

Memberwise Initializer를 보존하며 생성자를 추가할 수 있다..!!!

뭐.. 그런 말임...!

 

 

 

 

5. 확장에 서브 스크립트(Subscript) 추가하기

 

 

extension String {
    subscript(idx: Int-> String? {
        guard (0..<count).contains(idx) else {
            return nil
        }
        let target = index(startIndex, offsetBy: idx)
        return String(self[target])
    }
}
 

 

 

 위와 같이 extension을 통해 서브스크립트를 직접 구현해주면,

 

 

let sodeul = "Hello, Sodeul!"
sodeul[0]           // Optional("H")
sodeul[100]         // nil

 

 

이렇게 []를 통해 내가 원하는 index의 문자에 접근할 수 있음!!ㅎㅎ

만약 서브스크립트를 모른다면 이 포스팅을 보시길..

 

 

 

 

6. 확장에 중첩 타입 추가하기

 

가능한가!? 물논...!

이번엔 Swift 공식 문서 예제좀 훔쳐 오겠음

 

 

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
            }
    }
}
 

 

 

이렇게 Int 타입의 extension 안에 Kind라는 enum을 중첩해서 선언할 수 있고,

 

 

let num = 100
print(num.kind)      // positive
 
let num2 = -100
print(num2.kind)     // negative
 

 

 

사용도 가능허다...!

 

 

 

 

7. 확장에 프로토콜 추가하기

 

 

struct Human {
    let name: String
}
 
let sodeul: Human = .init(name: "sodeul")
let sodeulsodeul = sodeul
 
sodeul == sodeulsodeul      // error! Binary operator '==' cannot be applied to two 'Human' operands
 

 

 

프로토콜 포스팅도 아직 안 했지만.. (한다면 추가함)
내가 선언한 Human이란 인스턴스끼리 == 연산자를 통해 비교하고 싶은데

에러가 뜨면서 비교할 수 없음..!!

왜냐?? == 연산자를 사용하고 싶다면 Equatable이란 프로토콜을 채택하고 있어야 하거든..!

물논 Human 구조체 자체에 다음과 같이 프로토콜을 채택해도 되지만,

 

 

struct Human: Equatable {
    let name: String
 
    public static func == (lhs: Human, rhs: Human) -> Bool {
        return lhs.name == rhs.name
    }
}
 

 

 

이렇게 해도 문제는 없으나 보통 프로토콜을 채택할 땐,

원본은 원본에 관련된 함수들만 두고

 

 

struct Human {
    let name: String
}
 
extension HumanEquatable {
    public static func == (lhs: Human, rhs: Human) -> Bool {
        return lhs.name == rhs.name
    }
}
 

 

 

extension을 이용하여,

해당 프로토콜에 맞는 메서드만 구현하는 것이 유지보수나 가독성면에서 좋음..!!

 

 

 

7-1. extension을 통한 코드 가독성 높이기

 

위에서 설명한 것과 같은 맥락이긴 한데,,

만약 하나의 ViewController에서 TableViewDataSource & CollectionViewDataSource

채택해야 한다면,

 

class ViewControllerUIViewControllerUITableViewDataSourceUICollectionViewDataSource {
 
    override func viewDidLoad() {
    }
 
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int-> Int {
    }
 
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    }
 
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int-> Int {
    }
 
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    }
}
 

 

 

이렇게 원본 클래스에 모든 것을 작성해도 되지만....;;;;;

상당히... 지저분 하군요............

우린 extension을 배웠으니 활용해 봅시다...!

 

 

class ViewControllerUIViewController {
    override func viewDidLoad() {
    }
}
 
extension ViewControllerUITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int-> Int {
    }
 
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    }
}
 
extension ViewControllerUICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int-> Int {
    }
 
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    }
}
 

 

 

짜잔~~

이렇게 extension을 통해 프로토콜 기능별로 분리 한다면 코드가 더 깔끔해짐!!

또한 이 extension은 Objective-C의  #pragma mark 기능도 하기 때문에

 

 

 

 

extension에 따라 트리 구조가 바뀜!!!!!!!!

따라서 extension을 잘 활용하면 좋은 코드를 짤 수 있음 :)

 

 

 

 

8. 범용 타입에서 where을 통해 확장에 조건 두기

 

이건 나중에 Generic 공부할 때 다시 보겠지만, 범용 타입으로 선언된 자료형의 경우,

where이라는 키워드를 이용해서 확장에 제한을 둘 수 있음!!!

(Generic을 모른다면 이해 못할 수도...?)

 

 

 

8-1. 특정 프로토콜을 채택한 타입만 확장 가능하게 하기

 

 

extension Array where ElementEquatable {
    func printAll() {
        for item in self { print(item) }
    }
}
 

 

 

이렇게 범용 타입 Array의 Generic Type인 Element

Equatable이라는 프로토콜 조건을 where을 이용해서 주면

Equatable이란 프로토콜을 채택하고 있는 타입에 한해서만 타입이 확장됨!!

 

 

let nums: [Int= [12]
let points: [(IntInt)] = [(00), (11)]
 
nums.printAll()
points.printAll()               // error! Type '(Int, Int)' cannot conform to 'Equatable'

 

 

Array의 타입이 "Equatable을 채택하고 있는 Int"인 nums의 경우엔 타입이 확장되어,

extension에서 구현한 printAll이란 메서드를 사용할 수 있지만,

 

Array의 타입이 "Equatable을 채택하지 않는 튜플"인 points의 경우엔 타입 확장이 제한되어,

extension에서 구현한 printAll이란 메서드를 사용할 수 없음!!

 

 

 

8-2. 특정 타입만 확장 가능하게 하기

 

뭐 위랑 비슷한데, where을 다음과 같이 ==를 이용해서 쓸 경우

 

 

extension Array where Element == Int {
    func printAll() {
        for item in self { print(item) }
    }
}
 

 

 

오로지 "Int형 배열"만 확장이 가능하기 때문에,

 

 

let intNums: [Int= [1234]
let doubleNums: [Double] = [1.02.03.04.0]
 
intNums.printAll()
doubleNums.printAll()        // error! Referencing instance method 'printAll()' on 'Array' requires the types 'Double' and 'Int' be equivalent
 

 

 

이렇게 Array의 자료형이 Int형인 intNums는 확장이 되고,

그외의 타입인 doubleNums는 제한 조건에 걸려 확장이 적용되지 않음!

 

 

 

 

 

.

.

.

 

오늘은 extension에 대해 공부 했는데,

아마 읽으신 분이 있다면 생성자 부분에서 막히신 분이 많을 듯..ㅠ.ㅠ

최대한 빨리 생성자 부분 포스팅해서 링크 걸어 놓겟습다~~~~~~

피드백 & 궁금증 댓글 환영 🌝

 

 



Calendar
«   2024/04   »
1 2 3 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
최근 댓글
Visits
Today
Yesterday