티스토리 뷰

swift/문법

23. Swift 문법 - Protocol 2

DevBee 2020. 10. 26. 15:02

6. Protocol Types

Protocol 은 First-class Citizen 으로 독립적인 하나의 형식입니다. 변수나 상수를 선언할 때 자료형으로 사용하거나 파라미터의 자료형으로 사용할 수 있습니다. 그리고 리턴형으로 사용하는 것도 가능합니다.

 

간단한 예제를 살펴보겠습니다.

protocol Resettable {
    func reset()
}

class Size: Resettable {
    var width = 0.0
    var height = 0.0
    
    func reset() {
        width = 0.0
        height = 0.0
    }
}

let s = Size()

// Protocol 형식으로 저장하면 Protocol 에 선언되어 있는 멤버만 사용할 수 있습니다.
let resettable: Resettable = Size()
// r.width - 에러
resettable.reset()

 

프로토콜 적합성(Protocol Conformance)은 특정 형식이 프로토콜을 채용하고 있는지를 나타내는 척도입니다. 프로토콜 적합성은 type casting 연산자를 통해 확인합니다.

 

프로토콜 적합성 판단 문법은 다음과 같습니다.

// 형식이 프로토콜을 채용하고 있는지 확인하는 문법
instance is ProtocolName

// 인스턴스를 프로토콜 형식으로 캐스팅하거나 프로토콜 형식에 저장된 인스턴스를 실제 형식으로 캐스팅할 때 사용하는 문법
instance as ProtocolName
instance as? ProtocolName
instance as! ProtocolName
s is Resettable               // true
s is ExpressibleByNilLiteral  // false

// 인스턴스를 프로토콜 형식으로 캐스팅할 때는 컴파일 타임 캐스트와 런타임 캐스트를 모두 사용할 수 있습니다.
// 하지만 원래 형식으로 캐스팅할 떄는 컴파일 타임 캐스트는 사용할 수 없습니다.
let rr = Size() as Resettable
rr as? Size

 

값 형식은 상속이 불가능하지만 Protocol 을 활용하면 상속과 유사한 패턴을 구현할 수 있습니다. 예제를 통해 알아보겠습니다.

protocol Figure {
    func draw()
}

struct Triangle: Figure {
    func draw() {
        print("draw triangle")
    }
}

class Rectangle: Figure {
    func draw() {
        print("draw rect")
    }
}

struct Circle: Figure {
    var radius = 0.0
    
    func draw() {
        print("draw circle")
    }
}

let t = Triangle()
let r = Rectangle()
let c = Circle()
// 인스턴스가 값인지 참조 형식인지 파악하지 않고 같은 프로토콜 형식의 배열로 저장할 수 있습니다.
let list: [Figure] = [t, r, c]

for item in list {
    item.draw()
    
    if let c = item as? Circle {
        c.radius
    }
}

 

위 예제에서 확인할 수 있듯이 클래스에서 구현했던 업 캐스팅과 다운 캐스팅을 상속이 지원되지 않는 값 형식에서도 유사한 패턴으로 구현할 수 있습니다. 심지어 값 형식과 참조 형식에 관계 없이 함께 처리하는 것도 가능합니다.

 

7. Protocol Composition

하나의 형식은 다수의 프로토콜을 채용할 수 있습니다. 그리고 프로토콜을 채용한 하나의 인스턴스는 프로토콜 형식으로 저장될 수 있습니다.

 

문법을 살펴보겠습니다.

// 프로토콜을 & 로 연결하면 병합된 임시 프로토콜로 처리됩니다.
// 여기에서 임시라고 표현한 것은 새로운 프로토콜 형식이 생성되는 것은 아니기 때문입니다.
Protocol & Protocol & ...

// Protocol Composition 에 클래스를 추가하면 모든 sub class 를 저장할 수 있게 됩니다.
Class & Protocol & ...

 

예제를 살펴보겠습니다.

protocol Resettable {
    func reset()
}

protocol Printable {
    func printValue()
}

class Size: Resettable, Printable {
    var width = 0.0
    var height = 0.0
    
    func reset() {
        width = 0.0
        height = 0.0
    }
    
    func printValue() {
        print(width, height)
    }
}

class Circle: Resettable {
    var radius = 0.0
    
    func reset() {
        radius = 0.0
    }
}

class Oval: Circle {
    
}

// Resettable 과 printable 프로토콜을 모두 충족하는 형식으로 선언됩니다.
var rp: Resettable & Printable = Size()
// rp = Circle() - 에러

var cr: Circle & Resettable = Circle()
cr = Oval()

 

8. Optional Requirements

프로토콜에 선언된 모든 멤버가 필수 요구사항이기 때문에 해당 프로토콜을 채용하는 형식의 경우 프로토콜의 모든 멤버를 구현하여야 합니다. 하지만 Optional Requirements 를 사용하면 선택적으로 해당 멤버를 구현할 수 있습니다.

 

문법을 살펴보면 다음과 같습니다.

@objc protocol ProtocolName {
    @objc optional requirements
}

// @objc : objc Attribute - swift 로 작성한 코드를 objective-c 코드에서 사용할 수 있도록 하는 attribute 입니다.
// optional : optional Modifier - 선택적 멤버로 선언할 때 사용합니다.

 

이렇게 선언된 Protocol 을 objc protocol 이라고 부르기도 합니다. 그리고 이런 Protocol 은 클래스 전용입니다. 구조체나 열거형에서 해당 프로토콜을 채용하는 것은 불가능합니다. @objc 를 사용하면 AnyObject 프로토콜이 자동으로 상속되기 때문에 클래스에서만 채용할 수 있습니다.

@objc protocol Drawable {
    @objc optional var strokeWidth: Double { get set }
    @objc optional var strokeColor: UIColor { get set }
    func draw()
    @objc optional func reset()
}

class Rectangle: Drawable {
    func draw() {
        print("draw rect")
    }
}

let r: Drawable = Rectangle()
r.draw()

// 선택적 멤버에 접근할 때는 Optional Chaining 이 필요합니다.
// 선택적 멤버로 선언했다면 Optional 형식이 되기 때문입니다.
r.strokeWidth
r.strokeColor
r.reset?()
// r.reset() - 에러

 

9. Protocol Extension

Extension 은 형식을 확장합니다. Protocol 역시 형식이기 때문에 Extension 으로 확장할 수 있습니다. Extension 으로 Protocol 을 확장하면 Protocol 을 채용한 모든 형식에 기본 구현을 제공할 수 있습니다.

protocol Figure {
    var name: String { get }
    func draw()
}

extension Figure {
    func draw() {
        print("draw figure")
    }
}

struct Rectangle: Figure {
    var name = "Rectangle"
}

let r = Rectangle()
r.draw()  // 출력: draw figure

 

Rectangle 구조체에서 draw() 메소드를 직접 구현할 수 있으며 이 경우 확장을 통해 구현된 메소드보다 우선 순위가 높기 때문에 직접 구현한 메소드가 실행됩니다.

protocol Figure {
    var name: String { get }
    func draw()
}

extension Figure {
    func draw() {
        print("draw figure")
    }
}

struct Rectangle: Figure {
    var name = "Rectangle"
    
    func draw() {
        print("draw rectangle")
    }
}

let r = Rectangle()
r.draw()  // 출력: draw rectangle

 

Protocol Extension 은 프로토콜을 채용한 모든 형식에 멤버를 추가합니다. 이번에는 멤버를 추가할 형식을 제한해 보겠습니다.

extension Figure where Self: Equatable {
    func draw() {
        print("draw figure")
    }
}

 

여기에서 Self 는 프로토콜을 채용한 형식을 의미합니다. 형식이 Equatable 프로토콜을 채용하고 있다면 where 절이 true 로 평가됩니다. 앞으로 프로토콜에서 구현한 멤버는 Figure 프로토콜을 채용하면서 동시에 Equatable 프로토콜을 채용하고 있는 형식에 제한적으로 추가됩니다.

 

위에서 선언한 Rectangle 구조체에서 draw 메소드를 삭제하면 에러가 발생합니다. Rectangle 구조체는 Equatable 프로토콜을 채용하고 있지 않기 때문에 Protocol Extension 에서 구현한 메소드는 추가되지 않습니다. Rectangle 구조체가 Equatable 프로토콜을 채용하도록 수정한다면 에러 없이 Protocol Extension 에서 구현한 메소드가 추가됩니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
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 31
글 보관함