본문 바로가기

iOS

[iOS] [Swift] POP - 프로토콜 시작

프로토콜 시작


본 문서는 프로토콜지향 프로그래밍 서적을 참고하여 작성된 내용입니다.

 

객체지향 프로그래밍을 기반으로 개발을 하고 있다면 인터페이스 라는 개념에 익숙할 것 입니다.

 

객체지향 방식으로 설계를 진행하는 경우에는 보통은 클래스 구조와 객체가 어떻게 상호작용을 하는지에 중점을 두고 설계를 시작합니다.

 

객체란 프로퍼티 형태로 속성에 관한 정보를 가지며, 객체에 기대하는 속성과 행위(메소드)가 무엇인지 애플리케이션에게 전달해주는 청사진이 없다면 객체를 생성할 수 없습니다.

 

대부분의 객체지향 언어들은 이러한 청사진을 클래스 형태로 부릅니다.

 

즉, 클래스는 객체의 프로퍼티와 행위를 단일 타입으로 캡슐화하는 구성체입니다.

 

이러한 객체지향 방식의 패러다임과 프로토콜지향 방식 패러다임은 큰 차이가 있습니다.

 

프로토콜을 지향하는 설계는 클래스의 계층 구조가 아닌 프로토콜로 시작을 하게 됩니다.

 

프로토콜은 작업을 수행하기 위해 타입에서 필요로 하는 메소드, 프로퍼티 등 다양한 요구 사항을 정의하는 계약 역할을 합니다.

 

프로토콜을 채택하거나 이를 따르는 타입은 프로토콜에서 정의한 요구 사항을 반드시 구현해야 하기 때문에 프로토콜이 게약의 역할을 한다고 말합니다.

 

프로토콜 정의


프로토콜을 정의할때는 클래스 혹은 구조체 또는 열거형을 정의할 때 사용하는 문법과 매우 유사합니다.

 

protocol MyOwnProtocol {
    // 프로토콜 정의부가 여기에 위치합니다.
}

 

프로토콜을 정의하기 위해서는 protocol 이라는 키워드를 사용하게 되며 이렇게 정의한 프로토콜을 채택하고자 할 때는 아래와 같이 작성합니다.

 

struct JungsuSturct: MyOwnProtocol {
    // 구조체 구현부가 여기에 위차힙니다.
}

 

아시다시피 Swift는 다중 상속이 불가합니다, 하지만 다중 프로토콜 채택이 가능합니다.

마치 아래처럼요.

 

struct JungsuStruct: MyOwnProtocl, AnotherProtocol, JungsuProtocol {
    // 구조체 구현부가 여기에 위치합니다.
}

 

타입이 다양한 프로토콜을 채택할 수 있도록 하는 것은 프로토콜지향 프로그래밍에서 매우 중요한 개념입니다.

 

프로퍼티 요구 사항


프로토콜은 프로토콜을 따르는 타입에 명시된 이름과 타입을 갖는 특정 프로퍼티 제공을 요구할 수 있습니다.

 

프로토콜에서 프로퍼티를 정의할 때에는 get, set 키워드를 사용하여 읽기 전용 & 읽기 쓰기 전용 프로퍼티 여부를 명시합니다.

 

또한, 프로토콜에서는 Swift의 타입 추론(Type Inference)를 사용할 수 없으므로 반드시 프로퍼티의 타입을 명시해야 합니다.

 

다음 예시를 보며 프로토콜을 생성하는데 있어 프로퍼티를 어떻게 정의하는지 살펴보겠습니다.

 

protocol nameProtocol {
    var firstName: String {get set}
    var lastName: String {get set}
}

 

위 예시에서는 String 타입의 firstNamelastName 이라는 이름을 갖는 두 개의 읽기 쓰기 프로퍼티를 정의하였습니다.

 

따라서 이 프로토콜을 채택하는 모든 타입은 반드시 두 프로퍼티를 구현하도록 계약되는 겁니다.

 

만일 읽기 전용 프로퍼티를 정의하고자 할 때에는 아래와 같이 get 키워드만을 사용하여 정의하면 됩니다.

 

var readOnlyProperty: String {get}

 

메소드 요구 사항


프로퍼티에 대해 살펴봤으니 이번에는 메소드에 대해 살펴보도록 하겠습니다.

 

프로토콜은 프로토콜을 채택하는 타입에 구체적 메소드를 제공할 것을 요구할 수 있습니다.

 

해당 메소드들은 프로토콜 내에 정의되며 static 키워드를 사용하여 메소드가 타입 메소드가 되도록 정의할 수 있습니다.

 

protocol nameProtocol {
    var firstName: String {get set}
    var lastName: String {get set}

    func getFullName() -> String
}

이제 nameProtocol 을 채택하는 타입은 앞서 살펴본 두개의 프로퍼티에 더하여 String 타입을 반환하는 getFullName 이라는 메소드 또한 구현이 강제되어집니다.

 

만일, 구조체와 같은 value Type(구조체 또는 열거형) 의 경우 메소드가 메소드 자신이 속해 있느 인스턴스를 변경하고자 할 경우에는 mutating 를 추가하여 작성해주면 됩니다.

 

선택 가능한 요구 사항


앞서 살펴본 프로퍼티, 메소드 요구 사항은 프로토콜 내 정의된 경우 이를 채택할 때에는 반드시 구현되어야 합니다.

 

그러나 때로는 프로토콜이 선택 가능한 요구 사항을 정의하기를 바라는 경우도 존재하게 됩니다.

 

선택 가능한 요구 사항을 사용하기 위해서는 @objc 키워드를 이용합니다.

 

오직 클래스만이 @objc 속성을 사용하는 프로토콜을 채택할 수 있습니다. Value Type은 해당 프로토콜을 채택할 수 없습니다.

 

@objc protocol Phone {
    var phoneNumber: String {get set}
    @objc optional var emailAddress: String {get set}
    func dialNumber()
    @objc optional func getEmail()
}

optional 키워드를 사용하면 프로퍼티 혹은 메소드가 선택 가능하다는 것으로 표시할 수 있습니다.

 

위 예시는 선택 가능한 프로퍼티와 선택 가능한 메소드를 생성하는 방법입니다.



프로토콜 상속


앞서 얘기하였듯 프로토콜은 다중 채택이 가능하며 프로토콜은 프로토콜을 상속할 수 있습니다.

 

protocol nameProtocol {
    var firstName: String {get set}
    var lastName: String {get set}

    func getFullName() -> String
}

protocol Person: nameProtocol {
    var age: Int {get set}
}

struct Student: Person {
    // Propeties
    var firstName = ""
    var lastName = ""
    var age = 0

    // Methods
    func getFullName() -> String {
        return "\(firstName) \(lastName)"
    }
}

Student 구조체는 Person이라는 프로토콜을 채택하고 있으며, Person 프로토콜은 nameProtocol을 상속하고 있기에 즉 Student 구조체는 nameProtocol, Person 프로토콜을 채택하고 있는 모습과 동일합니다.

 

 

프로토콜 컴포지션


프로토콜 컴포지션은 타입이 다양한 프로토콜을 채용할 수 있도록 도와줍니다.

 

이는 클래스 계층 구조 대신 프로토콜을 사용함으로써 얻을 수 있는 가장 큰 장점입니다.

 

struct MyStruct: ProtocolOne, ProtocolTwo, ProtocolThree {
    // 구조체 구현부는 여기에 위치합니다.
}

 

위 예시가 프로토콜 컴포지션입니다.

 

컴포지션은 모든 요구 사항을 단일 프로토콜 혹은 단일 클래스에서 상속하지 않고 하나의 큰 요구사항을 모듈화하여 여러 작은 컴포넌트로 나눌 수 있게 해줍니다.

 

즉, 타입이 채택하는 프로토콜의 높이가 증가되어 깊이가 깊어지는 구조가 아니라 너비를 증가시키게 해줍니다.

 

이는 해당 프로토콜을 따르는 타입 모두가 필요로 하는 요구 사항이 아닌 것을 포함하는 비대한 타입을 생성하는 것을 피하게 해준다는 것을 의미합니다.

 

매우 단순한 개념처럼 보이지만, 이는 프로토콜지향 프로그래밍에서 반드시 필요한 개념입니다.

 

이 개녕미 없었다면 우리는 프로토콜의 재사용성에 유연하지 못할것이고 결국 타입에 따른 프로토콜을 더욱 자주 작성하게 되겠죠?

 

프로토콜을 타입으로 사용


프로토콜은 내부에 프로퍼티, 메소드등에 대한 선언만 되어있을뿐, 정의가 되어있지는 않습니다.

 

그러나 Swift에서는 프로토콜을 하나의 완벽한 타입으로 간주하며 다른 타입들처럼 사용할 수 있습니다.

 

이는 곧 프로토콜을 함수의 매개변수나 반환 타입으로 사용할 수 있다는 의미입니다.

 

protocol Person {
    var firstName: String {get set}
    var lastName: String {get set}
    var birthDate: Date {get set}
    var profession: String {get}

    init(firstName: String, lastName: String, birthDate: Date)
}

 

Person 프로토콜은 4개의 프로퍼티와 한 개의 생성자를 가지고 있습니다.

 

이번에는 프로토콜을 함수나 메소드 또는 생성자의 매개변수와 반환 타입으로 사용하는 방법에 대해 살펴봅니다.

 

func updatePerson(person: Person) -> Person {
    var newPerson: Person
    // Person 갱신 코드
    return newPerson
}

 

이처럼 일반적으로 사용해오던 타입의 역할을 합니다.



요약


  • 프로토콜은 객체지향 패러다임의 인터페이스와 유사함. 작업을 수행하기 위해 타입에서 필요로 하는 메소드, 프로퍼티, 그 외 요구 사항을 정의하는 계약의 역할
  • Swift는 다중 프로토콜 채택을 허용하며 이는 프로토콜지향 프로그래밍에서 매우 중요한 개념
  • 프로토콜 내 프로퍼티는 {get}, {get set} 을 통해 읽기 전용, 읽기쓰기 전용 여부를 명시하여야 합니다.
  • @objc 키워드로 제공되는 선택 가능한 요구 사항은 Reference Type만이 채택할 수 있습니다.
  • 프로토콜은 프로토콜을 상속할 수 있습니다.
  • 프로토콜 컴포지션이란, 다중 채택을 통해 타입의 구조를 높게 만드는게 아니라 너비를 증가시키게 해주는 기능.
    • 이를 통해 요구사항을 모듈화하여 여러 프로토콜로 나누어 작성하고, 불필요한 요구사항은 구현하지 않도록 도와준다는 의미입니다.
  • 프로토콜 자체가 타입으로 작용하여 메소드의 인자, 반환 타입으로도 사용할 수 있습니다.

'iOS' 카테고리의 다른 글

[iOS] [Swift] JSON Parsing with Codable  (0) 2021.02.27
[iOS] [Swift] POP - 프로토콜의 다형성  (0) 2021.01.01
[iOS] [Swift] UIKit Framework  (0) 2020.12.04
[iOS] [Swift] RxCocoa 맛보기  (0) 2020.11.26
[iOS] [Swift] RxSwift Operators Example  (0) 2020.11.20