iOS/Swift

[Swift] protocol에 대하여(개념 정리)

유훈 | Yuhun 2022. 6. 30. 23:49
반응형

프로토콜을 다시 보면서 제대로 사용해야겠다는 생각이 들어 요점을 정리해보려 합니다.

iOS개발을 하면서 프로토콜은 정말 많이 사용됩니다. 특히 delegate 패턴에서도 아주 많이 보이는 유형입니다.
자바를 해보신 분이라면 스위프트의 프로토콜은 자바의 인터페이스와 아주 유사하다고 할 수 있겠습니다.

프로토콜 protocol

프로토콜을 만들고 정의하는 기본 틀입니다.

protocol <이름> {
	// 명세들
}

프로토콜을 구현할 수 있는 구현체는 다음과 같습니다.

  1. 구조체 2. 클래스 3. 열거형 4. 익스텐션
struct,class,enum,extension 객체:protocol {}

프로토콜 프로퍼티

저장 프로퍼티와 연산 프로퍼티 프로토콜 (차이점 주의)

다음은 프로토콜에서 프로퍼티를 명세하는 부분인데 get과 set을 적절히 사용해 주어야합니다.

protocol namePro {
	var lastName:String {get set} // 저장 프로퍼티는 둘 다 필수
	var firstName:String { get } // 연산 프로퍼티는 get 만 있어도 된다.
}

프로토콜 메소드

  • 선언 뒤에 중괄호 없음 주의!
  • 외부 매개변수명은 그대로 따라야함(내부는 임의로 설정 가능)
protocol exp {
	func a(cmd: String)
	func b(c:Int) -> String // 명세를 구현할 때는 c라는 매개변수를 그대로 써야합니다.
}

// 만약 c가 아닌 다른 것을 사용하고 싶다면 내부 매개변수는 바꿔줄 수 있습니다.
func b(c d:Int) -> Stirng {} // 이런 구현은 가능

프로토콜에서 mutating을 사용하지 않을 때는 다음과 같다.

  • 구조체나 열거형 등 값 타입의 객체에서 내부 프로퍼티의 값을 변경하기를 원치 않을 때
  • 주로 클래스의 대상으로 간주하고 작성된 프로토콜일 때

위의 설명을 반대로 말하면 mutating은 struct에서 내부 프로퍼티를 변경할 때 꼭 써줘야한다라고 할 수 있습니다.

(protocol의 func 앞과 구현할때의 func 앞 모두)

protocol A {
	mutating func ex(param:String) // mutating 작성 필요
}

struct AA: A {
	var parameter:String?
	
	mutating func ex(param:String) {
		self.parameter = param // 이 코드가 없으면 mutating 생략 가능
		print(parameter)
	}
}

타입 프로퍼티나 타입 메서드 작성시에는 static을 붙이면 된다

mutating에 대해 class는 조금 다르다.

참조타입은 내부 프로퍼티와 상관없이 mutating이 필요없다.
(struct에서 값 수정에 mutating을 쓰고 class에선 신경쓰지 않는 맥락과 같네요.)

프로토콜 초기와 메소드

주의사항

  1. 구현되는 초기화 메소드의 이름과 매개변수명은 프로토콜의 명세에 작성된 것과 완전 일치 필요
  2. 프로토콜 명세에 선언된 초기화 메소드는 기본 제공되는 초기화 메소드라도 구현해야 함
  3. 클래스에서 초기와 메소드를 구현할 때는 required 키워드 필수(init에서만 사용!!)
// 프로토콜
protocol A {
	init()
	init(cmd:String)
}
// 구조체
struct St:A {
	var cmd:String

	init(){
		self.cmd = "start"
	}

	init(cmd:String){
		self.cmd = cmd
	}
}
//클래스

class Cl:A {
	var cmd:String

	required init() {
		self.cmd = "start"
	}

    required init(cmd:String) {
		self.cmd = cmd
	}
}

클래스에서 주의점 - 상속과 프로토콜 구현이 모두 이루어질 때

→ 부모 클래스에서 구현되었더라고 프로토콜에 있다면 ovveride 해주어야 함

→ 즉 override required init() {} 이 필요 (순서는 상관X)

** 상속은 프로토콜보다 먼저 선언!(상속은 단일 프로토콜은 다중이니까)

A: 상속,프로토콜1,프로토콜2...

타입으로서 프로토콜(delegate에 사용됨으로 잘 알아두자)

프로토콜 자체로는 인스턴스를 만들 수 없고 할 수있는 일도 없다. 하지만 자료형으로도 받을 수 있는데 사용은 예를 들면 프로토콜을 상위 클래스 타입으로 간주해 사용하는 것과 유사하다.

  1. 상수나 변수, 프로퍼티 타입으로 사용할 수 있다.
  2. 함수 메소드 또는 초기화 구문에서 매개변수 타입이나 반환 타입으로 프로토콜을 사용할 수 있다.
  3. 배열이나 사전 혹은 다른 컨테이너의 타입으로 사용할 수 있다.

3을 이용해 프로토콜 이외의 프로퍼티나 메소드를 은닉이 가능하다.

protocol A {
    func a()
    func b()
}

class exC: A {
    var boolType = false
    
    func a() {
        self.c()
    }
    func b() {
        self.d()
    }
    
    func c(){
        self.boolType = true
    }
    func d() {
        self.boolType = false
    }
}

// 이렇게 상위 클래스로 간주해 대입
let ex:A = exC()
// 사용할 때는 아래만 사용 가능하다. (은닉)
ex.a()
ex.b()

프로토콜을 두 개 채택하고 이를 모두 포함하는 객체 타입이 가능..!

// 두 개의 프로토콜을 채택한 클래스가 있다고 하자
protocol A {}
protocol B {}
class C: A,B {}

// 다음과 같이 사용함으로 A,B에 구현된 것만 사용을 제약할 수 있다..!!!!
let ex: A & B = C()

프로토콜의 활용

  • 객체에 프로토콜 추가 -> (tableView delegation 등에서 많이 사용했었다.)
extension <기존 객체>: <구현할 프로토콜> {
	// 프로토콜 구현 내용
}

프로토콜 상속

  • 프로토콜은 상속이 가능하다 → 여러개의 프로토콜을 하나로 합치는 것도 가능하다.
protocol A {
    func a()
}

protocol B {
    func b()
}

protocol C: A,B {
    func c()
    // 사실은 doA, doB, doC가 다 있는 상태라 할 수 있다.
}

class ABC: C {
    func a() {
    }
    func b() {
    }
    func c() {
    }
}

// 이는 프로토콜을 상위 클래스로 생각해 제한 가능
let abc:C = ABC() // 전부 활성
let a:A = ABC() // doA()만 활성

func ff(abc:C) {}
ff(abc: ABC())

→ 만약 프로토콜을 이용해 캐스팅한다면 다운캐스팅에서는 as? 같은 것으로 잘 처리할 것

클래스 전용 프로토콜

→ 클래스만 구현할 수 있도록 제한된 프로토콜

만약 다른 프로토콜 상속시 class를 가장 먼저 써야함

protocol A: class {}

프로토콜 옵셔널

optional 키워드를 이용해 구현을 필수가 아니게 할 수 있다. (이거 자바에서 EventAdaptor 에 사용된 내용이랑 비슷하군)

주의 - @objc가 필요함 (Objective-C 코드에서 참조할 수 있도록 한 것)

import Foundation

@objc
protocol optionalProtocol {
	@objc optional func onReceive() {}
}

→ 사용시 옵셔널 체이닝으로 onReceive?()로 사용해야 하고 반환값도 옵셔널임의 주의

반응형