프로토콜(Protocol: 규약 / 협약)
- 클래스와 상속의 단점
- 하나의 클래스만 상속 가능(다중 상속 불가능)
- 기본적인 상위클래스의 메모리 구조를 따라갈 수 밖에 없음
- 필요하지 않은 속성/메서드도 상속됨
- 클래스(레퍼런스 타입)에서만 가능
- 자격증의 개념이라고 생각하기
프로토콜의 도입
// 요구사항을 정의 (자격증의 필수 능력만 정의)
protocol SomeProtocol {
func playPiano()
}
// 채택 및 구현
// 구조체에서의 채택
struct MyStruct: SomeProtocol {
func playPiano() {
//code
}
// 이제 자격증의 능력이 생긴 것
}
// 클래스에서 채택
class MyClass: SomeProtocol { // Aclass 상속 시 -> Aclass,(콤마)+Protocol 이름 적기
func playPiano() {
//code
}
}
- 프로토콜을 타입으로 인식할 수 있음
프로토콜 문법
protocol MyProtocol { // 최소한의 요구사항 나열
func doSomething() -> Int
}
- 클래스에서 상속이 있는 경우
- 상위 클래스를 먼저 선언 후
- 프로토콜 채택 선언
class FamilyClass { }
// 2)채택 (클래스, 구조체, 열거형 다 가능)
class MyClass: FamilyClass, MyProtocol {
// 상위클래스인 FamilyClass 먼저 선언
// 3) (속성/메서드) 구체적인 구현
func doSomething() -> Int {
return 7
}
}
프로토콜의 요구사항의 종류
- 프로토콜을 채택하려는 클래스, 구조체, 열거형에 최소한 이런 내용을 구현해야한다고 선언하는 부분
- 속성 요구사항
- 메서드 요구사항 (메서드/생성자/서브스크립트)
- 속성 요구사항
import UIKit
import Foundation
protocol RemoteMouse {
var id: String { get } // ==> let 저장속성 / var 저장속성 / 읽기계산속성 / 읽기, 쓰기 계산속성
var name: String { get set } // ==> var 저장속성 / 읽기, 쓰기 계산속성
static var type: String { get set } // ==> 타입 저장 속성 (static)
// ==> 타입 계산 속성 (class)
}
struct TV: RemoteMouse {
var id: String = "456"
var name: String = "애플티비"
static var type: String = "리모콘"
}
class SmartPhone: RemoteMouse {
var id: String {
return "666"
}
var name: String {
get { "아이폰" }
set { }
}
static var type: String = "리모콘"
// 타입 저장 속성은 (상속은 되지만) 재정의 원칙적 불가능
}
class Ipad: RemoteMouse {
var id: String = "777"
var name: String = "아이패드"
// 타입 계산 속성은 재정의 가능 (class 키워드 가능)
class var type: String {
get { "리모콘" }
set { }
}
}
- 프로토콜 메서드 요구사항 정리
- 메서드의 헤드부분(인풋/아웃풋)의 형태만 요구사항으로 정의
- mutating 키워드 : (구조체로 제한하는 것은 아님) 구조체에서 저장 속성을 변경하는 경우, 구조체도 채택 가능하도록 허락하는 키워드
- 타입 메서드로 제한 하려면, static 키워드만 붙이면 됨
- 채택해서 구현하는 쪽에서 static / class 키워드 모두 사용
import UIKit
import Foundation
// 예시 1
// 1) 정의
protocol RandomDice {
static func reset() // 최소한 타입 메서드가 되야함 (class로 구현해서 재정의를 허용하는 것도 가능)
func randomNum() -> Int
// mutating func controlBox()
}
// 2) 채택 / 3) 구현
class Number: RandomDice {
static func reset() {
print("다시 세팅")
}
func randomNum() -> Int {
let a = random(in: 1...6)
print(a)
return a
}
}
// 예시 2
// 1) 정의
protocol Toggle {
mutating func toggle() // mutating 키워드는 메서드 내에서 속성 변경의 의미일뿐 (클래스에서 사용 가능)
}
// 2) 채택 / 3) 구현
enum OnOffSwitch: Toggle {
case on
case off
mutating func toggle() {
switch self { // on, off 둘 중 하나의 값을 가짐
case .off:
self = .on
case .on:
self = .off
}
}
}
var s = OnOffSwitch.off
s.toggle() // s -> .on
- 메서드 요구사항 - 생성자 요구사항
- (실제 프로젝트에서 사용하는 경우는 드묾)
- 클래스는 상속을 고려해야 하므로, 생성자 앞에 required를 붙어야 함
- 하위에서 구현을 강제하기 위함
- 구조체의 경우 상속이 없기 때문에, required 키워드 필요 없음
- final 키워드를 붙여서 상속을 막으면 required 생략가능
- 클래스에서는 반드시 지정생성자로 구현할 필요없음(편의생성자로 구현도 가능)
import UIKit
import Foundation
// E.g 1
protocol SomeProtocol {
init(num: Int)
}
class SomeClass: SomeClass {
required init(num: Int) {
// 실제 구현
}
}
class SomeSubClass: SomeClass {
// 하위 클래스에서 생성자 구현을 안하면 필수 생성자는 자동 상속
// required init(num: Int) 가 자동으로 구현된다는 뜻
}
// E.g 2
protocol AProtocol {
init()
}
class ASuperClass {
init() {
// 생성자의 내용 구현
}
}
class ASubClass: ASuperClass, AProtocol {
// AProtocol을 채택함으로 "required" 키워드 필요, 상속으로 인한 "override(재정의)" 재정의 l워드도 필요
required override init() {
// 생성자의 내용 구현
}
}
- 메서드 요구사항 - 실패가능 생성자의 경우
- [실패가능/불가능 생성자 요구사항]
- init?( ) 요구사항 → init( ) / init( )? / init!( )로 구현 O
- init( ) 요구사항 → init?( ) 로 구현 X
- 범위가 더 넓어지는 것은 안됨
// 실패가능 생성자
protocol AProto {
init?(num: Int) // 객체의 타입이 맞나? AClass? <== AClass은 범위가 속해있음
}
// 구조체에서 채택 (required 키워드 필요 X)
struct AStruct: AProto {
// Failable , Non-failable 모두 요구사항을 충족시ㅣㅁ
// init?(num: Int) {}
init(num: Int) {}
// init!(num: Int) {} // 셋 다 모두 괜찮음
}
// 클래스에서 채택
class AClass: AProto {
required init(num: Int) {}
}
- 서브스크립트 요구사항
- get, set 키워드를 통해서 읽기/쓰기 여부를 설정
- get 키워드 → 최소한 읽기 서브스크립트 구현 / 읽기, 쓰기 모두 구현 가능
- get/set 키워드 → 반드시 읽기, 쓰기 모두 구현해야 함
protocol DataList {
subscript(idx: Int) -> Int { get }
// 서브스크립트 문법에서 get 필수, set 선택
}
struct DataStructure: DataList {
// 읽기 전용 서브스크립트로 구현
subscript(idx: Int) -> Int { // 읽기 전용 서브스크립트로 구현한다면
get {
return 0
}
}
subscript(idx: Int) -> Int {
// 최소한만 따르면 됨
get {
return 0
}
set {
// 상세구현 생략
// 구현은 선택
}
}
}
- 프로토콜 채택과 구현 - 확장(Extension)에서
protocol Certificate {
func doSomething()
}
class Person {
}
// 관습적으로 본체보다는 확장에서, 채택 구현 (코드의 깔끔한 정리 가능)
extension Person: Certificate {
func doSomething() {
print("Do Something")
}
}
- 스위프트는 프로토콜을 “일급객체”로 취급
- 타입으로서 사용이 가능하다는 의미
- = Fisrt Class Citizen
- [프로토콜은 타입이다]
- “프로토콜”을 변수에 할당할 수 있음
- 함수를 호출할때, “프로토콜”을 파라미터로 전달할 수 있음
- 함수에서 “프로토콜”을 반환할 수 있음
protocol Remote {
func turnOn()
func turnOff()
}
class TV: Remote {
func turnOn() {
print("TV켜기")
}
func turnOff() {
print("TV끄기")
}
}
struct SetTopBox: Remote {
func turnOn() {
print("셋톱박스켜기")
}
func turnOff() {
print("박스끄기")
}
func doNetFlix() {
print("넷플 보기")
}
}
let sbox: Remote = SetTopBox()
sbox.turnOn()
sbox.turnOff() // 프로토콜에서 선언된 메서드만 실행이 가능함
(sbox as? SetTopBox)?.doNetFlix() // SetTopBox에서 정의한 메서드를 사용하고 싶을 땐 캐스팅을 한 이후에 사용하기
- 프로토콜 타입 취급의 장점
- 공통된 타입으로 뽑아낼 수 잇음
let electronic: [Remote] = [tv, sbox] // 프로토콜의 형식으로 담겨있음, 공통된 타입으로 뽑아낼 수 있다.
for item in electronic { // 켜기, 끄기 기능만 사용하니 타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
item.turnOn()
}
- 함수의 파라미터로 사용될 수 있음
func turnOnSomeElectronics(item: Remote) {
item.turnOn()
}
turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)
- 프로토콜 준수성 검사
- is / as 연산자 사용가능
- is 연산자
- 특정 타입이 프로토콜을 채택하고 있는지 확인(참 또는 거짓) / 그 반대도 확인가능
- 프로토콜이 더 범용성이 높다고 생각
- as 연산자
- 타입 캐스팅(특정 인스턴스를 프로토콜로 변환하거나, 프로토콜을 인스턴스 실제형식으로 캐스팅)
// 1) is연산자 ==============================
// 양방향으로 가능
// 특정타입이 프로토콜을 채택하고 있는지 확인
tv is Remote
sbox is Remote
// 프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능
electronic[0] is TV
electronic[1] is SetTopBox
// 2) as연산자 ==============================
// 업캐스팅(as)
let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()
// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
//(electronic[1] as? SetTopBox)?.doNetflix()
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.22 (0) | 2024.01.30 |
---|---|
Swift No.21 (1) | 2024.01.28 |
Swift No.19 (1) | 2024.01.25 |
Swift No.18 (1) | 2024.01.23 |
Swift No.17 (1) | 2024.01.23 |
프로토콜(Protocol: 규약 / 협약)
- 클래스와 상속의 단점
- 하나의 클래스만 상속 가능(다중 상속 불가능)
- 기본적인 상위클래스의 메모리 구조를 따라갈 수 밖에 없음
- 필요하지 않은 속성/메서드도 상속됨
- 클래스(레퍼런스 타입)에서만 가능
- 자격증의 개념이라고 생각하기
프로토콜의 도입
// 요구사항을 정의 (자격증의 필수 능력만 정의)
protocol SomeProtocol {
func playPiano()
}
// 채택 및 구현
// 구조체에서의 채택
struct MyStruct: SomeProtocol {
func playPiano() {
//code
}
// 이제 자격증의 능력이 생긴 것
}
// 클래스에서 채택
class MyClass: SomeProtocol { // Aclass 상속 시 -> Aclass,(콤마)+Protocol 이름 적기
func playPiano() {
//code
}
}
- 프로토콜을 타입으로 인식할 수 있음
프로토콜 문법
protocol MyProtocol { // 최소한의 요구사항 나열
func doSomething() -> Int
}
- 클래스에서 상속이 있는 경우
- 상위 클래스를 먼저 선언 후
- 프로토콜 채택 선언
class FamilyClass { }
// 2)채택 (클래스, 구조체, 열거형 다 가능)
class MyClass: FamilyClass, MyProtocol {
// 상위클래스인 FamilyClass 먼저 선언
// 3) (속성/메서드) 구체적인 구현
func doSomething() -> Int {
return 7
}
}
프로토콜의 요구사항의 종류
- 프로토콜을 채택하려는 클래스, 구조체, 열거형에 최소한 이런 내용을 구현해야한다고 선언하는 부분
- 속성 요구사항
- 메서드 요구사항 (메서드/생성자/서브스크립트)
- 속성 요구사항
import UIKit
import Foundation
protocol RemoteMouse {
var id: String { get } // ==> let 저장속성 / var 저장속성 / 읽기계산속성 / 읽기, 쓰기 계산속성
var name: String { get set } // ==> var 저장속성 / 읽기, 쓰기 계산속성
static var type: String { get set } // ==> 타입 저장 속성 (static)
// ==> 타입 계산 속성 (class)
}
struct TV: RemoteMouse {
var id: String = "456"
var name: String = "애플티비"
static var type: String = "리모콘"
}
class SmartPhone: RemoteMouse {
var id: String {
return "666"
}
var name: String {
get { "아이폰" }
set { }
}
static var type: String = "리모콘"
// 타입 저장 속성은 (상속은 되지만) 재정의 원칙적 불가능
}
class Ipad: RemoteMouse {
var id: String = "777"
var name: String = "아이패드"
// 타입 계산 속성은 재정의 가능 (class 키워드 가능)
class var type: String {
get { "리모콘" }
set { }
}
}
- 프로토콜 메서드 요구사항 정리
- 메서드의 헤드부분(인풋/아웃풋)의 형태만 요구사항으로 정의
- mutating 키워드 : (구조체로 제한하는 것은 아님) 구조체에서 저장 속성을 변경하는 경우, 구조체도 채택 가능하도록 허락하는 키워드
- 타입 메서드로 제한 하려면, static 키워드만 붙이면 됨
- 채택해서 구현하는 쪽에서 static / class 키워드 모두 사용
import UIKit
import Foundation
// 예시 1
// 1) 정의
protocol RandomDice {
static func reset() // 최소한 타입 메서드가 되야함 (class로 구현해서 재정의를 허용하는 것도 가능)
func randomNum() -> Int
// mutating func controlBox()
}
// 2) 채택 / 3) 구현
class Number: RandomDice {
static func reset() {
print("다시 세팅")
}
func randomNum() -> Int {
let a = random(in: 1...6)
print(a)
return a
}
}
// 예시 2
// 1) 정의
protocol Toggle {
mutating func toggle() // mutating 키워드는 메서드 내에서 속성 변경의 의미일뿐 (클래스에서 사용 가능)
}
// 2) 채택 / 3) 구현
enum OnOffSwitch: Toggle {
case on
case off
mutating func toggle() {
switch self { // on, off 둘 중 하나의 값을 가짐
case .off:
self = .on
case .on:
self = .off
}
}
}
var s = OnOffSwitch.off
s.toggle() // s -> .on
- 메서드 요구사항 - 생성자 요구사항
- (실제 프로젝트에서 사용하는 경우는 드묾)
- 클래스는 상속을 고려해야 하므로, 생성자 앞에 required를 붙어야 함
- 하위에서 구현을 강제하기 위함
- 구조체의 경우 상속이 없기 때문에, required 키워드 필요 없음
- final 키워드를 붙여서 상속을 막으면 required 생략가능
- 클래스에서는 반드시 지정생성자로 구현할 필요없음(편의생성자로 구현도 가능)
import UIKit
import Foundation
// E.g 1
protocol SomeProtocol {
init(num: Int)
}
class SomeClass: SomeClass {
required init(num: Int) {
// 실제 구현
}
}
class SomeSubClass: SomeClass {
// 하위 클래스에서 생성자 구현을 안하면 필수 생성자는 자동 상속
// required init(num: Int) 가 자동으로 구현된다는 뜻
}
// E.g 2
protocol AProtocol {
init()
}
class ASuperClass {
init() {
// 생성자의 내용 구현
}
}
class ASubClass: ASuperClass, AProtocol {
// AProtocol을 채택함으로 "required" 키워드 필요, 상속으로 인한 "override(재정의)" 재정의 l워드도 필요
required override init() {
// 생성자의 내용 구현
}
}
- 메서드 요구사항 - 실패가능 생성자의 경우
- [실패가능/불가능 생성자 요구사항]
- init?( ) 요구사항 → init( ) / init( )? / init!( )로 구현 O
- init( ) 요구사항 → init?( ) 로 구현 X
- 범위가 더 넓어지는 것은 안됨
// 실패가능 생성자
protocol AProto {
init?(num: Int) // 객체의 타입이 맞나? AClass? <== AClass은 범위가 속해있음
}
// 구조체에서 채택 (required 키워드 필요 X)
struct AStruct: AProto {
// Failable , Non-failable 모두 요구사항을 충족시ㅣㅁ
// init?(num: Int) {}
init(num: Int) {}
// init!(num: Int) {} // 셋 다 모두 괜찮음
}
// 클래스에서 채택
class AClass: AProto {
required init(num: Int) {}
}
- 서브스크립트 요구사항
- get, set 키워드를 통해서 읽기/쓰기 여부를 설정
- get 키워드 → 최소한 읽기 서브스크립트 구현 / 읽기, 쓰기 모두 구현 가능
- get/set 키워드 → 반드시 읽기, 쓰기 모두 구현해야 함
protocol DataList {
subscript(idx: Int) -> Int { get }
// 서브스크립트 문법에서 get 필수, set 선택
}
struct DataStructure: DataList {
// 읽기 전용 서브스크립트로 구현
subscript(idx: Int) -> Int { // 읽기 전용 서브스크립트로 구현한다면
get {
return 0
}
}
subscript(idx: Int) -> Int {
// 최소한만 따르면 됨
get {
return 0
}
set {
// 상세구현 생략
// 구현은 선택
}
}
}
- 프로토콜 채택과 구현 - 확장(Extension)에서
protocol Certificate {
func doSomething()
}
class Person {
}
// 관습적으로 본체보다는 확장에서, 채택 구현 (코드의 깔끔한 정리 가능)
extension Person: Certificate {
func doSomething() {
print("Do Something")
}
}
- 스위프트는 프로토콜을 “일급객체”로 취급
- 타입으로서 사용이 가능하다는 의미
- = Fisrt Class Citizen
- [프로토콜은 타입이다]
- “프로토콜”을 변수에 할당할 수 있음
- 함수를 호출할때, “프로토콜”을 파라미터로 전달할 수 있음
- 함수에서 “프로토콜”을 반환할 수 있음
protocol Remote {
func turnOn()
func turnOff()
}
class TV: Remote {
func turnOn() {
print("TV켜기")
}
func turnOff() {
print("TV끄기")
}
}
struct SetTopBox: Remote {
func turnOn() {
print("셋톱박스켜기")
}
func turnOff() {
print("박스끄기")
}
func doNetFlix() {
print("넷플 보기")
}
}
let sbox: Remote = SetTopBox()
sbox.turnOn()
sbox.turnOff() // 프로토콜에서 선언된 메서드만 실행이 가능함
(sbox as? SetTopBox)?.doNetFlix() // SetTopBox에서 정의한 메서드를 사용하고 싶을 땐 캐스팅을 한 이후에 사용하기
- 프로토콜 타입 취급의 장점
- 공통된 타입으로 뽑아낼 수 잇음
let electronic: [Remote] = [tv, sbox] // 프로토콜의 형식으로 담겨있음, 공통된 타입으로 뽑아낼 수 있다.
for item in electronic { // 켜기, 끄기 기능만 사용하니 타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
item.turnOn()
}
- 함수의 파라미터로 사용될 수 있음
func turnOnSomeElectronics(item: Remote) {
item.turnOn()
}
turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)
- 프로토콜 준수성 검사
- is / as 연산자 사용가능
- is 연산자
- 특정 타입이 프로토콜을 채택하고 있는지 확인(참 또는 거짓) / 그 반대도 확인가능
- 프로토콜이 더 범용성이 높다고 생각
- as 연산자
- 타입 캐스팅(특정 인스턴스를 프로토콜로 변환하거나, 프로토콜을 인스턴스 실제형식으로 캐스팅)
// 1) is연산자 ==============================
// 양방향으로 가능
// 특정타입이 프로토콜을 채택하고 있는지 확인
tv is Remote
sbox is Remote
// 프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능
electronic[0] is TV
electronic[1] is SetTopBox
// 2) as연산자 ==============================
// 업캐스팅(as)
let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()
// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
//(electronic[1] as? SetTopBox)?.doNetflix()
반응형
'iOS > Swift' 카테고리의 다른 글
Swift No.22 (0) | 2024.01.30 |
---|---|
Swift No.21 (1) | 2024.01.28 |
Swift No.19 (1) | 2024.01.25 |
Swift No.18 (1) | 2024.01.23 |
Swift No.17 (1) | 2024.01.23 |