[Swift] 프로토콜에 대해 알아보자!

zooneon·2020년 11월 25일
0

Swift 기본 문법

목록 보기
12/14

본 내용은 '스위프트 프로그래밍' 책을 학습한 후 이를 바탕으로 작성한 글입니다.

프로토콜

  • 프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항을 정의한다.
  • 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 한다.
  • 프로토콜은 정의를 하고 제시를 할 뿐, 스스로 기능을 구현하지 않는다.

프로토콜 채택

  • 프로토콜은 구조체, 클래스, 열거형의 모양과 비슷하게 정의할 수 있으며 protocol 키워드를 사용한다.
protocol 프로토콜 이름 {
	프로토콜 정의
}
  • 구조체, 클래스, 열거형 등에서 프로토콜을 채택하려면 타입 이름 뒤에 콜론을 붙여준 후 채택할 프로토콜 이름을 쉼표로 구분하여 명시하면 된다.
  • 클래스가 다른 클래스를 상속받는다면 상속받을 클래스 이름 다음에 채택할 프로토콜을 나열해준다.
struct SomeStruct: AProtocol, AnotherProtocol {
	//구조체 정의
}
class SomeClass: AProtocol, AnotherProtocol {
	//클래스 정의
}
enum SomeEnum: Aprotocol, AnotherProtocol {
	//열거형 정의
}

class AnotherClass: SomeClass, AProtocol, AnotherProtocol {
	//클래스 정의
}

프로토콜 요구사항

프로퍼티 요구

  • 프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있다.
  • 프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현하면 된다.
  • 프로퍼티를 읽기 전용으로 할지 읽고 쓰기가 모두 가능하게 할지는 프로토콜이 정해야 한다.
  • 프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의한다.
  • 읽기와 쓰기가 모두 가능한 프로퍼티는 프로퍼티의 정의 뒤에 { get set }이라고 명시하며, 읽기 전용 프로퍼티는 프로퍼티의 정의 뒤에 { get }이라고 명시한다.
  • 타입 프로퍼티를 요구하려면 static 키워드를 사용한다.
protocol SomeProtocol {
    var settableProperty: String { get set }
    var notNeedToBeSettableProperty: String { get }
}

//타입 프로퍼티
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
    static var anotherProperty: Int { get }
}

메서드 요구

  • 프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수 있다.
  • 프로토콜이 요구할 메서드는 프로토콜 정의에서 작성한다.
  • 메서드의 실제 구현부는 제외하고 메서드의 이름, 매개변수, 반환 타입등만 작성하며 가변 매개변수도 허용한다.
  • 프로토콜의 메서드 요구에서는 매개변수 기본값을 지정할 수 없다.
  • 타입 메서드를 요구할 때는 타입 프로퍼티 요구와 마찬가지로 앞에 static 키워드를 명시한다.
protocol SomeProtocol {
    func someMethod(data: Any, number: Int) -> String
}

가변 메서드 요구

  • 프로토콜이 어떤 타입이든 간에 인스턴스 내부의 값을 변경해야 하는 메서드를 요구하려면 프로토콜의 메서드 정의 앞에 mutating 키워드를 명시해야 한다.
  • 참조 타입인 클래스의 메서드 앞에는 mutating 키워드를 명시하지 않아도 인스턴스 내부 값을 바꾸는 데 문제가 없다.
  • 프로토콜에 mutating 키워드를 사용한 메서드 요구가 있다고 하더라도 클래스 구현에서는 mutating 키워드를 써주지 않아도 된다.
  • 만약 프로토콜에서 가변 메서드를 요구하지 않는다면, 값 타입의 인스턴스 내부 값을 변경하는 mutating 메서드는 구현이 불가능하다.
protocol Resettable {
    mutating func reset()
}

class Person: Resettable {
    var name: String?
    var age: Int?
    
    func reset() {
        selt.name = nil
        self.age = nil
    }
}

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

enum Direction: Resettable {
    case east, west, north, south, unknown
    
    mutating func reset() {
        self = Direction.unknown
    }
}

이니셜라이저 요구

  • 프로토콜에서 이니셜라이저를 요구할 때는 이니셜라이저의 매개변수를 지정하여 정의만 하고 중괄호를 포함한 이니셜라이저 구현은 하지 않는다.
  • 클래스 타입의 경우 상속받는 클래스는 부모클래스의 이니셜라이저를 모두 구현해야 하기 때문에 이니셜라이저를 구현할 때 required 식별자를 붙인 요구 이니셜라이저로 구현해야 한다.
  • 만약 클래스 자체가 상속받을 수 없는 final 클래스라면 required 식별자를 붙여줄 필요가 없다.
  • 만약 특정 클래스에 프로토콜이 요구하는 이니셜라이저가 이미 구현되어 있는 상황에서 그 클래스를 상속받은 클래스가 있다면, requiredoverride 식별자를 모두 명시하여 구현해주어야 한다.
  • 프로토콜은 일반 이니셜라이저 외에도 실패 가능한 이니셜라이저를 요구할 수도 있다.
  • 실패 가능한 이니셜라이저를 요구하는 프로토콜은 구현할 때 실패 가능한 이니셜라이저로 구현하거나 일반적인 이니셜라이저로 구현해도 상관 없다.
protocol Named {
    var name: String { get }
    
    init(name: String)
}

struct Pet: Named {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

//클래스의 경우 required 식별자 사용
class Person: Named {
    var name: String
    
    required init(name: String) {
        self.name = name
    }
}

//상속 불가능한 클래스의 경우 식별자 필요 없음
final class AnotherPerson: Named {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

//상속받은 클래스의 이니셜라이저 요구 구현 및 재정의
class School {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

class MiddleSchool: School, Named {
    required override init(name: String) {
        super.init(name: name)
    }
}

프로토콜의 상속과 클래스 전용 프로토콜

  • 프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다.
  • 프로토콜의 상속 리스트에 class 키워드를 추가해 프로토콜이 클래스 타입에만 채택될 수 있도록 제한할 수 있다.
  • 클래스 전용 프로토콜로 제한을 주기 위해서는 프로토콜의 상속 리스트의 맨 처음에 class 키워드가 위치해야 한다.
protocol Readable {
    func read()
}

protocol Writeable {
    func write()
}

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

class SomeClass: ReadWriteSpeakable {
    func read() {
        print("read")
    }
    
    func write() {
        print("write")
    }
    
    func speak() {
        print("speak")
    }
}

protocol ClassOnlyProtocol: class, Readable, Writeable {
    //클래스 전용 프로토콜
}

class AnotherClass: ClassOnlyProtocol {
    func read() {
        print("read")
    }
    
    func write() {
        print("write")
    }
}

프로토콜의 선택적 요구

  • 프로토콜의 요구사항 중 일부를 선택적 요구사항으로 저장할 수 있다.
  • 선택적 요구사항을 정의하고 싶은 프로토콜은 objc속성 이 부여된 프로토콜이어야 한다.
  • objc속성 이 부여되는 프로토콜은 Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있으므로 열거형이나 구조체 등에서는 objc속성이 부여된 프로토콜은 아예 채택할 수 없다.
  • objc속성을 사용하려면 Foundation 프레임워크 모듈을 import해야 한다.
  • 선택적 요구를 하면 프로토콜을 준수하는 타입에 해당 요구사항을 필수로 구현할 필요가 없다.
  • 선택적 요구사항은 optional 식별자를 요구사항의 정의 앞에 붙여주면 된다.
import Foundation

@objc protocol Moveable {
    func walk()
    @objc optional func fly()
}

class Tiger: NSObject, Moveable {
    func walk() {
        print("Tiger walks")
    }
}

class Bird: NSObject, Moveable {
    func walk() {
        print("Bird walks")
    }
    
    func fly() {
        print("Bird flys")
    }
}

let tiger: Tiger = Tiger()
let bird: Bird = Bird()

tiger.walk()    //Tiger walks
bird.walk()    //Bird walks
bird.fly()    //Bird flys
profile
블로그 이전했습니다. https://blog.zooneon.dev

0개의 댓글