[iOS] Protocol / 프로토콜 / 개념 의미 요구사항 확장 POP

2024. 12. 29. 13:42🍏/Swift

프로토콜 

  • 프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다.
  • 구조체, 클래스, 열거형은 프로토콜을 채택해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다.
  • 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다고 표현합니다.
  • 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야합니다. 즉 프로토콜은 기능을 정의하고 제시할 뿐이지 스스로 기능을 구현하지는 않습니다.
  1. 공통 인터페이스 정의:
    • 서로 다른 타입에 동일한 동작을 강제.
  2. 의존성 역전:
    • 코드의 유연성과 확장성을 증가.
  3. 추상화:
    • 구체적인 구현이 아닌 동작에 집중.

 

프로토콜의 의미: 구현자와 사용자의 관점

1. 구현하는 입장에서의 프로토콜

프로토콜은 타입(클래스, 구조체, 열거형 등)이 특정 기능을 반드시 구현하도록 강제합니다. 이는 개발 과정에서 협업과 유지보수의 일관성을 보장합니다.
구현자는 이렇게 생각할 수 있습니다:

"나는 이 프로토콜을 채택했으니, 프로토콜에서 요구한 기능을 구현해야 한다."

2. 사용하는 입장에서의 프로토콜

프로토콜을 채택한 타입의 인스턴스를 사용할 때, 프로토콜을 통해 타입이 제공하는 기능을 명확히 알 수 있습니다. 이는 컴파일러가 타입의 특정 기능을 요구할 때도 유용합니다.
사용자는 이렇게 생각할 수 있습니다:

"이 타입이 이 프로토콜을 준수하고 있으니, 이 타입은 반드시 이런 기능을 제공할 것이다."

 

프로토콜의 요구사항

프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의합니다.
구조체, 클래스, 열거형은 프로토콜을 채택(Adopted)해서 프로토콜의 요구사항을 실제로 구현할 수 있습니다.
어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 '프로토콜을 준수한다(Conform)' 고 표현합니다.
프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 합니다.

1. 프로퍼티 요구

프로퍼티 요구는 항상 var 키워드를 사용합니다. 
get은 읽기만 가능해도 상관 없다는 뜻이며
get과 set을 모두 명시하면 읽기 쓰기 모두 가능한 프로퍼티여야 합니다.

protocol Talkable {
    var topic: String { get set }
    var language: String { get }
}

2. 메서드 요구

프로토콜은 메서드 시그니처만 정의

protocol Talkable {
    //메서드 요구
    func talk()
}

3. 이니셜라이저 요구

특정 이니셜라이저를 요구할 수 있음.

protocol Talkable {
    //이니셜라이저 요구
    init(topic: String, language: String)
}

 

프로토콜 채택 및 준수

/*프로토콜*/

// Person 구조체는 Talkable 프로토콜을 채택했습니다.
struct Person: Talkable{
    // var topic: String
    // let language: String

    //읽기 전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
    var language: String{ return "한국어"}
    //물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있습니다.
    var subject: String = ""
    var topic: String{
        set{
            self.subject = newValue
        }
        get{
            return self.subject
        }
    }
    
    func talk()  {
        print("\(topic)에 대해 \(language)로 말합니다")
    }
    
    init(topic: String, language: String){
        self.topic = topic
        self.language = language
    }
}

 

프로토콜 확장(Protocol Extension)을 사용하는 이유

프로토콜 확장은 프로토콜에 기본 구현을 추가하는 기능입니다. 이를 통해 공통 동작을 재사용할 수 있습니다.

프로토콜 확장의 이점

  1. 기본 구현 제공:
    • 모든 타입에 동일한 동작 제공.
  2. 코드 재사용성 증가:
    • 공통 로직을 한 곳에서 정의.
  3. 기능 확장:
    • 기존 타입에 새로운 메서드 추가.
protocol Drawable {
    func draw()
}

extension Drawable {
    func draw() {
        print("Drawing a shape")
    }
}

struct Circle: Drawable {}
struct Square: Drawable {}

let circle = Circle()
circle.draw() // "Drawing a shape"

let square = Square()
square.draw() // "Drawing a shape"

 

프로토콜 지향 프로그래밍(Protocol-Oriented Programming)의 장점

프로토콜 지향 프로그래밍(POP)은 Swift의 프로토콜과 프로토콜 확장을 활용한 설계 패턴입니다. 객체 지향 프로그래밍(OOP)에서 클래스 중심의 설계를 대체하거나 보완합니다.

장점

  1. 유연성과 재사용성:
    • 공통 기능을 프로토콜 확장에서 정의하여 다양한 타입에 쉽게 적용.
  2. 다중 상속 문제 해결:
    • 클래스의 단일 상속 제약을 극복. 여러 프로토콜을 채택 가능.
  3. 테스트 용이성:
    • Mock 객체를 쉽게 만들어 단위 테스트에 유리.
  4. 추상화 강화:
    • 구체적인 구현이 아닌 동작에 집중하여 의존성 줄임.
protocol Flyable {
    func fly()
}

protocol Swimmable {
    func swim()
}

struct Duck: Flyable, Swimmable {
    func fly() {
        print("Duck is flying")
    }
    
    func swim() {
        print("Duck is swimming")
    }
}

let duck = Duck()
duck.fly() // "Duck is flying"
duck.swim() // "Duck is swimming"

 

프로토콜 상속

// 프로토콜 상속
// 프로토콜은 클래스와 다르게 다중상속이 가능합니다

//protocol 프로토콜 이름: 부모 프로토콜 이름 목록{
//
//}

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

struct SomeType: ReadWriteSpeakable {
    func read(){
        print("Read")
    }
    
    func write(){
        print("Write")
    }
    
    func speak(){
        print("Speak")
    }
}

 

클래스 상속과 프로토콜

클래스에서 상속과 프로토콜 채택을 동시에 하려면 상속받으려는 클래스를 먼저 명시하고, 그 뒤에 채택할 프로토콜 목록을 작성합니다.

class SuperClass: Readable {
    func read() {print("read")}
}
class SubClass: SuperClass, Writeable, ReadSpeakable{
    func write() { print("write") }
    func speak() { print("speak") }
}

 

프로토콜 준수 확인

인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있습니다.
is, as 연산자 사용

let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()

var someAny: Any = sup
someAny is Readable // true
someAny is ReadWriteSpeakable

someAny = sub
someAny is Readable
someAny is ReadSpeakable

someAny = sup

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
}

if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
    someReadSpeakable.speak()
}

someAny = sub
if let someReadable: Readable = someAny as? Readable{
    someReadable.read()
}