2014년 6월 15일 일요일

Swift - 프로토콜(Protocols)

스위프트의 프로토콜 기능은 개발자에게 ‘이 메소드나 프로퍼티 등의 요구사항(스펙)을 구현해라’ 라고 명령하는 것과 비슷한 용도로 쓰인다. 즉 특정 클래스를 만들 때 프로토콜을 명시하면 해당 프로토콜의 요구사항대로 메소드나 프로퍼티를 직접 구현해야 한다.

기존 Objective-C로 Cocoa나 Cocoa Touch용 앱을 만들어 봤다면 매우 익숙한 키워드일 것이다. 특히 delegate 를 사용할 때는 꼭 프로토콜도 따라다니는 패턴이었다. delegate의 스펙이 프로토콜(@protocol)에 정의되어 있기 때문이다.

스위프트에서 프로토콜은 protocol 이라는 키워드를 통해 정의할 수 있다.
protocol SomeProtocol {
    func someFunction() -> Int
    var someProperty: String { get set }
    optional func anotherFunction()
}
설명을 위한 코드이다 보니 딱히 실감은 안나겠지만 위와 긑은 방식으로 프로토콜을 정의하게 된다. 프로토콜 내부에는 메소드나 프로퍼티 등을 구현 없이 이름만 정의되어 있다는 것을 알 수 있다. 위 예에서는 someFunction() 이라는 메소드와 someProperty의 getter와 setter가 필요하다라는 의미로 이해하자.

3번째 anotherFunction 이라는 녀석은 optional 이라는게 앞에 붙어있다. 마치 스위프트 자체의 옵셔널과 비슷한데, 이 프로토콜을 구현할 때 anotherFunction()은 꼭 구현할 필요가 없다는 의미이다.

프로토콜 내부에는 메소드나프로퍼티의 Getter와 Setter 뿐만 아니라 static func나 class func 같은 정적 멤버 구현요구도 정의 할 수 있다. 참고하자.

프로토콜의 사용

구조체(struct)나 클래스(class)는 사용할 프로토콜을 이름 오른쪽에 콜론(:)을 붙인 이후 명시할 수 있다. 마치 클래스와 상속 시 부모클래스(Super Class)를 명시하는 것과 비슷하다.
struct SomeStruct: SomeProtocol {
    func someFunction() -> Int  {
        return 0;
    }
    
    var someProperty: String {
        get {
            return "from someProperty"
        }
        set {
            self.someProperty = newValue + "!"
        }
    }
}
이 예제는 앞서 정의한 SomeProtocol 프로토콜을 특정 구조체(struct)에서 구현한 것이다. 프로토콜을 쓴다고 해 놓고 구현하지 않으면 구현하지 않았다는 에러(not comfort to protocol)를 볼 수 있다.

구조체(struct)에서는 마치 클래스에서 상속을 하는 것 처럼(물론 상속은 불가능하지만) 특정 프로토콜을 사용한다고 명시한다.

클래스의 경우도 상속과 동일한 방식으로 프로토콜을 사용한다고 정의한다.
class SomeClass: SomeProtocol {
    func someFunction() -> Int  {
        return 10;
    }
    
    var someProperty: String {
        get {
            return "from someProperty"
        }
        set {
            self.someProperty = newValue + "!!!!!"
        }
    }
}
그런데 뭔가 이상함이 느껴질 것이다. SomeProtocol이 명시된 자리는 상속받을 부모클래스의 이름이 와야 된다. 하지만 위의 코드는 문제없이 컴파일이 된다.

스위프트의 class는 상속받을 이름에 프로토콜이 오면 자동으로 상속이 아닌 프로토콜로 인식하고 처리한다. 따라서 위의 코드도 문제가 없다. 만약 상속을 받아야 하는 상황이라면 아래와 같은 식으로 사용이 가능하다.
class AnotherSomeClass: SomeClass, SomeProtocol {
     ...   
}
제일 처음에 오는 것이 상속받을 클래스, 그 다음에는 프로토콜이다.

프로토콜은 다중으로 정의하는게 가능하다. 한 클래스가 여러 프로토콜을 요구받게 만들 수 있다. 콤마를 이용해 구분하면 된다.
class AnotherSomeClass: SomeClass, SomeProtocol, AnotherProtocol {
    ...
}

Objective-C로 Cocoa/Cocoa Touch 개발에 익숙하다면 프로토콜 개념을 이해하는데 무리는 없을 것이다.

마지막으로 optional 에 대해 알아보자. 앞서 이야기 했다싶이 Swift의 옵셔널 개념과 비슷해서 비슷한 문법으로 활용이 가능하다.
let anotherInstance = AnotherSomeClass()
...
anotherInstance.anotherFunction?()
물음표(?)가 찍혀있는게 보일 것이다. 옵셔널 체인과 비슷한 형식으로 동작된다. Objective-C에서 프로토콜 딜리게이션 패턴을 써 봤다면 responseToSelector 를 이용해 구현여부를 체크하는 것과 비슷한 역활을 한다고 볼 수 있다.

위의 AnotherSomeClass 클래스에는 anotherFunction()이 구현되어 있지 않으니 위 코드는 아무 일도 하지 않는다. 만약에 구현했다면? 당연히 해당 메소드가 실행될 것이다.

Delegation

Cocoa나 Cocoa Touch에서 프로토콜이 가장 빈번하게 사용되는 예는 아무래도 Delegation(위임)인 것 같다. 서로 다른 클래스 간의 이벤트를 알려주기에 효과적인 패턴이기 때문이다. 특히 GUI를 개발한다면 이 딜리게이션은 꼭 필요한 개념이다.

아래 예제는 PlusOneProtocol 이라는 프로토콜을 딜리게이션용으로 디자인했다. 그리고 PlusOneClass는 특정 상황에 호출할 PlusOneProtocol 타입의 딜리게이트를 받는 클래스, MyClass는 딜리게이트 용도(즉 위임받는) 클래스다.
protocol PlusOneProtocol {
    func resultOfPlusOne(value: Int)
}

class PlusOneClass {
    var delegate: PlusOneProtocol?
    
    var value: Int {
        set {
            // 프로토콜에 명시된 메소드를 호출한다. 
            self.delegate?.resultOfPlusOne(newValue + 1)
        }
        get {
            return 0
        }
    }
}

class MyClass: PlusOneProtocol {
    // 프로토콜 구현
    func resultOfPlusOne(value: Int) {
        println("MyClass: value = \(value)")
    }
}

var myObject = MyClass()

var plusOne = PlusOneClass()
plusOne.delegate = myObject
plusOne.value = 5
// 콘솔에 MyClass: value = 6 이라고 찍힌다.
PlusOneClass의 인스턴스인 plusOne의 delegate 프로퍼티에 MyClass의 인스턴스(myObject)를 지정한다. 이후 PlusOneClass의 value의 setter가 작동하면 delegate를 통해 프로토콜에 명시된  메소드를 호출한다. 딜리게이트로 지정되는 MyClass 함수는 프로토콜의 요구사항대로 resultOfPlusOne() 이라는 함수를 구현해 뒀고 따라서 PlusOneClass의 value setter가 호출되면 MyClass의 resultOfPlusOne() 메소드가 호출된다.

여기서 MyClass는 PlusOneProtocol을 규약을 이용해 특정 기능을 위임(delegate)받도록 설계되었다고 표현한다.

자연스럽게 프로토콜을 통해 별도의 두 클래스가 통신하게 되는 과정을 구현하게 되었다. 프로토콜이라는 의미(규약)에 맞게 여로간에 약속을 만들어 두는데 프로토콜이 유용하게 사용된다.

단순한 예제라서 이해하기 어려울지도 모르겠다. 다만 딜리게이션 패턴은 앞서 여러번 언급했지만 Cocoa 혹은 Cocoa Touch용 앱을 만들다 보면 빈번하게 이용하게되니 자동으로 체득될 것 같다.

[관련글] Swift의 기본 프로토콜 세 가지: Equatable, Comparable, Printable
[관련글] Swift - 순차적 타입(Sequence Type) 만들기
[관련글] Swift - 구조체(Structure) 훑어보기
[관련글] Swift - 클래스(Class) 훑어보기
[관련글] Swift - 프로퍼티(Properties)
[관련글] Swift - 메소드(Method)
[관련글] [Swift] 딜리게이션 패턴(Delegation Pattern)

[돌아가기] 스위프트(Swift) 가이드

댓글 없음 :