How to use Protocols in Swift | Advanced Learning #15
Protocols
구현 목표
- 프로토콜 지향 언어인 스위프트 언어의 특성 상 대부분의 디폴트 구조체, 클래스, 열거형 등이 프로토콜 및 익스텐션을 통해 효율적 코드로 구현되어 있음 → 변수, 함수 등을 통해 확장성 확보하기
- 프로토콜을 사용하는 까닭
구현 태스크
- 프로토콜을 통한 특정 변수 전달의 확장성
- 프로토콜 다중 상속
- 프로토콜 익스텐션 사용
핵심 코드
protocol ButtonTextProtocol {
var buttonText: String { get }
}
extension ButtonTextProtocol {
func buttonPreesed2() {
print("IT IS EXTENDED VERSION!")
}
}
protocol ButtonPressedProtocol {
func buttonPressed()
}
protocol ButtonDataSourceProtocol: ButtonTextProtocol, ButtonPressedProtocol {
}
ButtonTextProfocol
과 ButtonPressedProcotol
은 각각 변수와 함수를 필요조건으로 삼고 있는 프로토콜
- 해당 프로토콜을 상속하는 오브젝트(구조체, 클래스 등)는 필수적으로 해당 필요조건을 구체적으로 표현해야 함
ButtonTextProfocol
, ButtonPressedProtocol
을 모두 사용해야 하는 조건이라면 두 개를 모두 쓰거나 두 개를 상속한 ButtonDataSourceProtocol
이라는 새로운 프로토콜을 생성 및 사용 가능
- 프로토콜 또한 익스텐션을 사용 가능 →
buttonPressed2()
함수는 프로토콜 내부에서 필요 조건으로 지정된 변수, 함수와 다르게 해당 프로토콜을 상속하는 모든 오브젝트에서 동일한 함수에 접근 및 사용 가능 → 해당 프로토콜을 상속하는 오브젝트 내에서 새로운 커스텀 함수 buttonPressed2()
를 구현하는 것 역시 가능
소스 코드
import SwiftUI
protocol ColorThemeProtocol {
var primary: Color { get }
var secondary: Color { get }
var tertiary: Color { get }
}
struct DefaultColorTheme: ColorThemeProtocol {
let primary: Color = .white
let secondary: Color = .blue
let tertiary: Color = .gray
}
struct AlternativeColorTheme: ColorThemeProtocol {
let primary: Color = .black
let secondary: Color = .orange
let tertiary: Color = .brown
}
- 서로 다른 색을 가지고 있는 구조체
- 프로토콜이 없다면 각 구조체 타입을 선언해야 함
protocol ButtonTextProtocol {
var buttonText: String { get }
}
extension ButtonTextProtocol {
func buttonPreesed2() {
print("IT IS EXTENDED VERSION!")
}
}
protocol ButtonPressedProtocol {
func buttonPressed()
}
protocol ButtonDataSourceProtocol: ButtonTextProtocol, ButtonPressedProtocol {
}
class DefaultDataSource: ButtonDataSourceProtocol {
var buttonText: String = "PROTOCOLS ARE AWESOME!"
func buttonPressed() {
print("BUTTON WAS PRESSED!")
}
}
class AlternativeDataSource: ButtonDataSourceProtocol {
var buttonText: String = "PROTOCOLS ARE LAME!"
func buttonPressed() {
print("BUTTON IS PRESSED VIA ALTERNATIVE ONE!")
}
}
- 두 개의 프로토콜을 상속한 하나의 프로토콜을 받고 있는 구조체
struct ProtocolsBootCamp: View {
@State var colorTheme: ColorThemeProtocol
@State var datasource: ButtonDataSourceProtocol
var body: some View {
ZStack {
colorTheme.tertiary.ignoresSafeArea()
VStack {
Spacer()
Text(datasource.buttonText)
.font(.headline)
.foregroundColor(colorTheme.secondary)
.padding()
.background(colorTheme.primary)
.cornerRadius(10)
.onTapGesture {
datasource.buttonPressed()
datasource.buttonPreesed2()
}
Spacer()
if datasource is DefaultDataSource {
Text("CURENT COLORTHEME IS DEFAULT")
.font(.headline)
.foregroundColor(.blue)
.padding()
.background()
.cornerRadius(10)
Text("CURENT DATASOURCE IS DEFAULT")
.font(.headline)
.foregroundColor(.blue)
.padding()
.background()
.cornerRadius(10)
} else {
Text("CURENT COLORTHEME IS ALTERNATIVE")
.font(.headline)
.foregroundColor(.blue)
.padding()
.background()
.cornerRadius(10)
Text("CURRENT DATASOURCE IS ALTERNATIVE")
.font(.headline)
.foregroundColor(.pink)
.padding()
.background()
.cornerRadius(10)
}
Button {
if colorTheme is DefaultColorTheme {
colorTheme = AlternativeColorTheme()
} else {
colorTheme = DefaultColorTheme()
}
if datasource is DefaultDataSource {
datasource = AlternativeDataSource()
} else {
datasource = DefaultDataSource()
}
datasource.buttonPressed()
datasource.buttonPreesed2()
} label: {
Text("Change Button DataSource and Color theme!")
.font(.headline)
.fontWeight(.semibold)
.withDefaultButtonFormmating(.blue)
.padding(.horizontal, 10)
}
.withPressableStyle(0.9)
Spacer()
}
}
}
}
@State
변수로 외부에서 값을 받고 있는 colorTheme
, datasource
변수는 구체적인 구조체 타입이 아니라 해당 구조체가 상속하고 있는 프로토콜로 정의 → 해당 프로토콜을 받고 있는 오브젝트라면 모두 변수에 대입 가능
- 이니셜라이즈 이후에도
Default
구조체와 Alternative
구조체 간의 할당이 자유로운 까닭은 모두 동일한 프로토콜을 상속하고 있기 때문
구현 화면
datasource
를 변경하는 버튼을 누르면 datasource
의 구체적인 타입이 변경됨 → 해당 구조체 내 프로토콜 필요조건으로 선언된 buttonPressed()
는 서로 다른 텍스트를 출력하는 함수라는 점을 알 수 있음 → 프로토콜의 확장성