Swift 문법 CH.8

김성환·2020년 9월 7일
0

swift문법

목록 보기
11/11

안녕하세요. 본 포스트에서는 Swift의 프로토콜에 대해 설명하겠습니다.

※ 참고자료 : 꼼꼼한 재은 씨의 Swift:문법편

목차

  • 1. 프로토콜의 정의
  • 2. 타입으로서의 프로토콜
  • 3. 프로토콜의 활용

1. 프로토콜의 정의

프로토콜이란?

클래스나 구조체가 어떤 기준을 만족하거나 또는 특수한 목적을 달성하기 위해 구현해야 하는 메소드와 프로퍼티의 목록
자바에서 사용하는 인터페이스와 비슷한 개념

프로토콜의 사용

IOS는 특정 컨트롤에서 발생하는 각종 이벤트를 효율적으로 관리하기 위해 대리자(델리게이션)을 지정하여 이벤트 처리를 위임하고, 실제로 이벤트가 발생하면 위임된 대리자가 콜백 메소드를 호출해주는 델리게이션 패턴을 많이 사용하는데 이 패턴을 구현하기 위해 이용되는 것이 바로 프로토콜이다.

프로토콜의 역활

특정 기능이나 속성에 대한 설계도
즉, 프로토콜은 구체적인 내용이 없는 프로퍼티나 메소드의 단순한 선언 형태로 구성됨.
이를 프로토콜의 명세라 부른다.
또한 명세에 맞춰 실질적인 내용을 작성하는 것을 프로토콜을 구현한다 고 한다.

프로토콜의 정의

프로토콜은 protocol 이라는 키워드를 사용해 정의한다.

protocol 프로토콜이름 {
    프로퍼티 명세1
    프로퍼티 명세2
    ....
    메소드 명세1
    메소드 명세2
    ...
}

프로토콜을 구현할 수 있는 구현체

  • 구조체
  • 클래스
  • 열거형
  • 익스텐션
struct/class/enum/extension 객체이름 : 구현할 프로토콜이름 {
}

위와 같이 :(콜론)을 이용해 구현할 수 있다.

프로토콜의 프로퍼티

프로토콜에 선언되는 프로퍼티는 읽기 전용인지 읽고 쓰기 전용인지에 대해 정의해야 한다.

protocol 프로토콜이름{
    var 프로퍼티이름 : 타입 { get set } // 일고 쓰기 전용
    var 프로토콜이름 { get set } // 일고 쓰기 전용
    var 프로토콜이름 { get } // 읽기 전용
}

연산 프로퍼티와 저장 프로퍼티

구현하려는 것이 연산 프로퍼티일 경우 get,set을 모두 적용하는 읽고 쓰기 전용과 get만 적용하는 읽기 전용 모두 가능하지만, 저장 프로퍼티인 경우 get, set모두 적용하는 읽고 쓰기 전용만 가능하다.

  • 연산 프로퍼티는 모두 가능
  • 저장 프로퍼티는 읽고 쓰기 전용만 가능

프로토콜의 메소드

프로토콜에 선언되는 메소드는 일반 메소드의 선언과 같다.

protocol 프로토콜이름{
    func 함수이름(외부레이블 내부레이블: 타입) -> 반환타입
    func 함수이름(내부레이블: 타입) -> 반환타입
    func 함수이름() -> 반환타입
    func 함수이름()
}

프로토콜 메소드를 정의할때 반환타입이 없거나 인자값이 없을 경우 생략하면 된다.
이때, 인자에 외부 레이블도 생략이 가능하다. 하지만 프로토콜을 구현하려는 구현체에서 메소드를 구현하기 위해서는 외부레이블(외부레이블이 없을경우에는 내부레이블)의 이름을 똑같이 사용해야 한다.
아래는 예시이다.

protocol example{
    var a1 : String { get set } // a1은 연산 프로퍼티
    var a2 : String { get set } // a2는 저장 프로퍼티
    var a3 : String { get } // a3는 연산 프로퍼티(읽기 전용)
    func printFunc()
    func printTime(A1:String,A2 aa: Int)
}
class imple : example {
    var a1 : String{
        get{
            return self.a2
        }
        set(v){
            self.a2 = v;
        }
    }
    var a2 = "안녕하세요!"
    var a3 : String{
        return self.a1 + self.a2
    }
    func printFunc(){
        print(self.a1 + self.a3)
    }
    func printTime(A1:String,A2 aa: Int) -> (){ // 프로토콜의 인자 레이블과 같다.
        for _ in 0...aa{ // 내부 레이블인 aa를 쓰는 모습
            print(A1)
        }
    }
}
var test = imple()
test.printFunc()
test.a1 = "안돼"
test.printFunc()
test.printTime(A1:"안녕!!",A2:5)
// 실행결과
안녕하세요!안녕하세요!안녕하세요!
안돼안돼안돼
안녕!!
안녕!!
안녕!!
안녕!!
안녕!!
안녕!!

프로토콜의 타입 프로퍼티와 타입 메소드

static을 붙여서 정의가 가능하다.
하지만 구현시에도 static을 붙여야함.
아래는 예시이다.

protocol A{
    static var a:String{get set} // static으로 정의된 저장 프로퍼티
    static func prints() // static으로 정의된 prints함수
}
class f : A {
    static var a = "hello"
    static func prints() -> (){
        print(self.a)
    }
}
print(f.a)
f.prints()
// 실행결과
"hello"
"hello"

프로토콜의 초기화 메소드

프로토콜에 초기화 메소드도 정의가 가능하다.
정의는 init 키워드를 이용해 정의하면 된다.
이때, 기본 초기화 메소드 구문도 정의할 수 있다.
만약 프로토콜에 기본 초기화 메소드가 정의되어 있다면 기본 초기화 메소드도 구현을 해야한다.

protocol A{
    init()
    init(cmd: String)
}
struct B :A {
    var a :String
    init(){
        self.a = "hello"
    }
    init(cmd: String){
        self.a = cmd
    }
}
class C : A{
    var a :String
    required init(){
        self.a = "hello"
    }
    required init(cmd: String){
        self.a = cmd
    }
}

위의 예시를 보면 구조체와 클래스가 다르게 정의된 것을 확인할 수 있다. 바로 required의 유무인데 클래스의 경우 초기화 구문을 구현할 경우 required 키워드를 초기화 구문에 붙여줘야한다.
이때, 초기화 구문을 오버라이드 하는 경우가 있을 수 있는데 이때는 overriderequired 키워드 모두를 붙여야 한다.

protocol Init{
    init()
}
class Parent{
    init(){
    }
}
class Child : Parent,Init{
    override required init(){
    }
}

여기서 주의해햐 할 것은 하나의 클래스는 여러개의 프로토콜을 동시에 구현이 가능하지만 상속은 단일 상속만 가능하다. 그렇기 때문에 :(클론) 바로 뒤에는 상속될 객체가 먼저 오고 그 뒤에 구현할 프로토콜이 온다.
즉, class 클래스명 : 부모클래스명, 프로토콜명,프로토콜명,....

2. 타입으로서의 프로토콜

타입으로 사용하는 프로토콜

프로토콜을 타입으로 사용이 가능하다.
단, 프로토콜을 구현한 클래스에 한해서 가능하다.
즉, 특정 프로토콜을 구현한 클래스의 인스턴스가 그 프로토콜의 타입으로 생성할 수 있다는 뜻이다.

protocol Wheel{
    func spin()
    func hold()
}
class Bicycle : Wheel{
    var moveState = false
    func spin(){
        self.pedal()
    }
    func hold(){
        self.pullBreak()
    }
    func pedal(){
        self.moveState = true
    }
    func pullBreak(){
        self.moveState = false
    }
}
let trans1 = Bicycle() // Bicycle타입의 trans1 상수 생성
let trans2 : Wheel = Bicycle() // Wheel타입의 trans2 상수 생성

trans1은 Bicycle타입이다. 따라서 trans1이 갖는 것은 spin,hold,pedal,pullBreak,moveState이다. 또한 Bicycle타입이기 때문에 모두 접근이 가능하다.
하지만 trans2는 Wheel타입이다. 또한 Bicycle의 인스턴스를 갖고있다. 하지만 Wheel타입이기 때문에 trans2는 spin과 hold만 접근이 가능하다.
이것은 객체지향에서 배웠듯 상위 타입으로 선언해 접근하는 것과 같은 것이다.

  • 2개이상의 프로토콜을 구현하고 그 구현된 것들을 묶어서 타입으로도 사용이 가능하다.
    & 을 사용하면 된다.
    아래는 예시이다.
protocol A{
	...
}
protocol B{
	...
}
class C : A,B{
	...
}
var d : A&B = C() // A와B를 묶은 타입

3. 프로토콜의 활용

확장구문과 프로토콜

확장구문을 이용해 프로토콜을 구현할 수도 있다.
즉, 기존 클래스나 구조체에 프로토콜을 구현할 수 있다는 뜻이다.

extension 기존객체 : 구현할 프로토콜1,구현할 프로토콜2,...{
    프로토콜 구현 내용
}

프로토콜의 상속

클래스의 상속과는 다르게 프로토콜의 상속은 다중상속이 가능하다.
즉, 프로토콜 끼리는 여러개의 상속이 가능하다는 뜻이다.
프로토콜의 상속도 클래스와 같이 :(클론)을 사용하여 상속한다.

protocol A{
    func doA()
}
protocol B{
    func doB()
}
protocol C : A,B{
    func doC()
}
// A와 B의 상속을 받은 프로토콜 C의 모습
protocol C {
    func doA()
    func doB()
    func doC()
}

단, 프로토콜에서 선언이 겹치더라도 오버라이드를 안붙여도 된다.

타입 연산자 사용가능

  • is 는 타입 비교연산자
  • as 는 일반 캐스팅 연산자

클래스 전용 프로토콜

클래스만 구현할 수 있도록 제한 된 프로토콜을 정의 할 수도 있다.
이 경우 class 키워드를 사용하면 된다.
이때, 상속될 것들은 뒤로가고 항상 class키워드가 맨 앞에 와야한다.

protocol 프로토콜 이름 : class, 상속할 프로토콜1, ... {
    // 구현 내용 작성
}

Optional 키워드

프로토콜을 구현할 때는 기본적으로 프로토콜의 명세에 포함된 모든 프로퍼티, 메소드, 초기화 구문을 모두 구현해야 한다. 하지만 특별히 필요하지 않은 프로퍼티, 메소드, 초기화 구문이 있을 경우가 있게 될 건데, 이를 구현하지 않고 프로토콜을 구현하는 방법이 있다.
바로 optional 키워드를 사용하면 된다.
다시 말해, optional 키워드는 프로토콜을 구현할 때 필수적으로 구현해야 할 것들을 선택적으로 바꿔준다는 것이다.
즉, optional 키워드 붙은 것은 구현 안해도 된다는 뜻이다.

사용방법

optional 키워드를 사용하려면 @objc를 프로토콜 앞에 붙여야 한다.
@objc를 사용하기 위해서는 파운데이션 프레임워크를 참조해야 하므로 import Foundation을 해야한다.(이건 swift 기본편에서 다룰 것)

import Foundation
@objc // 프로토콜 정의 전에 도 붙이고
protocol 프로토콜 이름 {
    @objc optional var 명세 // 각각의 명세에도 붙인다.
    @objc optional func 명세
}
profile
개발자가 되고 싶다

0개의 댓글