iOS의 프로토콜이란




개요


애플은 WWDC2015에서 스위프트를 소개하면서 프로토콜지향 프로그래밍 언어라고 했다.

그러나 프로토콜 지향 언어라고 해서 프로토콜만 있는 것이 아니라 더 많은 것을 포함하고 있다.

스위프트의 표준 라이브러리는 프로토콜에 기반을 둔다.




프로토콜 요구 사항


프로토콜에서 프로퍼티를 정의할 때에는 get과 set 키워드를 사용해 명시해줘야한다.

프로토콜에서는 타입 추론을 사용할 수 없기 때문에 프로퍼티의 타입을 명시해줘야한다.

메소드는 구현부가 없는 점을 제외하고는 클래스와 구조체에서 정의했던 것과 같다.

static을 사용할 수 있지만 기본 값을 세팅하지는 못한다.

구조체 같은 값 타입일 경우 메소드 앞에 mutating을 추가해야한다. 따라서 프로토콜에서도 붙여줘야한다.

@objc로 프로토콜 속성을 부여하면 클래스만 채택이 가능하다.




프로토콜 상속


프로토콜은 상속이 가능하고 요구사항들을 추가할 수가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protocol Name {
  var firstName: String {get set}
  var lastName: String {get set}
  
  func getFullName() -> String
}

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

struct Student: Person {
  var firstName = ""
  var lastName = ""
  var age = 0
  
  func getFullName() -> String {
    return "\(firstName) \(lastName)"
  }
}




프로토콜 컴포지션


프로토콜 컴포지션은 타입이 여러 프로토콜을 채용할 수 있게 해준다.

클래스는 한 개의 슈퍼클래스만 상속할 수 있지만 프로토콜을 사용하면 해결할 수 있다.

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




프로토콜 타입


프로토콜에 아무런 기능이 구현돼 있지 않다고 하더라도 스위프트에서는 여전히 하나의 완벽한 타입으로 간주하고 사용할 수 있다.

쉽게말해 함수의 매개변수나 반환형으로 사용할 수 있다는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol Person {
  var firstName: String {get set}
  var lastName: String {get set}
  var age: Int {get set}
  
  func getFullName() -> String
}

func updatePerson(person: Person) -> Person {
  var newPerson: Person
  ...//newPerson 구현
  return newPerson
}




프로토콜의 다형성


다형성은 polymorphism으로 많다는 뜻의 poly와 형태를 뜻하는 morphe에 어원을 두고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protocol Person {
  var firstName: String {get set}
  var lastName: String {get set}
  var age: Int {get set}
  
  func getFullName() -> String
}

struct SwiftProgrammer: Person {
  ...
}

struct CProgrammer: Person {
  ...
}

var swift = SwiftProgrammer(...)
var c = CProgrammer(...)

var programmer: [Person] = [ ]
programmer.append(swift)
programmer.append(c)

만약에 programmer로 c의 다른 기능을 접근하고 싶다면 형변환을 해야한다.

이때는 as?로 하면 된다.




프로토콜의 연관타입


프로토콜에 associated type을 정의할 수가 있다.

연관타입이란 프로토콜 내에서 타입을 대신해 사용할 수 있는 이름을 제공한다.

연관타입에서 사용하는 실제 타입은 프로토콜이 채택되기 전까지는 정의되지 않는다.

연관타입을 사용한다는 것은 사용할 타입을 정확이 모르니깐 프로토콜을 채택하는 애한테 물어봐라고 할 수 있다.

associatedtype 키워드를 사용하면 된다.

1
2
3
4
5
protocol Queue {
  associatedtype QueueType
  mutating func addItem(item: QueueType)
  func count() -> Int
}

제네릭인것같은디




델리게이션


델리게이션은 코코아와 코코아 터치 프레임워크에서 광범위하게 사용된다.

동작을 델리겟(위임)하는 인스턴스는 델리게이트 인스턴스의 참조를 저장하고 있다가 어떠한 동작이 발생하면 델리게이팅 인스턴스는 계획된 함수를 수행하기 위해 델리게이트를 호출한다.

스위프트에서는 델리게이트가 해야할 일을 정의한 프로토콜을 생성하는 방식으로 델리게이션 디자인 패턴을 구현한다.

아래 예제를 보면 동작을 delegate한다는 것이 뭔지 감이 온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protocol DisplayNameDelegate {
  func displayName(name: String)
}

struct MyDelegate: DisplayNameDelegate {
  func displayName(name: String) {
    print("Name: \(name)")
  }
}

struct Person {
  var displayNameDelegate: DisplayNameDelegate
  
  var firstName = "" {
    didset {
      displayNameDelegate.displayName(name: getFullName())
    }
  }
  
  init(displayNameDelegate: DisplayNameDelegate) {
    self.displayNameDelegate = displayNameDelegate
  }
  
  func getFullName() -> String {
    return "\(firstName)"
  }
}