티스토리 뷰

programming/Swift

[Swift/iOS] 프로토콜 Protocol (1)

마들브라더 2022. 10. 25. 18:48

프로토콜이란

  • 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진
  • ‘프로토콜을 준수한다’라는 것은 프로토콜의 요구사항을 모두 충족시키는 것
  • 프로토콜은 정의를 하고 제시할 뿐 기능 구현을 하지 않음

프로토콜 채택

  • protocol 키워드 사용
  • 클래스가 다른 클래스를 상속받는다면, 클래스 이름 다음에 프로토콜 나열
protocol (프로토콜이름) {
        (프로토콜 정의)
}

// 타입의 프로토콜 채택
Struct SomeStruct: AProtocol, BProtocol {
        (구조체 정의)
}
Class SomeClass: SuperClass, AProtocol, BProtocoal {
        (클래스 정의)
}

 

프로토콜 요구사항

  • 프로토콜은 자신을 채택하면 프로퍼티나 메서드와 같은 기능들을 요구함

프로퍼티 요구

  • 프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야 하는지 요구할 수 있음
  • 그렇지만 그 프로퍼티의 종류를 요구하지 않음
  • 다만 읽기 전용으로 할지, 읽고 쓰고 모두 가능하게 할지는 프로토콜이 정함
  • 프로퍼티 요구사항은 항상 var 키워드를 사용한 변수 프로퍼티로 정의
protocol SomeProtocol {
    var setTableProperty: String { get set } // 읽기 쓰기 모두 가능한 프로퍼티
    var notNeedToBeSetTableProperty: String { get } // 읽기 전용 프로퍼티
}

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set } // 타입프로퍼티 요구
    static var anotherTypeProperty: Int { get }
}

// Sendable 프로토콜과 프로토콜을 준수하는 클래스
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
    }
}

let message = Message(sender: "sender", receiver: "receiver")
print(message.to) // receiver
print(message.from) // sender
message.to = "tester"
// message.from = "test2" 에러, 쓰기 불가능
print(message.to) // tester
print(message.from) // sender

let mail = Mail(sender: "sender", receiver: "receiver")
print(mail.to) // receiver
print(mail.from) // sender
mail.to = "tester"
mail.from = "tester2"
print(mail.to) // tester
print(mail.from) // tester2

 

메서드 요구

  • 특정 인스턴스 메서드나, 타입 메서드를 요구할 수도 있음
  • 타입 메서드를 요구할 때는 static을 명시하나, 실제 구현할 때는 class를 사용해도 문제 없음
// Receiveable, Sendable 프로토콜을 준수하는 클래스

// 수신 기능
protocol Receiveable {
    func received(data: Any, from: Sendable)
}

// 발신 기능
protocol Sendable {
    var from: Sendable { get } // 타입으로서 프로토콜은, 프로토콜을 채택한 클래스의 인스턴스가 들어갈 수 있다는 것을 의미함. Sendable을 채택한 클래스가 from의 타입으로 들어갈 수 있음.
    var to: Receiveable? { get }

    func send(data: Any)

    static func isSendableInstance(_ instance: Any) -> Bool
}

// 수신, 발신 가능한 클래스

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("There isn't a receiver of a message")
            return
        }
        // 수신 가능한 인스턴스의 메서드 호출
        receiver.received(data: data, from: self.from)
    }

    // 메시지 수신
    func received(data: Any, from: Sendable) {
        print("Message received \(data) from \(from)")
    }

    // 상속 가능한 메서드
    class func isSendableInstance(_ instance: Any) -> Bool {
            if let sendableInstance: Sendable = instance as? Sendable {
                return sendableInstance.to != nil
            }
            return false
    }
}

class Mail: Sendable, Receiveable {
    // 발신 가능한 객체는 Sendable 프로토콜을 준수하는 타입의 인스턴스, 여기서는 자신
    var from: Sendable {
        return self
    }

    // 수신 가능한 객체는 Receiveable을 준수하는 타입의 인스턴스
    var to: Receiveable?

    // 메시지 발신
    func send(data: Any) {
        guard let receiver: Receiveable = self.to else {
            print("There isn't a receiver of the mail")
            return
        }
        // 수신 가능한 인스턴스의 메서드 호출
        receiver.received(data: data, from: self.from)
    }

    // 메시지 수신
    func received(data: Any, from: Sendable) {
        print("Mail received \(data) from \(from)")
    }

    // 상속 불가능한 메서드
    static func isSendableInstance(_ instance: Any) -> Bool {
            if let sendableInstance: Sendable = instance as? Sendable {
                return sendableInstance.to != nil
            }
            return false
    }
}

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

// 수신 받을 인스턴스 없음
myPhoneMessage.send(data: "test") // There isn't a receiver of a message

// Message는 수발신 다 가능
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello") // Message received Hello from Message

// 메일 인스턴스 생성
let myMail = Mail()
let yourMail = Mail()

myMail.send(data: "Hi") // There isn't a receiver of the mail

myMail.to = yourMail
myMail.send(data: "to your mail") // Mail received to your mail from Mail

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

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

// myPhoneMessage는 프로토콜 준수함
Message.isSendableInstance(myPhoneMessage) // true

// yourPhoneMessage는 프로퍼티 설정이 되지 않음, 프로퍼티 준수 하지 않음
Message.isSendableInstance(yourPhoneMessage) // false

 

가변 메서드 요구

  • 메서드가 인스턴스 내부의 값을 변경할 필요가 있음
  • 인스턴스 메서드에서 자신 내부의 값을 변경하고자 할때는 mutating func 명시
  • 어떤 타입이든 간에 인스턴스 내부의 값을 변경해야하는 메서드를 요구하려면 mutating 명시
    • 클래스 구현에서는 mutating 키워드 써주지 않아도 됨
protocol Resettable {
    mutating func reset()
}

class ClassPerson: Resettable {
    var name: String?
    var age: Int?

    func reset() {
        self.name = ""
        self.age = 0
    }
}

struct StructPerson: Resettable {
    var name: String?
    var age: Int?

    mutating func reset() {
        self.name = ""
        self.age = 0
    }
}
  • 만약 Resettable 프로토콜에서 가변 메서드를 요구하지 않는다면, mutating 메서드는 구현 불가능
protocol Resettable {
    func reset()
}

class ClassPerson: Resettable {
    var name: String?
    var age: Int?

    func reset() {
        self.name = ""
        self.age = 0
    }
}

struct StructPerson: Resettable {
    var name: String?
    var age: Int?

    func reset() {
//        self.name = ""
//        self.age = 0
    }
}
댓글