티스토리 뷰

Swift 공식 가이드/Swift 2

Protocols (3)

찜토끼 2016. 6. 19. 10:15

Swift 3.0.1 가이드에 대응하는 정리글을 작성하였습니다!!!

Protocols 정리 최신버전 > http://wlaxhrl.tistory.com/59





Apple 제공 Swift 프로그래밍 가이드(2.2)의 Protocols 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.



프로토콜 3편입니다. 이번 시간에는 프로토콜 합성부터 시작합니다. 1,2편을 아직 안 읽으셨다면 먼저 읽어보세요.


Protocols 1편 링크 > http://wlaxhrl.tistory.com/28

Protocols 2편 링크 > http://wlaxhrl.tistory.com/29



프로토콜 합성Protocol Composition

프로토콜 합성은 한번에 여러 프로토콜을 따르는 타입이 필요할 때 유용하게 활용할 수 있다. protocol<SomeProtocol, AnotherProtocol> 형태로 만들 수 있고, 필요한 만큼 쉼표로 구분하여 꺽쇠(<>) 안에 필요한 프로토콜을 추가할 수 있다.

일단 예제를 보자.

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

// 두 프로토콜을 따르는 구조체
struct Person: Named, Aged {
    var name: String
    var age: Int
}

// 꼭 두 프로토콜을 모두 따르는 타입을 파라미터로 받겠다
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
    print("생일축하해 \(celebrator.name) 너는 이제 \(celebrator.age)살이 되었구나")
}

let birthdayPerson = Person(name: "예림", age: 23)
wishHappyBirthday(birthdayPerson)
// "생일축하해 예림 너는 이제 23살이 되었구나"

<note> 프로토콜 합성은 새로운 영구적인 프로토콜 타입을 만드는 것이 아니라, 프로토콜들의 요구사항을 합쳐놓은 임시 로컬 프로토콜temporary local protocol을 정의하는 것이다.



프로토콜을 따르는지 체크하기(= 프로토콜 일치 확인 = Checking for Protocol Conformance)

어떤 타입이 특정 프로토콜을 따르고 있는지 체크해보기 위해 isas 연산자를 사용할 수 있다. 타입 캐스팅 단원에서 다룬 타입 체크와 유사한 방식이다.

  • is : 인스턴스가 특정 프로토콜을 따르면 true, 아니면 false 리턴

  • as? : 인스턴스가 특정 프로토콜을 따르면 프로토콜 타입의 옵셔널 값을 리턴, 아니면 nil 리턴

  • as! : 강제로 프로토콜 타입으로 다운캐스팅하고, 실패 시에는 런타임 에러


예제를 보자.

protocol HasArea {
    var area: Double { get }
}

// 프로토콜 O
class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}

// 프로토콜 O
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

// 프로토콜 X
class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

for object in objects {
    if let objectWithArea = object as? HasArea {
        // 실제로 object는 Circle, Country 타입이지만
        // objectWithArea는 HasArea 타입으로 저장되었기 때문에
        // objectWithArea를 통해서는 area 프로퍼티에만 접근 가능하다
        // (Circle, Country 자체의 프로퍼티에 접근불가)
        print("Area is \(objectWithArea.area)")
    } else {
        print("Something that doesn't have an area")
    }
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area



프로토콜의 선택적 요구사항Optional Protocol Requirements

프로토콜에 선택적 요구사항optional requirements을 정의할 수 있다. 선택적 요구사항은 프로토콜을 따르는 타입이 반드시 구현하지 않아도 되는 요구사항이며 optional 수식어로 표기한다. 옵셔널 요구사항이 되면 그 파라미터/메서드의 타입 자체가 자동으로 옵셔널이 된다. 예를 들어 (Int) -> String((Int) -> String)?이 된다.

프로토콜을 따르는 타입이 선택적 요구사항을 구현하지 않았을 가능성이 있기 때문에, 선택적 요구사항은 옵셔널 체이닝을 이용해 호출하도록 한다. 예를 들어 someOptionalMethod?(someArgument).

<note> 프로토콜이 @objc 속성일 때만 선택적 요구사항을 정의할 수 있다. 해당 프로토콜이 Objective-C와 상호운용될 일이 없더라도 @objc 속성으로 만들어야 한다. 또한 그렇게 정의된 프로토콜은 Objective-C 클래스를 상속받은 클래스, @objc 클래스만이 따를 수 있다. (구조체, ENUM은 불가)

// 수를 더하는 방식을 위한 프로토콜
@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int // ((Int)->(Int))? 타입
    optional var fixedIncrement: Int { get } // Int? 타입
}
//  요구사항이 모두 optional이기 때문에 구현을 하나도 하지 않아도 이 프로토콜을 따를 수 있다


// 수를 더하는 데 쓰일 클래스
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    
    func increment() {
        if let amount = dataSource?.incrementForCount?(count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}


이제 CounterDataSource를 따르는 클래스들의 예제를 보자.

// CounterDataSource 프로토콜을 간단하게 구현한 클래스
class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

var counter = Counter()
counter.dataSource = ThreeSource()

for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12


// CounterDataSource 프로토콜을 좀 더 복잡하게 구현한 클래스
@objc class TowardZeroSource: NSObject, CounterDataSource {
    func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

counter.count = -4 // 0으로 초기화
counter.dataSource = TowardZeroSource()

for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0



프로토콜 확장

프로토콜은 자신을 따르는 타입에게 메서드와 프로퍼티를 제공하기 위해 확장될 수 있다.

간단한 예제를 보자.

protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.37464991998171"
print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"



<기본 구현 제공하기>

프로토콜 확장을 통해 프로토콜의 요구사항(메서드, 프로퍼티)에 대한 기본 구현을 제공할 수 있다. 물론 프로토콜을 따르는 타입이 알아서 요구사항을 구현했다면 확장을 통한 기본 구현 대신 자신이 구현한 코드가 동작한다.

<note> 프로토콜 확장을 통한 프로토콜 요구사항의 기본 구현과, 프로토콜 선택적 요구사항은 구분된다. 프로토콜을 따르는 타입이 요구사항을 구현하지 않아도 된다는 것은 두 경우 모두 해당하지만, 전자의 경우에는 옵셔널 체이닝 없이도 호출될 수 있다. (후자는 호출할 때 옵셔널 체이닝이 꼭 필요)

protocol TextRepresentable {
    var textualDescription: String { get }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription
    }
}
// 이제 PrettyTextRepresentable를 따르는 타입은
// prettyTextualDescription 프로퍼티를 스스로 구현하지 않아도
// 기본 구현이 자동으로 사용된다


class MyTextRepresentable: PrettyTextRepresentable {
    var textualDescription: String = "My Text Representable";
}

let myTextRepresentable = MyTextRepresentable()
print(myTextRepresentable.textualDescription)
print(myTextRepresentable.prettyTextualDescription)
// My Text Representable
// My Text Representable



<프로토콜 확장에 제약을 추가Adding Constraints to Protocol Extensions>

프로토콜 확장을 정의할 때, 확장이 제공하는 메서드/프로퍼티를 사용하기 위해, 그 프로토콜을 따르는 타입이 반드시 지켜야 하는 제약을 지정할 수 있다. 제약은 where 절을 사용하여 표기한다.

예제로 알아보자.

// Collection의 아이템이 TextRepresentable 프로토콜을 따르고 있어야만 허용
extension CollectionType where Generator.Element: TextRepresentable {
    var textualDescription: String {
        let itemAsText = self.map {
            $0.textualDescription
        }
        return "[" + itemAsText.joinWithSeparator(",") + "]"
    }
}

struct Hamster: TextRepresentable {
    var name: String
    var textualDescription: String {
        return "\(name)라는 이름의 햄스터"
    }
}

let hamster1 = Hamster(name: "햄토리")
let hamster2 = Hamster(name: "햄순이")
let hamster3 = Hamster(name: "햄돌이")

let hamsters = [hamster1, hamster2, hamster3]
print(hamsters.textualDescription)
// "[햄토리라는 이름의 햄스터, 햄순이라는 이름의 햄스터, 햄돌이라는 이름의 햄스터]"

let notHamster = "나는 그냥 스트링"
let testList:[Any] = [hamster1, hamster2, notHamster]
print(testList.textualDescription)
// 에러. Type 'Any' does not conform to protocol 'TextRepresentable'



<note> 만약 타입이 제한된 확장constrained extensions을 여러 개 중복으로 만족시키고, 그 확장들에서 제공하는 메서드/프로퍼티의 이름이 같을 경우에는 어떻게 될까? 가장 세분화된 제한이 되어있는 확장을 사용하게 된다. 이게 무슨 말인지 예제를 통해 알아보자.

extension CollectionType where Generator.Element: TextRepresentable {
    var textualDescription: String {
        let itemAsText = self.map {
            $0.textualDescription
        }
        return "[" + itemAsText.joinWithSeparator(",") + "]"
    }
}

extension CollectionType where Generator.Element: Any {
    var textualDescription: String {
        return "[ Any 타입의 아이템들 ]"
    }
}

let hamster1 = Hamster(name: "햄토리")
let hamster2 = Hamster(name: "햄순이")
let hamster3 = Hamster(name: "햄돌이")

// hamsters에 들어있는 아이템은 앞에서 정의한 두 개의 확장에 모두 들어맞는 케이스
let hamsters = [hamster1, hamster2, hamster3]
print(hamsters.textualDescription)
// "[햄토리라는 이름의 햄스터, 햄순이라는 이름의 햄스터, 햄돌이라는 이름의 햄스터]" 가 출력된다
// 첫 번째 확장을 주석처리하면?
// 그때는 "[ Any 타입의 아이템들 ]"가 출력된다


'Swift 공식 가이드 > Swift 2' 카테고리의 다른 글

Access Control  (0) 2016.07.17
Generics  (3) 2016.07.10
Protocols (2)  (0) 2016.06.19
Protocols (1)  (0) 2016.06.18
Extensions  (0) 2016.06.12
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   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
글 보관함