프로토콜에서 정의 가능한 메서드의 종류와 형태는 다양합니다. (인스턴스 메서드, 생성자, 서브 스크립트 등... / 파라미터의 유무, 리턴형의 유무 등...)
메서드의 종류와 형태에 따라 프로토콜에서 정의하는 방법이 다르며, 채택(Adopt) 후 사용하는 방법과 기능이 조금씩 다를 수 있습니다.
✅ 일반적인 인스턴스 메서드(Instance Method) 정의
- 일반적인 인스턴스 메서드를 정의할 때는 "메서드명", "리턴(반환) 타입", "파라미터명", "파라미터 타입"만을 작성합니다.
- 메서드의 기능은 구현하지 않습니다. 즉, 중괄호 { } 을 작성하지 않습니다.
protocol ProtocolMethod{ func test1() // 프로토콜 + 리턴값이 없는 경우 func test2() -> String // 프로토콜 없이 값만 리턴하는 경우 func test3(x: String) -> String // 프로토콜을 받아 값을 리턴하는 경우 } class kim: ProtocolMethod{ func test1() { print("프로토콜 + 리턴값이 없는 경우") } func test2() -> String { return "프로토콜 없이 값을 리턴하는 경우" } func test3(x: String) -> String { return "\(x) 프로토콜을 받아 값을 리턴하는 경우" } }
✅ "mutating" 키워드가 들어간 인스턴스 메서드(Instance Method) 정의
- 클래스는 함수(메서드)를 사용하여 저장 속성의 값을 변경할 때는 큰 문제가 없습니다. (데이터를 메모리의 힙 영역에 저장하기 때문 = Reference Type)
- 구조체는 함수(메서드)를 사용하여 저장 속성의 값을 변경하는 것이 불가능합니다. 때문에 mutating 키워드를 사용해야 합니다. (데이터를 메모리의 스택 영역에 저장하기 때문 = Value Type)
- 프로토콜에서 "mutating" 키워드가 들어간 메서드가 정의되었을 때, 클래스는 해당 키워드를 생략할 수 있습니다.
(잘 생각해보면... 당연한...)protocol ProtocolMethod{ mutating func test1(x: Int) } class kim: ProtocolMethod{ var a: Int = 0 func test1(x: Int) { // 클래스는 mutating 키워드 없이 작동 self.a = x } } struct lee: ProtocolMethod{ var a: Int = 0 mutating func test1(x: Int) { // 구조체는 mutating 키워드 사용! self.a = x } }
✅ 타입 메서드(Type Method) 정의
- 프로토콜에서 타입 메서드를 정의할 때는 "static" 사용해야 합니다. ("class" 키워드 사용 X)
- "static" 키워드로 선언했지만, 사용 시 상황에 따라 "static" 또는 "class"로 사용이 가능합니다.
protocol ProtocolMethod{ static func test1() static func test2() -> String static func test3(x: String) -> String } class kim: ProtocolMethod{ static func test1() { print("프로토콜 + 리턴값이 없는 경우") } class func test2() -> String { // static 키워드로 정의했지만, class 타입으로 사용 가능! return "프로토콜 없이 값을 리턴하는 경우" } static func test3(x: String) -> String { return "\(x) 프로토콜을 받아 값을 리턴하는 경우" } }
✅ 지정 생성자(Designated Initializer) 정의
- 클래스는 지정 생성자가 정의된 프로토콜을 채택하여 사용할 때는 필수 생성자(Required Initializer)로 정의하여 사용해야 합니다. (프로토콜의 요구사항은 필수이기 때문)
- 구조체는 필수 생성자가 없기 때문(구조체는 상속 기능이 없기때문)에 일반적인 지정 생성자로 정의하여 사용할 수 있습니다.
protocol ProtocolMethod{ init(x: Int) } class kim: ProtocolMethod{ //클래스는 생성자 기능이 있는 프로토콜을 채택할 때 필수 생성자로 정의하여 사용해야 합니다. required init(x: Int){ //code } } struct lee: ProtocolMethod{ init(x: Int) { //code } }
✅ 편의 생성자(Convenience Initializer)
- 프로토콜에서 편의 생성자를 정의하는 것은 불가능합니다.
🚨 Convenience initializer not allowed in non-class type 'ProtocolMethod'
✅ 필수 생성자(Required Initializer)
- 프로토콜에서 필수 생성자를 정의하는 것은 불가능합니다.
🚨 'required' initializer in non-class type 'ProtocolMethod'
✅ 실패가능 생성자(Failable Initializer)
- 클래스 또는 구조체는 실패가능 생성자가 정의된 프로토콜을 채택하여 사용할 때 지정 생성자 또는 실패가능 생성자로 정의하여 사용할 수 있습니다. (옵셔널 타입이 일반 타입보다 큰 개념이기 때문에 가능)
protocol ProtocolMethod{ init?(x: Int) } class kim1: ProtocolMethod{ required init?(x: Int){ // ? / ! 둘 다 가능 //code } } class kim2: ProtocolMethod{ required init(x: Int){ //code } } struct lee1: ProtocolMethod{ init!(x: Int) { // ? / ! 둘 다 가능 //code } } struct lee2: ProtocolMethod{ init(x: Int) { //code } }
프로토콜에서 서브스크립트를 정의할 때는 get만을 사용하여 읽기 전용(read only)으로 정의하여 사용하거나 get과 set을 둘 다 사용하여 읽기/쓰기(read/write)용으로 정의해야 합니다.
✅ 서브스크립트(Subscripts) 정의
- 프로토콜에서 서브스크립트를 get(읽기) 전용으로 정의하여 사용할 때 get(읽기) 또는 get/set(읽기/쓰기)으로 정의할 수 있습니다.
- 프로토콜에서 서브스크립트를 get/set(읽기/쓰기)용으로 정의하여 사용할 때 get/set(읽기/쓰기)으로만 정의할 수 있습니다.
protocol ProtocolMethod1{ subscript (index1: Int) -> Int{ get } } protocol ProtocolMethod2{ subscript (index2: Int) -> Int{ get set } } class kim: ProtocolMethod1{ var arrNum = [1,2,3,4,5] subscript(index1: Int) -> Int { get{ return arrNum[index1] } set{ //set(쓰기) 생략 가능 arrNum[index1] = newValue } } } class lee: ProtocolMethod2{ var arrNum = [1,2,3,4,5] subscript(index2: Int) -> Int { get{ return arrNum[index2] } set{ //set(쓰기) 생략 불가능, 필수 작성 arrNum[index2] = newValue } } }
참고자료: 앨런 Swift문법 마스터 스쿨