[Swift] 프로토콜 - 4 (Protocol)

프로토콜 타입 콜렉션

프로토콜 타입을 콜렉션 타입에 저장할 타입으로 사용이 가능하다

protocol Age {
    var age: Int { get }
}

class Human: Age {
    var age: Int
    init(age: Int) {
        self.age = age 
    }
}

class Animal: Age {
    var age: Int
    init(age: Int) {
        self.age = age 
    }
}

var arr: [Age] = [Human(age: 20), Animal(age: 10)]

for e in arr {
    print(e.age)
}

각 인스턴스의 실제 타입은 다르지만 모두 Age 프로토콜을 준수하기 때문에

Age의 요구사항인 age 프로퍼티를 사용할 수 있음

이렇게 사용할 경우에는 프로토콜에 정의되지 않은 실제 인스턴스의 고유 프로퍼티 및 메서드는 사용할 수 없다

프로토콜 상속

프로토콜은 하나 이상의 다른 프로토콜을 상속할 수 있다

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

상속되는 요구사항 외에 요구사항을 추가할 수 있다

protocol Introduce {
    var info: String { get }
}

protocol Commentator: Introduce {
    var comment: String { get }
}

이런 프로토콜을 채택하면, 상속 받는 프로토콜의 요구사항도 모두 구현해야 한다

extension Int: Commentator {
    var info: String { "\(self)" }
    var comment: String { "my number is \(info)" }
}

print(8.comment)
// my number is 8

클래스 전용 프로토콜

프로토콜을 정의할 때 AnyObject를 상속 받으면 클래스 타입만 해당 프로토콜을 채택할 수 있도록 제한할 수 있다

protocol ClassProtocol: AnyObject {}
class SomeClass: ClassProtocol {}
// Non-class type 'SomeStruct' cannot conform to class protocol 'ClassProtocol'
struct SomeStruct: ClassProtocol {}

프로토콜 구성

여러 프로토콜을 단일 요구사항으로 결합할 수 있다

프로토콜 구성은 프로토콜들을 결합하여 하나의 임시 로컬 프로토컬을 정의한 것 처럼 동작한다

새로운 프로토콜 타입을 정의하는 것이 아니다

프로토콜 구성은 앰퍼샌드(&)를 사용하여 필요한 만큼 프로토콜을 결합할 수 있다

결합한 프로토콜의 요구사항만 사용이 가능하다

protocol Name {
    var name: String { get }
}
protocol Age {
    var age: Int { get }
}

class Human: Name, Age {
    var name: String
    var age: Int
    init(_ name: String, _ age: Int) {
        self.name = name
        self.age = age
    }
}

class Animal: Name {
    var name: String
    init(_ name: String) { self.name = name } 
}

func printInfo(model: Name & Age ) {
    print(model.name)
    print(model.age)
}

printInfo(model: Human("sweetfood",20))
// Argument type 'Animal' does not conform to expected type 'Age'
printInfo(model: Animal("lucy"))

Human은 Name, Age를 모두 채택하여 printInfo의 매개변수로 사용이 가능하지만

Animal은 사용할 수 없다

프로토콜 일치 확인

Type casting의 is, as 연산자를 사용하여

프로토콜을 확인하고 특정 프로토콜로 캐스팅 할 수 있다.

  • is : 프로토콜을 준수하면 true, 그렇지 않으면 false를 반환한다
  • as? : 프로토콜을 준수하면 해당 프로토콜로 캐스팅 하고 그렇지 않으면 nil 반환
  • as! : 강제로 해당 프로토콜로 캐스팅한다. 성공하지 못하면 추후 런타임 에러 발생
protocol HasArea {
    var area: Double { get }
}

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi radius radius }
    init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

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 {
        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)

선택적 요구사항을 정의할 수 있다

선택적 요구사항은 프로토콜을 채택한 타입에서 필수로 구현하지 않아도 됨을 의미

선택적 요구사항 정의시 @objc optional 키워드를 사용하여 정의한다

@objc 키워드를 사용하면 클래스 타입에서만 채택할 수 있기 때문에 선택적 프로토콜 요구조건은 클래스에서만

채택할 수 있다

선택적 요구사항이 있는 프로토콜을 타입으로 사용하여 프로퍼티나 메서드를 호출 시 ( 델리게이트 패턴 같은)

자동으로 옵셔널로 취급하여 사용한다.

선택적이기 때문에 구현을 했을 수도, 안했을수도 있기 때문

예를들어

@objc protocol SomeProtocol {
    @objc optional someMethod()
}

SomeProtocol 타입으로 사용하는 프로퍼티에서 someMethod() 메서드를 호출하면

someMethod?()로 사용해야한다

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

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

Counter 타입의 increment() 메서드 안을 보면

if let amount = dataSource?.increment?(forCount: count) { 
... 
} else if let amount = dataSource?.fixedIncrement { 
...
}

increment?(forCount: count)로 사용, fixedIncrement를 옵셔널 바인딩으로 사용하는 것을 볼 수 있다.

태그:

카테고리:

업데이트:

댓글남기기