안녕하세요 :) 소들입니다 호호홋
오늘은 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 Human: Equatable {
public static func == (lhs: Human, rhs: Human) -> Bool {
return lhs.name == rhs.name
}
}
|
extension을 이용하여,
해당 프로토콜에 맞는 메서드만 구현하는 것이 유지보수나 가독성면에서 좋음..!!
7-1. extension을 통한 코드 가독성 높이기
위에서 설명한 것과 같은 맥락이긴 한데,,
만약 하나의 ViewController에서 TableViewDataSource & CollectionViewDataSource를
채택해야 한다면,
class ViewController: UIViewController, UITableViewDataSource, UICollectionViewDataSource {
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 ViewController: UIViewController {
override func viewDidLoad() {
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
}
extension ViewController: UICollectionViewDataSource {
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 Element: Equatable {
func printAll() {
for item in self { print(item) }
}
}
|
이렇게 범용 타입 Array의 Generic Type인 Element에
Equatable이라는 프로토콜 조건을 where을 이용해서 주면
Equatable이란 프로토콜을 채택하고 있는 타입에 한해서만 타입이 확장됨!!
let nums: [Int] = [1, 2]
let points: [(Int, Int)] = [(0, 0), (1, 1)]
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] = [1, 2, 3, 4]
let doubleNums: [Double] = [1.0, 2.0, 3.0, 4.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에 대해 공부 했는데,
아마 읽으신 분이 있다면 생성자 부분에서 막히신 분이 많을 듯..ㅠ.ㅠ
최대한 빨리 생성자 부분 포스팅해서 링크 걸어 놓겟습다~~~~~~
피드백 & 궁금증 댓글 환영 🌝
'iOS > Swift' 카테고리의 다른 글
Swift) 상속(Inheritance) 정복하기 (2/2) - 오버라이딩(Overriding) (4) | 2021.03.16 |
---|---|
Swift) 상속(Inheritance) 정복하기 (1/2) - 상속이란? (8) | 2021.03.15 |
Swift) 서브스크립트(Subscript) 정복하기 (6) | 2021.03.08 |
Swift) static? class? 메서드 완벽 정복하기 (4) | 2021.03.05 |
Swift) 프로퍼티 정복하기 (4/4) - 프로퍼티 옵저버(Property Observer) (2) | 2021.03.03 |