프로토콜

Swift/기본문법 2017.07.17 11:00

프로토콜

프로토콜(Protocol)은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다. 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다. 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다(Conform)고 표현합니다. 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 합니다. 

즉, 프로토콜은 기능을 정의하고 제시 할 뿐이지 스스로 기능을 구현하지는 않습니다.


소스코드


정의 문법

protocol 키워드를 사용하여 정의합니다.

protocol 프로토콜 이름 {
    /* 정의부 */
}


프로토콜 구현

protocol Talkable {
    
    // 프로퍼티 요구
    // 프로퍼티 요구는 항상 var 키워드를 사용합니다
    // get은 읽기만 가능해도 상관 없다는 뜻이며
    // get과 set을 모두 명시하면 
    // 읽기 쓰기 모두 가능한 프로퍼티여야 합니다
    var topic: String { get set }
    var language: String { get }
    
    // 메서드 요구
    func talk()
    
    // 이니셜라이저 요구
    init(topic: String, language: String)
}


프로토콜 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택했습니다
struct Person: Talkable {
    // 프로퍼티 요구 준수
    var topic: String
    let language: String
    
    // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
//    var language: String { return "한국어" }
    
    // 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있습니다
//    var subject: String = ""
//    var topic: String {
//        set {
//            self.subject = newValue
//        }
//        get {
//            return self.subject
//        }
//    }
    
    // 메서드 요구 준수    
    func talk() {
        print("\(topic)에 대해 \(language)로 말합니다")
    }

    // 이니셜라이저 요구 준수    
    init(topic: String, language: String) {
        self.topic = topic
        self.language = language
    }
}


> 프로퍼티 요구는 다양한 방법으로 해석, 구현할 수 있습니다.

struct Person: Talkable {
    var subject: String = ""

    // 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
    var topic: String {
        set {
            self.subject = newValue
        }
        get {
            return self.subject
        }
    }
    
    var language: String { return "한국어" }
    
    func talk() {
        print("\(topic)에 대해 \(language)로 말합니다")
    }
    
    init(topic: String, language: String) {
        self.topic = topic
    }
}



프로토콜 상속

프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다. 프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 프로토콜은 클래스와 다르게 다중상속이 가능합니다.

protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
 /* 정의부 */
 }


protocol Readable {
    func read()
}
protocol Writeable {
    func write()
}
protocol ReadSpeakable: Readable {
    func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
    func speak()
}

struct SomeType: ReadWriteSpeakable {
    func read() {
        print("Read")
    }
    
    func write() {
        print("Write")
    }
    
    func speak() {
        print("Speak")
    }
}


클래스 상속과 프로토콜

클래스에서 상속과 프로토콜 채택을 동시에 하려면 상속받으려는 클래스를 먼저 명시하고 그 뒤에 채택할 프로토콜 목록을 작성합니다.

class SuperClass: Readable { func read() { } } class SubClass: SuperClass, Writeable, ReadSpeakable { func write() { } func speak() { } }


프로토콜 준수 확인

is, as 연산자를 사용해서 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있습니다.

let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()

var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false

someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true

someAny = sup

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read

if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
    someReadSpeakable.speak()
} // 동작하지 않음

someAny = sub

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read



관련문서

The Swift Programming Language - Protocols





by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/


p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓ 블로거에게 공감은 큰 힘이 됩니다 ↓↓↓ 

저작자 표시 비영리 변경 금지
신고

'Swift > 기본문법' 카테고리의 다른 글

오류처리  (0) 2017.07.24
익스텐션  (0) 2017.07.20
프로토콜  (0) 2017.07.17
assert와 guard  (0) 2017.07.13
타입캐스팅  (2) 2017.07.10
옵셔널 체이닝  (2) 2017.07.06
Posted by yagom


오늘의 주제

1. 프로토콜 지향 프로그래밍



안녕하세요, 야곰입니다. 


지난 포스팅에서는 스위프트의 프로토콜과 익스텐션에 대해 알아봤습니다.


2017/01/23 - [Swift] - Swift란 어떤 언어인가?

2017/01/25 - [Swift] - Swift 기초문법 - 변수, 상수, 기초 데이터 타입

2017/02/06 - [Swift] - Swift - 함수, 콜렉션 타입

2017/02/28 - [Swift] - Swift - 구조체 클래스

2017/03/07 - [Swift] - Swift - 프로토콜, 익스텐션

이번에는 스위프트와 함께 대두된 프로토콜 지향 프로그래밍 디자인 패턴에 대해 알아보겠습니다 :)



프로토콜 지향 프로그래밍

애플은 2015년 9월, WWDC에서 스위프트 버전 2.0을 발표하면서 스위프트는 프로토콜 지향 언어(Protocol-Oriented Language)라고 발표했습니다. 프로토콜 지향 언어는 도대체 무슨 뜻일까요? 스 위프트의 표준 라이브러리에서 타입과 관련된 것을 살펴보면 대부분이 구조체로 구현되어 있습니다. 객체지향 프로그래밍 패러다임에 기반을 둔 언어는 대부분 클래스의 상속을 사용해 타입에 공통된 기능을 구현합니다. 그런데 스위프트는 클래스로 구현된 타입은 별로없고, 대부분 구조체로 기본 타입이 구현되어 있습니다. 상속도 되지 않는 구조체로 어떻게 그렇게 다양한 공통 기능을 가질 수 있는 걸까요? 해답은 프로토콜과 익스텐션에 있습니다.

프로토콜 초기구현

지난 포스팅에서 프로토콜(Protocol)특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진이라고 말씀드렸습니다. 프로토콜을 채택(Adopted)한 타입은 프로토콜이 요구하는 기능을 구현하여 프로토콜을 준수해야(Conform)합니다. 
익스텐션은 기존 타입의 기능을 확장하며, 프로토콜은 프로토콜을 채택한 타입이 원하는 기능을 강제로 구현한다는 점을 우리는 알고 있습니다. 그런데 특정 프로토콜을 정의하고 여러 타입에서 이 프로토콜을 준수하게 만들어 타입마다 똑같은 메서드, 똑같은 프로퍼티, 똑같은 서브스크립트 등을 구현해야 한다면...? 얼마나 많은 코드를 중복 사용해야 하며 또 유지보수는 얼마나 힘들어질지 생각만 해도 머리가 아플 겁니다. 이때 필요한 게 바로 익스텐션과 프로토콜의 결합입니다. 
프로토콜을 채택한 타입의 정의부에 프로토콜의 요구사항을 구현하지 않더라도 프로토콜의 익스텐션에 미리 프로토콜의 요구사항을 구현해 둘 수 있습니다. 이를 프로토콜 초기구현이라고 합니다. 

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

struct Person: Talkable {
    var topic: String
    var name: String
    
    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
}

여기 지난 포스팅에서 예제로 보았던 Talkable 프로토콜이 있습니다. 이 프로토콜은 Person이라는 구조체 타입에만 채택이 되었으므로 여러 프로퍼티와 메서드를 구현하더라도 Person에만 구현하면 되므로 큰 문제가 없었습니다. 그런데, Talkable이라는 프로토콜을 Person 뿐만 아니라 다른 타입에서도 채택하고 싶다면? 아마도 그 타입에서도 Talkable 프로토콜이 요구하는 사항을 모두 구현해 주어야 할 것입니다.

protocol Talkable { var topic: String { get set } func talk(to: Self) } struct Person: Talkable { var topic: String var name: String func talk(to: Person) { print("\(topic)에 대해 \(to.name)에게 이야기합니다") } } struct Monkey: Talkable { var topic: String func talk(to: Monkey) { print("우끼끼 꺄꺄 \(topic)") } }


그런데 프로토콜이 요구하는 사항을 미리 모두 한꺼번에 구현해 둘 수 있다면 중복된 코드를 피할 수 있을 것입니다.

protocol Talkable { var topic: String { get set } func talk(to: Self) } // 익스텐션을 사용한 프로토콜 초기 구현 extension Talkable { func talk(to: Self) { print("\(to)! \(topic)") } } struct Person: Talkable { var topic: String var name: String } struct Monkey: Talkable { var topic: String } let yagom = Person(topic: "Swift", name: "yagom") let hana = Person(topic: "Internet", name: "hana") yagom.talk(to: hana) hana.talk(to: yagom)


위의 코드에서는 Person과 Monkey에 Talkable의 요구사항인 talk(to:) 메서드를 구현하지 않았음에도 전혀 오류가 발생하지 않습니다.
이렇게 하나의 프로토콜을 만들어두고, 초기 구현을 해둔다면 여러 타입에서 해당 기능을 사용하고 싶을 때 프로토콜을 채택하기만 하면 됩니다.
만약에 프로토콜 초기 구현과 다른 동작을 해야한다면, 그저 그 타입에 프로토콜의 요구사항을 재정의해주면 됩니다.

struct Monkey: Talkable { var topic: String func talk(to: Monkey) { print("\(to)! 우끼기기기끼기기") } } let sunny = Monkey(topic: "바나나") let jack = Monkey(topic: "나무") sunny.talk(to: jack)


프로토콜 초기 구현을 잘 해둔다면 여러 프로토콜을 그저 채택만 하기만하면 그 타입에 기능이 추가되는 것이죠.

protocol Flyable { func fly() } extension Flyable { func fly() { print("푸드득 푸드득") } } protocol Runable { func run() } extension Runable { func run() { print("후다닥 후다닥") } } protocol Swimable { func swim() } extension Swimable { func swim() { print("어푸 어푸") } } protocol Talkable { func talk() } extension Talkable { func talk() { print("재잘재잘 쪼잘쪼잘") } } struct Bird: Flyable, Talkable { } let bird = Bird() bird.fly() bird.talk() struct Person: Runable, Swimable, Talkable { } let person = Person() person.run() person.talk() person.swim()


위 코드처럼 프로토콜 초기 구현을 잘 해두면 이렇게 프로토콜 채택만으로도 그 기능을 사용할 수 있게 됩니다. 프로토콜 초기 구현이 프로토콜 지향 프로그래밍의 핵심이라고 볼 수 있습니다.

프로토콜 지향 프로그래밍을 추구하는 이유

그렇다면 프로토콜 지향 프로그래밍을 하는 이유는 무엇일까요?

  • 구조체, 클래스, 열거형 등 구조화된 타입 중에 상속은 클래스 타입에서만 가능합니다.
    • 클래스는 참조 타입이므로 참조 추적에 비용이 많이 발생합니다. 비교적 비용이 적은 값 타입을 활용하고 싶어도, 상속을 할 수 없으므로 때마다 기능을 다시 구현해 주어야 했지만, 프로토콜 지향 프로그래밍은 그 한계를 없앴습니다.
  • 기능의 모듈화가 더욱 명확해 집니다.
    • 클래스가 상속을 할 수 있도록 설계되어 있다고 하더라도 다중상속을 지원하는 언어는 많지 않습니다. 다중상속을 지원하지 않는다는 뜻은 하나의 상속체계에서 다른 상속체계에 속해있는 기능을 끌어다 쓸 수 없다는 뜻입니다. 그런데 프로토콜 지향 프로그래밍은 기능을 프로토콜이라는 단위로 묶어 표현하고 초기 구현을 해 둘 수 있으니 상속이라는 한계점을 탈피할 수 있습니다.

어떠신가요? 프로토콜 지향 프로그래밍! 정말 매력적이지 않나요?

익스텐션을 통한 각 프로토콜의 초기구현은 구현코드를 볼 수 없기 때문에 어떻게 구현되었는지 는 확실히 볼 수 없지만 Array의 정의만 보더라도 제네릭, 프로토콜을 다양하게 사용한 것을 볼 수 있습니다. 아마도 각 타입별로 공유하는 초기구현은 익스텐션으로 구현되어 있을 것입니다. 

스위프트의 주요 기능을 하나하나 알아갈수록 스위프트 표준 라이브러리의 코드가 눈에 잘 들어오실거예요. 하나의 기능을 알아갈 때마다 스위프트 표준 라이브러리를 살펴보면서 어떤 기능을 통해 구현 되었는지, 어떻게 연관이 되는지 읽어보고, 해석해보고, 상상해보는 것도 언어를 이해하는 데 도움이 됩니다. 

이번 포스팅에서는 프로토콜 지향 프로그래밍(Protocol Oriented Programming, POP) 대해 알아보았습니다. 
앞으로 또 무슨 주제를 써 나갈까요? 
그 동안 많은 도움이 되었길 빕니다!

고맙습니다 :D




by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/

twitter : http://www.twitter.com/yagomSoft ( @yagomsoft )

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   

저작자 표시 비영리 변경 금지
신고
Posted by yagom


오늘의 주제

1. 프로토콜
2. 익스텐션




안녕하세요, 야곰입니다. 


지난 포스팅에서는 스위프트의 구조체와 클래스에 대해 알아봤습니다.


2017/01/23 - [Swift] - Swift란 어떤 언어인가?

2017/01/25 - [Swift] - Swift 기초문법 - 변수, 상수, 기초 데이터 타입

2017/02/06 - [Swift] - Swift - 함수, 콜렉션 타입

2017/02/28 - [Swift] - Swift - 구조체 클래스



이번에는 프로토콜과 익스텐션에 대해 알아보겠습니다 :)



프로토콜


프로토콜(Protocol)특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다. 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다. 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 ‘프로토콜을 준수한다(Conform)’고 표현합니다. 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 합니다. 즉, 프로토콜은 기능을 정의하고 제시 할 뿐이지 스스로 기능을 구현하지는 않습니다. 


프로토콜 정의

프로토콜은 구조체, 클래스, 열거형의 모양과 비슷하게 정의할 수 있으며 protocol 키워드를 사용합니다. 


protocol 프로토콜 이름 {
    프로토콜 정의
}
구조체, 클래스, 열거형 등에서 프로토콜을 채택하려면 타입 이름 뒤에 콜론(:)을 붙여준 후 채택할 프로토콜 이름을 쉼표(,)로 구분하여 명시해줍니다. 

struct SomeStruct: AProtocol, AnotherProtocol {
    // 구조체 정의
}

class SomeClass: AProtocol, AnotherProtocol {
    // 클래스 정의
}

enum SomeEnum: AProtocol, AnotherProtocol {
    // 열거형 정의
}

위 코드의 각 타입은 AProtocol과 AnotherProtocol을 채택한 것입니다. 만약, 클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 다음에 채택할 프로토콜을 나열해줍니다. 
class SomeClass: SuperClass, AProtocol, AnotherProtocol {
    // 클래스 정의
}

위의 SomeClass는 SuperClass를 상속받았으며 동시에 AProtocol과 AnotherProtocol 프로토콜을 채택한 클래스입니다. 

프로토콜 요구사항

프로토콜은 타입이 특정 기능을 수행하기 위해 필요한 기능을 요구합니다. 프로토콜이 자신을 채택한 타입에 요구하는 사항은 프로퍼티나 메서드와 같은 기능들입니다. 프로토콜은 프로퍼티, 메서드, 서브스크립트, 이니셜라이저 등의 기능을 요구할 수 있습니다.

프로퍼티 요구


프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있습니다. 그렇지만 프로토콜은 그 프로퍼티의 종류(연산 프로퍼티인지, 저장 프로퍼티인지 등)는 따로 신경쓰지 않습니다. 프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현해주면 됩니다. 다만, 프로퍼티를 읽기 전용으로 할지 혹은 읽고 쓰기가 모두 가능하게 할지는 프로토콜이 정해야 합니다. 

프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의됩니다. 읽기와 쓰기가 모두 가능한 프로퍼티는 프로퍼티의 정의 뒤에 {get set}이라고 명시하며, 읽기 전용 프로퍼티는 프로퍼티의 정의 뒤에 {get}이라고 명시해줍니다. 

protocol SomeProtocol { var settableProperty: String { get set } var notNeedToBeSettableProperty: String { get } } protocol AnotherProtocol { static var someTypeProperty: Int { get set } static var anotherTypeProperty: Int { get } }


위 코드의 SomeProtocol에 정의된 settableProperty는 읽기와 쓰기 모두를 요구했고, notNeedToBeSettableProperty는 읽기만 가능하다면 어떻게 구현되어도 상관없다는 요구사항입니다. 타입 프로퍼티를 요구하려면 static 키워드를 사용합니다. 클래스의 타입 프로퍼티에는 상속 가능한 타입 프로퍼티인 class 타입 프로퍼티와 상속 불가능한 static 타입 프로퍼티가 있습니다만 이 두 타입 프로퍼티를 따로 구분하지 않고 모두 static 키워드 를 사용하여 타입 프로퍼티를 요구하면 됩니다. AnotherProtocol에 정의된 someProperty 와 anotherProperty는 모두 타입 프로퍼티를 요구합니다.

protocol Talkable {
    var topic: String { get set }
}

struct Person: Talkable {
    var topic: String
}

Talkable 프로토콜은 어떤 주제에 대해 말할 수 있게 하기 위한 프로퍼티인 topic를 요구합니다. 그래서 Talkable 프로토콜을 채택하여 준수하는 Person 클래스는 topic 프로퍼티를 가져야합니다.


메서드 요구


프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수도 있습니다. 프로토콜이 요구할 메서드는 프로토콜 정의에서 작성합니다. 다만, 메서드의 실제 구현부인 중괄호({}) 부분은 제외하고 메서드의 이름, 매개변수, 반환 타입 등만 작성합니다. 프로토콜의 메서드 요구에서는 매개변수 기본값을 지정할 수 없습니다. 타입 메서드를 요구할 때는 타입 프로퍼티 요구와 마찬가지로 앞에 static 키워드를 명시합니다. static 키워드를 사용 하여 요구한 타입 메서드를 클래스에서 실제 구현할 때에는 static 키워드나 class 키워드 어느 쪽을 사용해도 무방합니다.

protocol Talkable {
    var topic: String { get set }
    func talk(to: Person)
}

struct Person: Talkable {
    var topic: String
    var name: String
    
    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
}

이니셜라이저 요구


프로토콜은 프로퍼티, 메서드 등과 마찬가지로 특정한 이니셜라이저를 요구할 수도 있습니다. 프로토콜에서 이니셜라이저를 요구하려면 메서드 요구와 마찬가지로 이니셜라이저를 정의하지만 구현은 하지 않습니다. 즉, 이니셜라이저의 매개변수를 지정하기만 할 뿐, 중괄호를 포함한 이니셜라이저 구현은 하지 않습니다. 


protocol Talkable {
    var topic: String { get set }
    func talk(to: Person)
    init(name: String, topic: String)
}

struct Person: Talkable {
    var topic: String
    var name: String
    
    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
    
    init(name: String, topic: String) {
        self.name = name
        self.topic = topic
    }
}

let yagom: Person = Person(name: "야곰", topic: "스위프트")
let hana: Person = Person(name: "하나", topic: "코딩")

yagom.talk(to: hana)
// 스위프트에 대해 하나에게 이야기합니다

프로토콜의 상속


프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다. 프로토콜 상속 문법은 클래스의 상속 문법과 유사합니다. 

protocol Readable {
    func read()
}
protocol Writeable {
    func write()
}
protocol ReadSpeakable: Readable {
    func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
    func speak()
}

class SomeClass: ReadWriteSpeakable {
    func read() {
        print("Read")
    }
    
    func write() {
        print("Write")
    }
    
    func speak() {
        print("Speak")
    }
}
위 코드의 ReadSpeakable 프로토콜은 Readable 프로토콜을 상속받았고 ReadWriteSpeakable 프로토콜은 Readable과 Writeable 프로토콜을 상속받았습니다. 그래서 ReadWriteSpeakable 프로토콜을 채택한 SomeClass는 세 프로토콜이 요구하는 read(), write(), speak() 메서드를 모두 구현해야 합니다. 


익스텐션

익스텐션(Extension)은 스위프트의 강력한 기능 중 하나입니다. 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능입니다. 기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있습니다.

스위프트의 익스텐션이 타입에 추가할 수 있는 기능은 다음과 같습니다. 

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티 
  • 타입 메서드 / 인스턴스 메서드
  • 이니셜라이저
  • 서브스크립트 
  • 중첩 타입
  • 특정 프로토콜을 준수할 수 있도록 기능 추가 
익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없습니다. 클래스 의 상속과 익스텐션을 비교해보겠습니다. 이 둘은 비슷해보이지만 실제 성격은 많이 다릅니다. 

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

 

상속 

익스텐션 

확장 

수직 확장 

수평 확장 

사용 

클래스 타입에만 사용 

클래스, 구조체, 프로토콜, 제네릭 등 모든 타입 

 재정의

재정의 가능 

재정의 불가 


익스텐션을 사용하는 대신 원래 타입을 정의한 소스에 기능을 추가하는 방법도 있겠지만, 외부 라이브러리나 프레임워크를 가져다 썼다면 원본 소스를 수정하지 못합니다. 이처럼 외부에서 가져온 타입에 내가 원하는 기능을 추가하고자 할 때 익스텐션을 사용합니다. 따로 상속을 받지 않아도 되며, 구조체와 열거형에도 기능을 추가할 수 있으므로 익스텐션은 매우 편리한 기능입니다. 

익스텐션은 모든 타입에 적용할 수 있습니다. 모든 타입이라 함은 구조체, 열거형, 클래스, 프로토콜, 제네릭 타입 등을 뜻합니다. 즉, 익스텐션을 통해 모든 타입에 연산 프로퍼티, 메서드, 이니셜라이저, 서브스크립트, 중첩 데이터 타입 등을 추가할 수 있습니다.

더불어 익스텐션은 프로토콜과 함께 사용하면 굉장히 강력한 기능을 선사합니다. 그 내용은 다음 포스팅인 프로토콜 지향 프로그래밍에서 조금 더 자세히 다루겠습니다. 


익스텐션 문법


익스텐션은 extension이라는 키워드를 사용하여 선언합니다. 
extension 확장할 타입 이름 {
    // 타입에 추가될 새로운 기능 구현
}
익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다. 이런 경우에는 클래스나 구조체에서 사용하던 것과 똑같은 방법으로 프로토콜 이름을 나열해줍니다. 
extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3 {
    // 프로토콜 요구사항 구현
}

스위프트 라이브러리를 살펴보면 실제로 익스텐션이 굉장히 많이 사용되고 있음을 알 수 있습니다. Double 타입에는 수많은 프로퍼티와 메서드, 이니셜라이저가 정의되어 있으며 수많은 프로토콜을 채택하고 있을 것이라고 예상되지만, 실제로 Double 타입의 정의를 살펴보면 그 모든것이 다 정의되어 있지는 않습니다. 

그러면 Double 타입이 채택하고 준수해야 하는 수많은 프로토콜은 어디로 갔을까요? 어디에서 채택하고 어디에서 준수하도록 정의되어 있을까요? 당연히 답은 익스텐션입니다. 이처럼 스위프트 표준 라이브러리 타입의 기능은 대부분 익스텐션으로 구현되어 있습니다. Double 외에도 다른 타입들의 정의와 익스텐션을 찾아보면 더 많은 예를 보실 수 있습니다. 꼭 한 번 찾아보세요! 

익스텐션으로 확장할 수 있는 항목 

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

연산 프로퍼티 추가


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

위 코드의 익스텐션은 Int 타입에 두 개의 연산 프로퍼티를 추가한 것입니다. Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool 타입으로 알려주는 연산 프로퍼티입니다. 익스텐션으로 Int 타입에 추가해준 연산 프로퍼티는 Int 타입의 어떤 인스턴스에도 사용이 가능합니다. 위의 코드처럼 인스턴스 연산 프로퍼티를 추가할 수도 있으며, static 키워드를 사용하여 타입 연산 프로퍼티도 추가할 수 있습니다. 


메서드 추가


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

extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}
print(3.multiply(by: 2))  // 6
print(4.multiply(by: 5))  // 20

var number: Int = 3
print(number.multiply(by: 2))   // 6
print(number.multiply(by: 3))   // 9
위 코드의 익스텐션을 통해 Int 타입에 인스턴스 메서드인 multiply(by:) 메서드를 추가했습니다. 여러 기능을 여러 익스텐션 블록으로 나눠서 구현해도 전혀 문제가 없습니다. 관련된 기능별로 하나의 익스텐션 블록에 묶어주는 것도 좋습니다. 


이니셜라이저 추가


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

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

extension String { subscript(appendValue: String) -> String { return self + appendValue } subscript(repeatCount: UInt) -> String { var str: String = "" for _ in 0..<repeatCount { str += self } return str } } print("abc"["def"]) // "abcdef" print("abc"[3]) // "abcabcabc"


이번엔 스위프트의 프로토콜과 익스텐션 대해 알아봤습니다. 다음 포스팅에서는 프로토콜 지향 프로그래밍(Protocol Oriented Programming, POP) 대해 알아보겠습니다. 

다음 번에 또 뵈어요~ 고맙습니다 :D




by yagom

facebook : http://www.facebook.com/yagomSoft

facebook group : https://www.facebook.com/groups/yagom/

twitter : http://www.twitter.com/yagomSoft ( @yagomsoft )

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


저작자 표시 비영리 변경 금지
신고
Posted by yagom

오늘의 주제

1. Protocol
2. Delegate 만들어 보기



서른 세 번째 시간입니다.^^

지난 번에는 유저의 응답을 받아 볼 수 있는 피커뷰에 대해 알아보았습니다~
이번에는 프로토콜이라는 것에 대하여 알아보도록 할게요~
잘만 사용하면 좋은 녀석입니다 ㅎ

스따뚜~~~~~~~~~~~~





# Protocol이란?

네트워크에서 프로토콜이라 함은 일종의 통신규약을 말하는데요, Objective-C에서는 약간 다른 의미로 사용됩니다.
음... 뭐 굳이 말하자면 클래스간의 통신 규약이라고도 말할 수 있겠네요 ㅎ
프로토콜은 일정한 메소드를 통하여 클래스 간에 통신을 할 수 있는 통로를 제공합니다.
어떠한 객체에서 다른 객체로 데이터나 메세지를 전달하고 싶을 때 사용할 수 있습니다.

말로는 잘 느낌이 안오시죠?ㅎ
그런데 이렇게 말씀드리면 느낌이 조금 오시려나요?ㅎ

이 프로토콜이란 친구를 사용하여 코코아 터치에 많이 사용되고 있는 위임패턴(Delegation Pattern)을 구현할 수 있습니다.

그럼 기본적으로 클래스에서 제공되는 델리게이트 메소드 말고 우리가 직접 델리게이트 프로토콜과 메소드를 만들어 보는 시간을 갖도록 하겠습니다.ㅎ

# Delegate 만들어보기

이번 실습에서는 ViewController의 한 라벨에다가 ViewControllerForWeb에서 우리가 불러왔던 웹페이지의 마지막 주소를 세팅하는 것을 해보겠습니다.

그러려면 일단 ViewController에 라벨을 하나 넣어줘야겠죠?ㅎ
라벨을 넣어주시고 아울렛을 연결해 줍니다.


그리고 이제는 프로토콜 생성을 위하여 ViewControllerForWeb.h로 옵니다.
거기에 프로토콜을 작성해 주고, 프로퍼티로 델리게이트를 만들어 줍니다.
@required와 @optional은
말 그대로 다른 객체와 프로토콜을 통해 연결했다면 꼭 구현해줘야 하는 메소드이다. 선택적으로 구현해 주어야 하는 메소드이다를 구분해 준 것입니다.
지난 번에 피커뷰에서 피커뷰 델리게이트 중에 필수적으로 구현이 되어야 하는 메소드가 있었지요? 그것을 생각하시면 편할거예요.


그리고 ViewControllerForWeb.m에서 헤더에서 만들어준 델리게이트를 synthesize로 받아줍니다.


그리고 우리가 마지막으로 ViewControllerForWeb을 닫을 때 프로토콜을 통하여 ViewController로 메소드를 호출합니다.


그리고 이제는 ViewController로 돌아와서 델리게이트로서의 역할을 하도록 세팅해 주고, 메소드 들을 선언해 줍니다.


그리고 구현파일 ViewController.m으로 돌아와서 실제적으로 메소드를 구현해 줍니다.
또, ViewControllerForWeb의 객체를 생성해 줬을 때, 자신을 델리게이트로 설정을 해 줍니다.



자, 이제 실행해 볼까요?ㅎ

이렇게 웹페이지를 띄웠다가~


닫고 이전 화면으로 돌아가면~ 맨 마지막에 요청하였던 페이지의 주소가 라벨에 세팅이 되어있음을 확인 할 수 있습니다 ㅎ


아까 구현해놓고 실행하지 않았던 빨간색으로 글자 바꾸는 메소드는 여러분께서 한 번 적절히 활용해서 꼭 실행해 보시기 바랍니다 ㅎ

정말 중요한 개념인데 금방 끝냈네요~

굉장히 중요한 내용 중에 하나이기 때문에 혹여나 이해가 안되시거나 부족하다 싶으면 꼭 말씀해 주세요.
보강설명 들어가겠습니다...ㅎ
사실 지금 좀 부족한 감이 있습니다만, 여러분의 피드백이 필요한 시점입니다 ㅎㅎ
피드백 없으면 이 내용은 포스팅이 또 안나오겠지요..ㅎ

by yagom

twitter : @yagomsoft

p.s 제 포스팅을 RSS 피드로 받아보실 수 있습니다.

RSS Feed 받기   


↓↓↓저 열심히 썼는데 추천 한방 꾹 눌러주고 가시는 건 어떨까요? 로그인이 필요 없습니다. ^~^ 고맙습니다~ ↓↓↓ 



저작자 표시 비영리 변경 금지
신고
Posted by yagom


티스토리 툴바