[Swift] 프로토콜(Protocols) - 작성중

Wody·2021년 3월 23일
0

원문 : Swift LanguageGuide - protocols
번역 : Swift LanguageGuide(한국어) - protocols

프로토콜은 특정 기능 수행에 필수적인 요구를 정의한 청사진(blueprint)입니다. 프로토콜을 만족시키는 타입을 "프로토콜을 따른다(conform)"고 말합니다. 프로토콜에 필수 구현을 추가 하거나 추가적인 기능을 더하기 위해 프로토콜을 확장(extend)하는 것이 가능합니다.

프로토콜 문법 (Protocols Syntax)

프로토콜의 정의는 클래스, 구조체, 열거형 등과 유사합니다.

protocol SomeProtocol {
	// 프로토콜의 정의를 여기에 작성
}

프로토콜을 따르는 타입을 정의하기 위해선 타입 이름 뒤에 콜론(:)을 붙이고 따를(conforming) 프로토콜 이름을 적습니다. 만약 따르는 프로토콜이 여러개라면 콤마(,)로 구분합니다

struct SomeStructure: FirstProtocol, AnotherProtocol {
	// 스트럭트의 정의를 작성
}

// 서브클래싱인 경우 수퍼클래스를 프로토콜 앞에 적어줍니다
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
	// 클래스의 정의
}

프로퍼티 요구 (Property Requirements)

프로토콜에서는 프로퍼티가 저장 프로퍼티인지, 연산 프로퍼티인지 명시하지 않습니다. 하지만 프로퍼티의 이름과 타입, gettable(읽기 전용), settable(읽기 쓰기 가능)한지 명시합니다. 필수 프로퍼티는 항상 var로 선언합니다.

protocol SomeProtocol {
    // 읽기와 쓰기 모두 가능한 프로퍼티
    var mustBeSettable: Int { get set }
    
    // 읽기만 가능한 프로퍼티
    var doseNotNeedToBeSettable: Int { get }
}

// 타입 프로퍼티는 static 키워드를 적어 선업합니다
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
    static var anotherTypeProperty: Int { get }
}

예제

protocol Sendable {
    var from: String { get }
    var to: String { get }
}

class Message: Sendable {
   var sender: String
   var from: String {
       return self.sender
   }
   
   var to: String
   
   init(sender: String, receiver: String) {
       self.sender = sender
       self.to = receiver
   }
}

class Mail: Sendable {
    var from: String
    var to: String
    
    init(sender: String, receiver: String) {
        self.from = sender
        self.to = receiver
    }
}

메소드 요구

프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수 있다. 프로토콜이 요구할 메서드는 프로토콜 정의에서 작성한다. 단 메서드의 실제 구현부 중괄호({}) 부분은 제외하고 메서드의 이름, 매개변수, 반환 타입 등만 작성하며 가변 매개변수도 허용한다.
타입 메서드를 요구할 때는 타입 프로퍼티 요구와 마찬가지로 앞에 static 키워드를 명시한다.

//  무언가 수신받을 수 있는 기능
protocol Receiveable {
    func received(data: Any, from: Sendable)
}

//  무언가 발신할 수 있는 기능
protocol Sendable {
    var from: Sendable { get }
    var to: Receiveable? { get }
    
    func send(data: Any)
    
    static func isSendableInstance(_ instance: Any) -> Bool
}

//  수신, 발신이 가능한 Message 클래스
class Message: Sendable, Receiveable {
    //  발신은 발신 가능한 객체, 즉 Sendable 프로토콜을 준수하는 타입의 인스턴스여야 함.
    var from: Sendable {
        return self
    }
    
    //  상대방은 수신 가능한 객체, 즉 Receiveable 프로토콜을 준수하는 타입의 인스턴스여야 함.
    var to: Receiveable?
    
    //  메시지를 발신합니다.
    func send(data: Any) {
        guard let receiver: Receiveable = self.to else {
            print("Message has no receiver")
            return
        }
        // 수신 가능한 인스턴스의 received 메서드를 호출합니다.
        receiver.received(data: data, from: self.from)
    }
    
    //  메시지를 수신합니다.
    func received(data: Any, from: Sendable) {
        print("Message received \(data) from \(from)")
    }
    
    //  class 메서드이므로 상속이 가능
    class func isSendableInstance(_ instance: Any) -> Bool {
        if let sendableInstance: Sendable = instance as? Sendable {
            return sendableInstance.to != nil
        }
        return false
    }
}

//  수신, 발신이 가능한 Mail 클래스
class Mail: Sendable, Receiveable {
    var from: Sendable {
        return self
    }
    
    var to: Receiveable?
    
    func send(data: Any) {
        guard let receivere: Receiveable = self.to else {
            print("Mail has no receiver")
            return
        }
        
        received(data: data, from: self.from)
    }
    
    func received(data: Any, from: Sendable) {
        print("Mail received \(data) from \(from)")
    }
    
    //  static 메서드이므로 상속이 불가능합니다.
    static func isSendableInstance(_ instance: Any) -> Bool {
        if let sendableInstance: Sendable = instance as? Sendable {
            return sendableInstance.to != nil
        }
        return false
    }
}

//  두 Message 인스턴스를 생성
let myPhoneMessage: Message = Message()
let yourPhoneMessage: Message = Message()

//  아직 수신받을 인스턴스가 없음
myPhoneMessage.send(data: "Hello") //   Message has no receiver

//  Message 인스턴스는 발신과 수신 모두 가능하르모 메시지를 주고 받을 수 있음.
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello") //   Message received Hello from Message

//  두 Mail 인스턴스를 생성
let myMail: Mail = Mail()
let yourMail: Mail = Mail()

myMail.send(data: "Hi") //  Mail has no receiver

//  Mail과 Message 모두 Sendable과 Receiveable 프로토콜을 준수하므로
//  서로 주고 받을 수 있음.
myMail.to = yourMail
myMail.send(data: "Hi") //  Mail received Hi from Mail

myMail.to = myPhoneMessage
myMail.send(data: "Bye") // Message received Bye from Mail

//  String은 Sendable 프로토콜을 준수하지 않음
Message.isSendableInstance("Hello") //  false

//  Mail과 Message는 Sendable 프로토콜을 준수합니다.
Message.isSendableInstance(myPhoneMessage) //   true

//  yourPhoneMessage는 to 프로퍼티가 설정되지 않아서 보낼 수 없는 상태
Message.isSendableInstance(yourPhoneMessage) //  false
Mail.isSendableInstance(myPhoneMessage) //  true
Mail.isSendableInstance(myMail)  // true

가변 메서드 요구

: 메서드가 인스턴스 내부의 값을 변경할 필요가 있을 때, 값 타입(구조체와 열거형)의 인스턴스 메서드에 자신 내부의 값을 변경하고자 할 떄는 메서드의 func 키워드 앞에 mutating 키워드를 적어 메서드에서 인스턴스 내부의 값을 변경한다는 것을 명시합니다.

protocol Resettable {
    // reset() 메서드는 mutating 키워드를 통해 인스턴스 내부의 값을 변경할 수 있음을 암시
    mutating func reset()
}

class User: Resettable {
    var name: String?
    var age: Int?
    
    // User 클래스의 name, age 프로퍼티 값을 수정하는 메서드
    func reset() {
        self.name = nil
        self.age = nil
    }

이니셜라이저 요구

프로토콜은 프로퍼티, 메서드 등과 마찬가지로 특정한 이니셜라이저를 요구할 수 있음. 이니셜라이저를 정의하지만 구현하지 않기 때문에 중괄호를 포함하지 않음.

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

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

struct의 경우 상속할 수 없기 떄문에 이니셜라이즈를 구현할 때 크게 신경쓸 필요는 없다.
다만 클래스를 구현할 경우 required 식별자를 붙인 이니셜라이즈로 구현해야 한다.
만약 상속받을 수 없는 final 클래스라면 required 식별자를 붙여줄 필요가 없다.

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

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

0개의 댓글