[Swift] 프로토콜 (Protocol)

2025. 2. 6. 13:52·iOS/Swift

안녕하세요, iOS 개발하는 루피입니다!

 

오늘은 Swift 프로토콜에 대해 정리해 보도록 하겠습니다.

 

바로 시작합니다.


1. 프로토콜이란?

특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다.

쉽게 생각하면 프로토콜이란 일종의 약속입니다.

 

프로토콜은 직접 구현하지 않고 약속만 합니다.

너는 나를 채택하면 이런 변수를 가지고 있어야!

너는 나를 채택하면 이런 메서드를 가지고 있어야 해!라고 약속을 하는 건데요.

 

이때 약속에 대한 직접 구현은 구조체, 클래스, 열거형 등을 통해 이루어져야 합니다. 

 

즉, 프로토콜은 정의를 하고 제시를 할 뿐이지 스스로 기능을 구현하지는 않습니다!!!

 

우선 프로토콜을 사용하는 방식에 대해서 먼저 살펴 보도록 하겠습니다.

1)  프로토콜 선언

protocol 프로토콜 이름 { 프로토콜 정의 }

 

2) 프로토콜 채택

struct SomeStruct: AProtocol, AnotherProtocol { 구조체 정의 }
class SomeClass: AProtocol, AnotherProtocol { 클래스 정의 }
enum SomeEnum: AProtocol, AnotherProtocol { 열거형 정의 }

위와 같이 콜론( : ) 뒤에 프로토콜 이름을 적어 프로토콜을 채택합니다. 이때, 채택할 프로토콜이 여러 개라면 쉼표( , )로 구분하여 명시해 줍니다.

 

근데 궁금한 점이 생기지 않으신가요? 아니 Strcut 랑 Enum은 상속을 받지 않으니깐 채택할 프로토콜만 적어주면 된다도 치자... 근데 Class는? 상속할 Class도 있을 수 있잖아???? 

 

맞습니다! 바로 코드로 보시죠

class SomeClass: SuperClass, AProtocol, AnotherProtocol { 클래스 정의 }

위 처럼 SomClass가 SuperClass를 상속하는 동시에 프로토콜을 채택해야 하는 경우 상속받는 Class를 먼저 적어주고, 다음으로 Protocol을 적어 주면 됩니다.

 

자 그러면 본격적으로 프로토콜의 가장 기본적인 형태를 적어보겠습니다.

//프로토콜로 약속
protocol Naming {
    //우리는 이런 변수를 가지고 있을거야!! 라고 약속
    var name : String {get set}
    
    //우리는 이런 메소드를 가지고 있을거야!! 라고 약속
    func getName() -> String
}

struct Friend : Naming{
    var name: String
    func getName() -> String {
        return "내 친구: " + self.name
    }
}
var myFriend = Friend(name: "조로")
myFriend.getName()

 

코드만 보고 이해가 가시나요??? 코드를 설명하기 전에 프로토콜의 요구사항에 대해 좀 더 살펴 보도록 하겠습니다.


2. 프로토콜 요구사항

프로토콜은 타입이 특정 기능을 실행하기 위해 필요한 기능을 요구합니다. 프로토콜이 자신을 채택한 타입에 요구하는 사항은 프로퍼티나 메서드와 같은 기능들입니다.

1) 프로퍼티 요구

프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있습니다!

 

그렇지만 프로토콜은 그 프로퍼티의 종류 (연산 프로퍼티인지? 저장 프로퍼티인지? 등)는 따로 신경 쓰지 않습니다.

그냥 프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현해주면 됩니다.

 

다만 프로퍼티를 읽기 전용으로 할지 혹은 읽고 쓰기가 모두 가능하게 할지는 프로토콜이 정해야 합니다.

 

만약, 프로토콜이 읽고 쓰기가 가능한 프로퍼티를 요구한다.

  • 읽기만 가능한 상수 저장 프로퍼티를 구현할 수 없습니다.
  • 읽기 전용 연산 프로퍼티를 구현할 수 없습니다.

만약, 프로토콜이 읽기 가능한 프로퍼티를 요구한다.

  • 상수 저장 프로퍼티를 구현할 수 있습니다.
  • 읽기 전용 연산 프로퍼티를 포함 어떤식으로든 프로퍼티를 구현할 수 있습니다.

만약 프로토콜이 쓰기만 가능한 프로퍼티를 요구한다 -> 그런 건 없으니깐 생략하겠습니다!

 

프로토콜의 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의합니다.

 

타입 프로퍼티를 요구하려면 static 키워드를 사용합니다.

클래스의 타입 프로퍼티에는 class 타입 프로퍼티와 static프로퍼티가 있습니다만 이 두 타입 프로퍼티를 따로 구분하지 않고 모두 static 키워드를 사용하여 타입 프로퍼티를 요구하면 됩니다.

protocol SomeProtocol {
	var settableProperty: String { get set }
    var notNeedToBeSettableProperty: String { get }
}

protocol AnotherProtocol {
	static var someTypeProperty: Int { get set }
   	static var anotherTypeProperty: Int { get }
}

2) 메서드 요구

프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수도 있습니다!

 

타입 메서드를 요구할 때는 타입 프로퍼티 요구와 마찬가지로 앞에 static 키워드를 명시합니다. static 키워드를 사용하여 요구한 타입 메서드를 클래스에서 실제 구현할 때는 static 키워드나 class 키워드 어느 쪽을 사용해도 무방합니다.

 

즉, 프로토콜에서 static 키워드를 통해 타입 메서드를 요구하더라도 클래스에서 실제로 구현할 때 class 타입 메서드로 구현할지, static 타입 메서드로 구현할지는 프로토콜을 채택하여 사용하는 클래스의 특성에 따라 골라 사용해 주면 됩니다.

protocol SomeProtocol {
    static func someMethod()
}

class SomeClass: SomeProtocol {
    static func someMethod() {
        print("static function")
    }
}

class AnotherClass: SomeProtocol {
    class func someMethod() {
        print("class function")
    }
}

3) 가변 메서드 요구

가끔은 메서드가 인스턴스 내부의 값을 변경할 필요가 있습니다. 이때 값 타입(구조체와 열거형)의 인스턴스 메서드에서 자신 내부의 값을 변경하고자 할 때는 메서드의 func 키워드 앞에 mutating 키워드를 적어 메서드에서 인스턴스 내부의 값을 변경 할수 있도록 할 수 있습니다. 당연히 class 같은 참조 타입의 인스턴스 메서드일 경우는 mutating 키워드를 사용하지 않아도 가능합니다.

protocol Resettable {
	mutating func reset()
}

class Person: Resettable {
	var name: String?
    var age: Int?
    
    func reset() {
    	self.name = nil
        self.age = nil
    }
}

struct Point: Resettable {
	var x: Int = 0
    var y: Int = 0
    
    mutating func reset() {
    	self.x = 0
        self.y = 0
    }
}

enum Direction: Resettable {
	case east, west, south, north, unknown
    
    mutating func reset() {
    	self = Direction.unknown
    }
}

4) 이니셜라이저 요구

프로토콜은 프로퍼티, 메서드 등과 마찬가지로 특정한 이니셜라이저를 요구할 수도 있습니다.

프로토콜에서 이니셜라이저를 요구하려면 메서드 요구와 마찬가지로 이니셜라이저를 정의하지만 구현은 하지 않습니다.

 

프로토콜을 구현하고, 요구사항을 반영하는 케이스를 보겠습니다.

protocol Named {
    var name: String { get }
    init(name: String)
}

A) Struct 일 경우

struct Pet: Named {
    var name: String
    init(name: String) {
        self.name = name
    }
}

구조체는 상속할 수 없기 때문에 이니셜라이저 요구에 대해 크게 신경 쓸 필요 없습니다.

B) Class 일 경우

class SampleClass : Named {
    var name: String
    required init(name: String) {
        self.name = name
    }
}

이니셜라이저 요구에 부합하는 이니셜라이저를 구현할 때는 required 식별자를 붙인 요구 이니셜라이저로 구현해야 합니다. 왜냐하면 상속받는 클래스에 해당 이니셜라이저를 모두 구현해야 한다는 뜻입니다.

C) 상속 불가능 Class 일 경우

final class FinalClass: Named {
    var name: String
    init(name: String) {
        self.name = name
    }
}

final 클래스라면 클래스 자체가 상속받을 수 없기 때문에 required 식별자를 붙여줄 필요가 없습니다.

상속할 수 없는 클래스의 요청 이니셜라이저 구현은 무의미하기 때문입니다.

D) 다른 Class를 상속 받으면서 요구를 재정의할 경우

class Person {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Developer : Person, Named {
    required override init(name: String) {
        super.init(name: name)
    }
}

Person 클래스에서 상속받은 init(name:) 이니셜라이저를 재정의해야 하며, 동시에 Named 프로토콜의 이니셜라이저 요구도 충족시켜주어야 합니다. 이때는 override와 required 식별자를 모두 표기해야 합니다.

3. 프로토콜의 선택적 요구

프로토콜의 요구사항 중 일부를 선택적 요구사항으로 지정할 수 있습니다. 이때, 선택적 요구사항을 정의하고 싶은 프로토콜은 objc 속성이 부여된 프로토콜이어야 합니다. 

 

(objc 속성이 부여된 프로토콜은 Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있기에 열거형이나, 구조체 등에서는 objc 속성이 부여된 프로토콜은 아예 채택이 불가능합니다.)

import Foundation

@objc protocol Moveable {
    func walk()
    @objc optional func fly()
}

class Tiger: Moveable {
    func walk() {
        print("Walk")
    }
}

class Bird: Moveable {
    func walk() {
        print("Walk")
    }

    func fly() {
        print("Fly")
    }
}

let tiger: Tiger = Tiger()
let bird: Bird = Bird()

var movableInstance: Moveable = tiger

movableInstance.walk()
movableInstance.fly?()

movableInstance = bird

movableInstance.walk()
movableInstance.fly?()

// Walk
// Walk
// Fly

선택적 요구를 하면 프로토콜을 준수하는 타입에 해당 요구사항을 필수로 구현할 필요가 없습니다.

선택적 요구사항은 optional 식별자를 요구사항의 정의 앞에 붙여주면 됩니다.

 

이때, 메서드나 프로퍼티를 선택적 요구사항으로 요구하게 되면 그 요구사항의 타입은 자동적으로 옵셔널이 됩니다.


오늘도 화이팅입니다!

'iOS > Swift' 카테고리의 다른 글

[Swift] GCD를 공부해봅시다.  (0) 2025.02.18
[Swift]DispatchObject  (0) 2025.02.13
[Swift] Where절  (0) 2025.02.05
[Swift] 제네릭(Generics)  (1) 2025.02.04
[Swift] ARC  (0) 2025.01.28
'iOS/Swift' 카테고리의 다른 글
  • [Swift] GCD를 공부해봅시다.
  • [Swift]DispatchObject
  • [Swift] Where절
  • [Swift] 제네릭(Generics)
kimsangjunzzang
kimsangjunzzang
루피 님의 블로그 입니다.
  • kimsangjunzzang
    루피 님의 블로그
    kimsangjunzzang
  • 전체
    오늘
    어제
    • 분류 전체보기 (123) N
      • iOS (64) N
        • Swift (35) N
        • UIKit (9)
        • SwiftUI (8)
        • RxSwift (12)
      • FE (8)
        • 모던 자바스크립트 (3)
        • HTML (5)
      • CS (13) N
      • Algorithm (20)
      • 트러블 슈팅 (5)
      • 그 외 정리 (2) N
      • 대외활동 & 회고록 (5)
      • 일간 회고록(TIL) (2) N
      • 바로 안 나오면 모르는거다 (4)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    회고
    ios
    백준
    SwiftUI
    HTML
    boj
    CS
    rxswift
    uikit
    알고리즘
    Algorithm
    ViewController
    C++
    프로그래머스
    Concurrency
    회고록
    디자인 패턴
    swift
    web
    AppleDeveloperAcademy
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
kimsangjunzzang
[Swift] 프로토콜 (Protocol)
상단으로

티스토리툴바