타입으로서의 프로토콜
- 프로토콜은 다른 타입이 허용되는 여러 곳에서 다음과 같은 프로토콜을 사용할 수 있다.
- 함수, 메서드 또는 이니셜라이저에서의 매개변수 타입 또는 리턴 타입
- 상수, 변수 또는 프로퍼티로서의 타입
- 배열 또는 딕셔너리 또는 다른 컨테이너의 항목으로서의 타입
타입으로서의 프로토콜
프로토콜은 요구만 하고 스스로 기능을 구현하지 않는다. 하지만 프로토콜은 코드에서 완전한 하나의 타입으로 사용되기 때문에 여러 위치에서 프로토콜을 타입으로 사용할 수 있다.
프로토콜은 이름을 정할 때 대문자 카멜 케이스를 사용하기 때문에 SomeProtocol
, AnotherProtocol
처럼 첫 글자를 반드시 대문자로 표현해야 한다.
protocol RandemNumberGenrator {
func random() -> Double
}
RandemNumberGenrator
프로토콜에서 메서드를 요구했고, 이 프로토콜을 채택하는 타입은 반드시 random()
메서드를 구현해야 한다.
이제 타입으로서의 프로토콜을 사용해보자!
protocol RandomNumberGenerator {
func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
class Dice {
let sides: Int
//클래스에서 RandomNumberGenrator을 채택하지 않고
//genrator 상수 저장 프로퍼티에 타입으로 채택 해줬다.
let generator: RandomNumberGenerator
//Dice클래스의 저장 프로퍼티에 기본값이 없기때문에 init으로 초기화
//generator은 RandomNumberGenrator타입 이기 때문에 random()메서드 구현의무 없음(접근은 가능)
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
위 코드를 보면 Dice
자식클래스에 RandemNumberGenrator
프로토콜을 채택하지 않고 상수 저장 프로퍼티에 RandemNumberGenrator
타입으로 준 것을 확인할 수 있다. 그래서 generator
는 RandemNumberGenrator
타입이 된다.
또, 클래스의 저장프로퍼티에 기본값이 없기 때문에 init
으로 초기화를 해주고, generator
은 RandemNumberGenrator
타입이기 때문에 random()
이라는 메서드 요구를 구현할 필요가 없다. random()
메서드에 접근은 가능하다.
Delegate 패턴
"Delegate는 클래스나 구조체가 책임을 일부 다른 타입의 인스턴스로 전달 또는 위임 할 수 있게 하는 Design Pattern이다."
Delegate
패턴은 iOS 개발에서 자주 사용되는 디자인 패턴이다.
이것을 사용하지 않고 iOS 앱을 개발하는 것은 거의 불가능에 가깝다고 한다.
Delegate
란 단어는 '위임하다'라는 사전적 의미 가지고 있듯이, '위임자'라고 생각하면 된다. Delegate
패턴은 delegate
즉, 위임자를 갖고 있는 객체가 다른 객체에게 자신의 일을 위임하는 형태의 디자인 패턴이다.
위에 설명이 이해가 안 가는데.. 아래에서 쉽게 풀어보자면!
일단 이 Delegate
패턴을 실제로 작동하게 하는 기술이 바로 Protocol
이다.
대표적인 예시로 UITableView Class
는 UITableViewDelegate
프로토콜과 UITableViewDataSource
라는 프로토콜을 통해서 Table
을 관리, 상호작용하고 셀을 보여준다.
또, UITextView
는 UITextViewDelegate
프로토콜을 사용하여 TextView
내부의 상태변화(작성 시작, 중지 등..)를 보고받는다. TextView
는 사용자의 입력에 따라서 응답하고 이것에 따라 Delegate
기능을 호출하게 된다.
예를 들어 아래 예시는 TextView
가 포함되어 있고 해당 TextView
는 delegation
을 사용한다.
class SomeViewController: UIViewController, UITextViewDelegate {
var textView: UITextView
func viewDidLoad() {
textView.delegate = self
// TextViewDelegate는 SomeViewController의 인스턴스
}
}
UIViewController
를 상속받는 SomeViewController
를 생성했고, 동시에 UITextViewDelegate
라는 프로토콜도 채택했다. class
내부에는 UITextView
의 인스턴스인 textView
를 선언했고 viewDeleLoad
매소드에 textView.delegate
를 self
로 지정했다.
이렇게 UITextViewDelegate
를 보면, textView
에서 발생하는 모든 이벤트에 응답하기 위해 다양한 함수를 구현할 수 있는 것이다.
textViewDidBeginEditing(_:)
textViewDidEndEditing(_:)
textView(_:shouldChangeTextIn: replacementText:)
textViewDidChange(_:)
textViewDidChange
기능을 통해 textView
에 있는 문자 수를 업데이트하는 등의 기능을 사용할 수 있는 것이다.
또 다른 예를 보자면,
만약 UITextField
를 사용하여 iOS
앱을 개발할 때 UITextField
의 모든 메서드나 프로퍼티를 처음부터 만들어 나간다.
텍스트 필드의 모양, 모서리, cornerRadius
등..
그리고 WeatherViewController
에 TextFieldDidBeginEditing()
메서드를 생성한다고 가정해보자. WeatherViewController
가 편집을 감지할 때 이를 알리기 위해선 텍스트 필드가 필요한데, 이것을 작동하려면 UITextField
에 대한 클래스 정의를 UITextField
에 만들고 거기에서 textFieldDidBeginEditing
메서드를 호출하면 된다.
이렇게!
하지만 또 다른 AnotherClass
가 있고 그곳에 textFieldDidBeginEditing
메서드를 사용하려고 할 때 또다시 메서드를 클래스 안에 생성해야 할까? 이럴 때 필요한 것이 바로 Delegate Design Pattern!
솔루션은 이러하다.
UITextFieldDelegate
프로토콜을 생성하고textFieldDidBeginEditing
메서드 생성.UITextField
에delegate
변수를 생성하고 반드시UITextFieldDelegate
를 데이터 타입으로 채택.WeatherViewController
에UITextField
를 새롭게 초기화하여 생성하고.TextField.delegate
를self
로 지정해준다.- 마지막으로
textFieldDidBeginEditing
메서드를 호출.
이렇게 하면 또 다른 Class
가 있어도 UITextField
를 수정하지 않고 그대로 사용 가능하다는 장점이 있다.
이처럼 Delegate
를 통해서 업무를 위임하는 것이 아니라 기능을 확장하기 위해서 사용하는 것이 바로 Delegate Design Pattern 이다.
클래스 만들어보기
UITextField
를 사용할 때, 프로토콜과 Delegate
에 대해 더 깊게 알아보도록 하자! (엄청 복잡하기 때문에.. 정신줄 단단히!!!)
Udemy에서 나온 Delegate 패턴
에 대해 인용해서 가져와보자면
1. EmergenctCallHandler
라는 콜센터가 있고, 호출기(Delegate
)가 있다.
2. 이 Delegate
를 소지할 수 있는 사람은 CPR을 할 줄 알아야 하고, 이것은 AdvancedLifeSupport라는 인증서(Protocol
)이 있어야 한다. 즉, 이 프로토콜을 소지하고 있는 사람은 Delegate
를 가질 수 있는 조건이 있으며 그게 어떠한 사람이던(Class
) 상관없다.
코드로 알아보자면 아래와 같다.
//MARK: 1
protocol AdvancedLifeSupport {
func performCPR() //COR을 수행할줄 알아야함.
}
//MARK: 2
class EmergencyCallHandler {
//delegate로 AdvancedLifeSupport가 채택되었기 때문에 반드시 CPR을 할 줄 알아야 함.
//또, 반듯이 이 delegate는 AdvancedLifeSupport 데이터 타입을 채택해야 함.
var delegate: AdvancedLifeSupport?
func assessSituation(Director: String) {
print("무슨일이 일어나고있는지 \(Director)에게 전달해주세요.")
}
//이 메서드는 대리인이 누구인지 상관없다.
//delegate를 가지고있다면 CPR을 진행하라 에만 관심이 있음.
func medicalEmergency() {
delegate?.performCPR()
}
}
//MARK: 3
//대리인 지정
struct Paramedic: AdvancedLifeSupport {
//EmergencyCallHandler가 초기화 될때
init(handler: EmergencyCallHandler) {
handler.delegate = self
}
//어떻게 CPR을 할지 메서드 작성
func performCPR() {
print("위생병이 가슴압박 30회, 인공호흡 2회 시행, 가슴압박 및 인공호흡 반복한다.")
}
}
//대리인
class Doctor: AdvancedLifeSupport {
init(handler: EmergencyCallHandler) {
handler.delegate = self
}
func performCPR() {
print("의사가 가슴압박 30회, 인공호흡 2회 시행, 가슴압박 및 인공호흡 반복한다.")
}
func useStethoscope() {
print("청진기를 사용하여 심장소리를 듣는다")
}
}
//대리인
//Doctor 클래스를 참조한다.
class Surgeon: Doctor {
//슈퍼클래스인 Doctor가 하는 CPR을 동일하게 수행가능
override func performCPR() {
super.performCPR()
print("외과의사가 의사가하는 일을 옆에서 서포트 한다.")
}
func useElectricDrill() {
print("외과의사가 피를 석션 한다.")
}
}
//MARK: 4
let Seogun = EmergencyCallHandler()
let Mijin = Surgeon(handler: Seogun)
Seogun.assessSituation(Director: "서근")
Seogun.medicalEmergency()
//무슨일이 일어나고있는지 서근에게 전달해주세요.
//의사가 가슴압박 30회, 인공호흡 2회 시행, 가슴압박 및 인공호흡 반복한다.
//외과의사가 의사가하는 일을 옆에서 서포트 한다.
Delegate
를 채택해서 어떤 일을 할지 결정하고, 위임자를 설정해서 누가 누구의 일을 대신 맡을지 정해주고, 특정 상황에서 무슨 일을 대신해주면 되는지 작성해주면 Delegate Design Pattern
을 사용할 수 있다.
Delegation을 사용하는 이유
1. Delegate
는 protocol
을 준수하는 것만으로도 구현이 가능하므로 매우 유연하다.
2. 완전한 Class
또는 Struct
를 상속할 필요가 없기 때문에 Delegate
를 더 가볍게 사용할 수 있다.
3. Class
간 요구 사항을 전달해주는 protocol
만 있으면 연결이 쉽다.
4. Delegate
, Delegate Design Pattern
은 한 Class
와 또 다른 Class
의 상호작용을 간단히 할 수 있도록 돕는다.
읽어주셔서 감사합니다 🤟
'SWIFT > Grammar' 카테고리의 다른 글
Swift : 기초문법 [프로토콜#5 - 준수, 옵셔널 프로토콜 요구사항] (0) | 2022.01.31 |
---|---|
Swift : 기초문법 [프로토콜#4 - 익스텐션, 컬렉션 타입, 상속] (0) | 2022.01.30 |
Swift : 기초문법 [프로토콜#2 - 메서드 요구사항] (0) | 2022.01.27 |
Swift : 기초문법 [프로토콜#1 - 프로퍼티 요구사항] (1) | 2022.01.25 |
Swift : 기초문법 [타입 캐스팅(Type Casting)] (1) | 2022.01.24 |