[Swift] 프로토콜 Protocol

Bibi·2022년 3월 7일
0

[Swift] 프로토콜 Protocol

Swift 프로그래밍 - 377쪽 ~

프로토콜 : 특정 역할(기능)을 하기 위한 메서드, 프로퍼티, 기타 요구사항의 청사진을 정의한 것

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

  • 구조체/클래스/열거형은 특정 프로토콜을 채택(adopted)해서 그 역할을 위한 요구사항을 구현한다.
  • 어떤 타입이 어떤 프로토콜의 요구사항을 따르면, '해당 프로토콜을 준수한다(conform)'라고 표현한다.

스위프트에서 프로토콜은 완전한 하나의 타입으로 사용된다.

  • 파라미터나 리턴타입으로 사용 가능
  • 프로퍼티, 변수, 상수의 타입으로 사용 가능
  • 배열 등 컨테이너 요소의 타입으로 사용 가능

프로토콜 만들기

: protocol 키워드를 사용해 선언한다.

protocol 프로토콜이름 {
    프로토콜 정의
}

프로퍼티 요구사항

  • 프로퍼티와 타입 프로퍼티 요구 가능
  • 프로퍼티 요구사항은 항상 var로 선언
  • 프로퍼티를 읽기 전용({ get }) 으로 할지, 읽고 쓰기({ get, set })로 할지 정해줘야 함
  • 구현할 때는 프로퍼티의 이름과 타입만 맞으면 됨
  • 타입 프로퍼티는 static 키워드를 사용해 선언
protocol SomeProtocol {
    var settableProperty: String { get set }
    var notNeedToBeSettableProperty: String { get }
    static var typeProperty: Int { get }
}

메서드 요구사항

  • 인스턴스 메서드 또는 타입 메서드 요구 가능
  • 메서드에서 함수 body를 제외하고 시그니처만 작성
  • 타입 메서드는 static 키워드 사용
protocol Sendable {
    var from: Sendable { get }
    
    func send(data: Any)
  
    static func isSendableInstance(_ instance: Any) -> Bool
}

가변 메서드 요구 mutating method requirements

  • 값 타입(구조체, 열거형)의 인스턴스 메서드에서 자신 내부의 값을 변경할 때 func 앞에 mutating으로 선언해 준다.
  • 가변 메서드를 요구한 프로토콜을 클래스가 구현할 때는 mutating 키워드를 생략한다.
  • 프로토콜에서 가변 메서드를 요구하지 않는다면, mutating 메서드는 구현이 불가능하다
protocol Resettable {
  mutating func reset()
}

struct Point: Resettable {
    var x: Int = 0
    var y: Int = 0
  
    mutating func reset() {
        self.x = 0
        self.y = 0
    }
}

이니셜라이저 요구

  • 메서드와 마찬가지로 이니셜라이저의 매개변수만 지정하고, body는 구현하지 않는다.
  • 클래스 타입에서 프로토콜이 요구한 이니셜라이저를 구현할 때는 required 키워드를 붙인 요구 이니셜라이저로 구현한다.
    • 해당 클래스를 상속받는 모든 클래스는 같은 프로토콜과 요구 이니셜라이저를 구현해야 하기 때문.
    • 상속할 수 없는 final 클래스라면 required를 생략 가능함
  • 프로토콜이 요구한 이니셜라이저가 구현된 클래스를 상속받은 클래스에서는 required override를 모두 명시해야 함
  • 실패 가능한 이니셜라이저도 요구 가능
protocol Name {
  var name: String { get }
  
  init(name: String)
}

class Person: Named {
  var name: String
  
  required init(name: String) {
    self.name = name
  }
}

프로토콜 채택하기

  • 구조체, 클래스, 열거형 등에서 타입 이름 뒤에 콜론을 붙인 후, 채택할 프로토콜을 쉼표로 나열함
  • 클래스 상속도 함께 받는 경우, 상속받는 클래스 이름 다음에 프로토콜 나열
struct SomeStruct: AProtocol, AnotherProtocol {
  
}

프로토콜의 상속

  • 프로토콜끼리도 상속이 가능하다.
  • 문법은 클래스 상속 문법과 유사하다.
  • 구현할 때는 상속받은 프로토콜의 요구사항까지 모두 구현해야 한다.
protocol Readable {
    func read()
}

protocol Writeable {
    func write()
}

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

클래스 전용 프로토콜

  • 프로토콜의 상속 리스트에 class 키워드를 추가하면, 프로토콜이 클래스 타입에만 채택될 수 있도록 제한할 수 있다.
  • 맨 처음에 class 키워드를 넣어야 한다.
protocol ClassOnlyProtocol: class, Readable, Writeable {
  // 이 프로토콜은 클래스에서만 채택 가능
}

프로토콜 조합(composition)

  • 매개변수 타입에 여러 프로토콜을 한 번에 조합하여 요구할 수 있음
    • 주어진 프로토콜을 모두 준수해야 함
  • 매개변수 하나가 프로토콜을 둘 이상 요구할 수 있음
  • 특정 클래스의 인스턴스 역할을 할 수 있는지도 함께 확인할 수 있음
    • 단, 클래스는 한 타입만 조합할 수 있음
protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

func celebrateBirthday(to celebrator: Named & Aged) {
    print("Happy Birthday \(celebrator.name)! Now you ard \(celebrator.age)")
}

class Car: Named {
  var name: String
  
  init(name: String) {
    self.name = name
  }
}

var someVariable: Car & Named

프로토콜 준수 확인

타입캐스팅의 is, as 연산자로 대상이 프로토콜을 준수하는지 확인하거나, 특정 프로토콜로 캐스팅할 수 있다.

  • is : 해당 인스턴스가 프로토콜을 준수하는지 확인
  • as? : 다른 프로토콜로 다운캐스팅 시도
  • as! : 다른 프로토콜로 강제 다운캐스팅
struct Person: Named, Aged {
  var name: String
  var age: Int
}

let bibi: Person = Person(name: "bibi", age: 99)
print(bibi is Named) // true
print(bibi is Aged) // true

if let castedInstance: Name = bibi as Named {
  print("\(castedInstance) is Named")
} // Person is Named

프로토콜 변수와 상수

  • 프로토콜을 타입으로 갖는 변수/상수에는, 그 프로토콜을 준수하는 타입의 어떤 인스턴스든 할당할 수 있다.
  • 프로토콜만으로 스스로 인스턴스 생성 및 초기화를 할 수는 없다.

위임을 위한 프로토콜 (Delegation)

  • 위임(Delegation 델리게이션) : 클래스나 구조체가 자신의 책임을 다른 인스턴스에게 위임하는 디자인 패턴.
    • 책임을 수행하도록 정의한 프로토콜을 준수하는 타입은 그 책임을 수행함이 보장되므로, 프로토콜을 준수할 때 책임을 위임할 수 있다.
  • 위임 패턴은 애플 프레임워크에서 사용하는 주요 패턴이다.
    • 다양한 프로토콜이 "...Delegate"와같은 이름으로 정의되어 있다.
    • 예) UITableView 타입의 인스턴스가 할 일을 위임받아 처리하는 인스턴스는 UITableViewDelegate 프로토콜을 준수해야 함

0개의 댓글