Swift | Protocol - 1
Protocol
많은 블로그에서 정리를 하자면 청사진이다라고 하지만
난 잘...
내 기준에서 이해하기론 프로토콜을 정의만 하고 프로토콜을 채택한 곳에서 구현을
할 수 있다라고 정의를 내렸고
사실 Protocol이 c#과 비슷하게 사용하는건 Interface라고 생각했다
인터페이스도 동일하게 구현없이 공통된 목적을 가진 함수를 만들고
구체적인 구현은 채택한 곳에서만 사용하고 있기 때문이다
쉽게 말하자면 예를 들어 게임 중 대전게임이나 몬스터를 공격할 때
각각의 능력이나 눈에 보이는 모션은 다르지만
공통적으로 공격한다라는 함수를 구현해야하기 때문에
Attack()과 같은 공통된 함수를 만들어야한다
그래서 내가 이해한
Protocol에 대한 이점은
공통적으로 목적을 가진 함수이지만 그 중에서도 사용하지 않는 놈들은 Protocol을 넣지만 않으면
사용하지 않아도 되기 때문에
코드의 효율성이 높아지고 메모리 낭비도 줄일 수 있다
구조체, 클래스에서 사용이 가능하므로 정말 효율 좋은 녀석이다 라고 생각하자
Protocol의 기본 사용법
기본적으로 메서드를 구현해도 세부내용을 작성하지 않는다 원칙
protocol SomeProtocol { // 요구사항을 정의 (필수 능력만 정의)
func playPiano()
}
// 채택 및 구현
// 구조체에서 채택
// 반드시 프로토콜을 사용하게 되면 반드시 메서드를 구현해야함
struct MyStruct: SomeProtocol { // 이제 자격증의 능력이 생긴 것임
func playPiano() {
// 구체적인 구현
}
}
// 클래스에서 채택
class MyClass: SomeProtocol { // 이제 자격증의 능력이 생긴 것임
func playPiano() {
// 구체적인 구현
}
}
//상속시
//반드시 앞에 클래스가 와야하며 뒤에 프로토콜이 붙어야한다
//class 자식클랙스 이름 : 상위 클래스, 프로토콜 {
//}
// 프로토콜을 채택하게되면 ===> 반드시 프로토콜의 능력을 갖게 된다.
protocol SomeProtocol {
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
}
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
}
요즘 이사 준비하는데 은행에서 제대로 처리 해주지 않아
화가 많이 난 상태기 때문에
공격관련으로 지어봤습니다 하하..

이렇게 빨간색으로 오류가 나는 이유는
반드시 프로토콜을 채택하게 되면
구현을 해야하기 때문이에요

스위프트에서 픽스를 누르시면 자동으로
함수를 구현해주고
저희는 편하게 내용만 작성 하면 된답니다

Protocol의 도입
클래스와 상속의 단점을 보완
상속을 하며 속성이나 메서드를 모두 사용하는 경우면 얼마나 좋을까?
하지만 상속을 하며 사용하지 않는 속성이나 메서드들도 분명 있을것이다
그래서 우리가 이것을 사용하는 이유이기도 하다
예를 들어
class 자동차를 만들었다고 하자
거기에 자동차 부품의 속성들이 가득할것이다
근데 여러 자동차의 상속을 하다가
갑자기 자동차에 오토바이를 상속하게 된다면?.. 오잉...
버려지는 메서드나 속성들이 한가득일것이다
그래서 프로토콜을 사용하는 이유라고 생각하면 된다
Protocol 속성 요구 사항
속성은 var로만 선언이 가능하며 let으로 선언할 수 없다
get, set 키워드를 통해서 읽기 / 쓰기 여부를 설정하는데 (최소한의 요구사항으로 get만 사용해도 된다)
저장 속성/계산 속성으로 모두 구현이 가능하다
static도 사용이 가능하며 만약 상속을 가능케 하고 싶으면 class 키워드도 붙일 수 있습니다
저장/계산 속성
protocol Product {
var id: String { get }
var name: String { get set }
static var type: String { get set }
}
//인스턴스 저장/계산 속성
struct IMac: Product {
var id: String = "C0PLML"
var name: String = "아이맥"
static var type: String = "컴퓨터"
}
let imac = IMac()
imac.id
imac.name
IMac.type
제가 구매하고 싶은 위시템을 넣은건 기분탓입니다
여기서 보시면 알 수 있는 부분은 id를 get으로 설정하였지만 프로토콜이 채택된 IMac을 보시면
계산 속성을 사용 하고 있습니다
저장 타입 속성
protocol Product {
var id: String { get }
var name: String { get set }
static var type: String { get set }
}
//타입속성
class IPad: Product {
var id: String { "아이패드123453" }
var name: String { get { "ipad" } set { newValue }}
//타입 저장 속성은 (상속은 되지만) 재정의 원칙적으로 불가능하다
static var type: String = "아이패드"
}
let ipad = IPad()
ipad.id
ipad.name
IPad.type
계산 타입 속성
class IPad1: Product {
var id: String = "아이패드123453"
var name: String = "ipad"
//타입 계산 속성은 재정의가 가능하다 (class 키워드 가능)
class var type: String { get {"ipad"} set { newValue } }
}
let ipad1 = IPad1()
ipad1.id
ipad1.name
IPad1.type
예제
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let jenni = Person(fullName: "jenni lee")
class Starship: FullyNamed {
var perfix: String?
var name: String
init(perfix: String? = nil, name: String) {
self.perfix = perfix
self.name = name
}
var fullName: String {
return (perfix != nil ? perfix! + " " : "") + name
}
}
var ncc1701 = Starship(perfix: "화성갈거니까", name: "스페이스X")
print(ncc1701.fullName) //화성갈거니까 스페이스X
Protocol 메서드 요구사항
-메서드의 헤드부분(인풋/아웃풋)의 형태만 요구사항으로 정의
-mutating 키워드는 구조체로만 제한하는 것은 아니다! 구조체에서 저장 속성 변경하는 경우 구조체도 채택 가능하도록 허락하는 키워드이기 때문에 클래스에서 작성시 mutating 키워드를 작성할 필요가 없다
(mutating keword는 구조체와 열거형에서만 사용)
-타입메서드로 제한 하려면 static 키워드만 붙이면 된다 (채택해서 구현하는 곳에서 static/class 키워드 모두 사용할 수 있다)
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 {
//truncatingRemainder란?
//스위프트에서 나머지 값을 구하려면 %연산자를 사용한다
//%는 Int의 계산에만 적용이 가능하다
//truncatingRemainder는 소수점이 있는 Double, FLoat의 나머지 값을 구할 수 있다
print(lastRandom)
//계속해서 난수를 생성하기 때문에 호출 할 때마다 다른 값이 나온다
//난수 생성기 알고리즘이라고 함
lastRandom = ((lastRandom * a + c)
.truncatingRemainder(dividingBy: m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("random number1: \(generator.random())")
Protocol Mutating
mutating은 열거형이나 구조체의 값타입에서 값이 변경가능한 의미로 사용되는 함수 앞에 사용되지만
프로토콜에서도 사용이 가능하다
하지만 클래스에서 사용못하는건 아니지만
만약 사용하게 되면 mutating키워드는 날려버리자
다른 언어와 다르게 mutating이 있기 때문에 mutating 키워드를 사용해서 클래스와 구조체의 접근도
가능하다는것을 알게되었다
다시한 번 말하지만 mutating으로 정의를 하였지만 클래스에서 사용할 때는 붙이지 않는다고 한다
protocol Toggleable {
mutating func toggle()
}
enum OnOffSwitch: Toggleable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle() //.on
Protocol 생성자 요구 사항
*사실상 프로젝트에서 사용하는 경우는 드물다고 합니다*
- 클래스는(상속 고려해야함) 생성자 앞에 required를 붙여야함(하위에서 구현을 강제)
- 구조체의 경우에는 상속이 없기 때문에 required 키워드가 필요가 없다
-final을 붙여서 상속을 막으면 required가 생략될 수 있다
-클래스에서는 반드시 지정생성자로 구현할 필요가 없다(편의 생성자로 구현도 가능)
protocol SomeProtocol { // 생성자를 요구사항으로 지정 가능
init(num: Int)
}
// 예제 - 1 ======================
class SomeClass: SomeProtocol {
required init(num: Int) {
// 실제 구현
}
}
class SomeSubClass: SomeClass {
// 하위 클래스에서 생성자 구현 안하면 필수 생성자는 자동 상속
// required init(num: Int)
}
// 예제 - 2 ======================
protocol AProtocol {
init()
}
class ASuperClass {
init() {
// 생성자의 내용 구현
}
}
class ASubClass: ASuperClass, AProtocol {
// AProtocol을 채택함으로 "required" 키워드 필요하고, 상속으로 인한 "override(재정의)" 재정의 키워드도 필요
required override init() {
// 생성자의 내용 구현
}
}
위 코드에서 보다시피
프로토콜에서 생성자를 만들 수 있고
클래스에서 final로 구현할 때, 구조체에서는 requred를 붙일 필요가 없고
그 외 반드시 required가 붙어야한다
그리고 반드시 지정생성자로 구현하지 않아도 편의 생성자로도 구현해도 된다고 한다
실패 가능 생성자
init?() => init() / init?() / init!() 구현이 가능
inint() => init?() 구현할 수 없음 이유는 범위가 더 넓어지는건 안되기 때문이다
// 실패가능 생성자
protocol AProto {
init?(num: Int) // (객체의 타입이 맞을까?) AClass? <==== AClass은 범위가 속해있음
}
// 구조체에서 채택 (required 키워드는 필요없음)
struct AStruct: AProto { // Failable/Non-failable 모두 요구사항을 충족시킴
//init?(num: Int) {}
init(num: Int) {}
//init!(num: Int) {} // 이것도 괜찮음
}
// 클래스에서 채택
class AClass: AProto {
required init(num: Int) {}
}
참조 - 엘런 강의 및 apple 공식 문서(영문판)