[Swift] 프로토콜

김태형·2023년 4월 16일
0

Swift

목록 보기
19/22

이번에는 프로토콜에 대해 알아보려고 한다. 사실 swift에서 매우 중요한 개념이기 때문에 이번 책을 통해 프로토콜에 대해 통달했다는 생각하지 않고, 그냥 이러한 개념이구나라는 정도로 알았습니다.


프로토콜

  • 특정 역할을 하기 위한 메소드 ,프로퍼티, 기타 요구사항 등의 청사진을 정의
    -> 특정 역할을 하기 위해 필요한 것들을 정의해놓은 규범

  • 프로토콜을 채택해서 특정 기능을 실행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있음

  • ‘프로토콜을 준수한다’ → 프로토콜의 요구사항을 모두 따름

  • 프로토콜은 정의를 하고 제시를 하는 것이지, 스스로가 기능을 구현하는 것은 아님!

  • 프로토콜은 쉼표를 통해 여러 개 채택 가능

    • 상속과 같은 것도 함께 사용 가능
struct SomeClass: SuperClass, AProtocol, AnotherProtocol {
	
}

프로퍼티 요구

  • var 키워드를 통해 프로퍼티 요구사항을 정의할 수 있음
  • 읽기와 쓰기가 모두 가능한 프로퍼티는 { get set }으로 정의할 수 있음
  • 읽기 전용은 { get }으로 정의
protocol SomeProtocol {
	var settableProperty: String { get set }
	var notNeedToBeSettableProperty: String { get }
}

메소드 요구

  • 메소드의 실제 구현부인 중괄호({}) 부분은 제외하고 메소드의 이름, 매개변수, 반환 타입 등만 작성
//무언가를 수신받을 수 있는 기능

protocol Receivable {
    func received(data: Any, from: Sendable)
}


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


//수신,발신이 가능한 Message 클래스
class Message: Sendable, Receivable {
    //발신은 Sendable 프로토콜을 준수하는 타입의 인스턴스여야 함
    var from: Sendable {
        return self
    }
    
    //상대방은 Receivable 프로토콜을 준수하는 타입의 인스턴스여야 함
    var to: Receivable?
    
    //메세지 발신
    func send(data: Any) {
        guard let receiver: Receivable = 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, Receivable {
    var from: Sendable {
        return self
    }
    
    var to: Receivable?
    
    func send(data: Any) {
        guard let receiver: Receivable = self.to else {
            print("Mail has no receiver")
            return
        }
        receiver.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

//수신받을 인스턴스가 있어서 메세지를 주고받을 수 있음
myPhoneMessage.to = yourPhoneMessage
myPhoneMessage.send(data: "Hello")  //Message received Hello from Message

이니셜라이저 요구

  • 이니셜라이저의 경우 ‘클래스’의 경우일 때 신경을 써야 함
    • 프로토콜의 이니셜라이저 요구에 부합하는 이니셜라이저를 구현할 때는 지정 이니셜라이저인지, 편의 이니셜라이저인지 중요하지 않음
    • 하지만 이니셜라이저 요구에 부합하는 이니셜라이저를 구현할 때는 required 식별자를 붙여야 함
    • 클래스에서 이니셜라이저가 필요할 때는 required를 붙인다.
      • 만약 상속받을 수 없는 final 클래스라면 required를 안붙여도 됨
    • 만약 클래스에 프로토콜이 요구하는 이니셜라이저가 이미 구현되어 있는 상황에서 그 클래스를 상속받은 클래스가 있다면, required와 override 식별자를 모두 명시하여 이니셜라이저를 구현해야 함
protocol Named {
    var name: String { get }
    
    init(name: String)
}

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

class Person: Named {
    var name: String
    
    required init(name: String) {    //class에서 이니셜라이저가 필요하기 때문에 required를 붙임
        self.name = name
    }
}



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

class MiddleSchool: School, Named {
	required override init(name: String) {    //이미 School에서 정의가 되어 있어서 override 작성
		super.init(name: name)
	}
}


프로토콜 상속

  • 클래스와 상속 문법과 유사하게 상속할 수 있음
  • 프로토콜의 상속 리스트에 class 키워드를 추가하면 프로토콜이 클래스 타입에만 채택될 수 있도록 제한 가능
    • 근데 이거 deprecated임!!
  • 맨 처음에 class 위치

프로토콜 조합과 프로토콜 준수 확인

  • 하나의 매개변수가 여러 프로토콜을 모두 준수하는 타입이어야 한다면 하나의 매개변수에 여러 프로토콜을 한 번에 조합하여 요구할 수 있음
  • &를 사용하여 여러 프로토콜 조합 가능
protocol: Named {
	var name: String { get }
}

protocol: Aged {
	var age: Int { get }
}

struct Person: Named, Aged {
	var name: String
	var age: Int
}



func celebrateBirthday(to celebrator: Named & Aged) {    //&를 사용하여 프로토콜 사용
	print("Happy Birthday \(celebrator.name)! Now you are \(celebrator.age)")
}

let ted: Person = Person(name: "Ted", age: 26)    //이렇게해서 &를 사용하지 않고도 가능
celebrateBirthday(to: ted)  

프로토콜의 선택적 요구

  • 선택적 요구사항을 적용하려면 objc 속성이 부여된 프로토콜이어야 함
    • objc 속성이 부여되는 프로토콜은 Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있음
      • 열거형이나 구조체 등에서는 채택할 수 없음
    • Foundation 프레임워크 모듈을 임포트해야함
  • 옵셔널을 통해 선택적 요구사항을 정의할 수 있음

위임을 위한 프로토콜

  • 위임(Delegate) : 클래스나 구조체가 자신의 책임이나 임무를 다른 타비의 인스턴스에게 위임하는 디자인 패턴
    • 비동기 처리에서도 많이 쓰임
  • Delegate Pattern
    • 애플의 프레임워크에서 중요한 패턴 중 하나

      ex. UITableView 타입의 인스턴스가 해야 하는 일을 위임받아 처리하는 인스턴스는 UITableViewDelegate 프로토콜을 준수함

      UITableViewDelegate 프로토콜을 준수하는 인스턴스는 UITableView의 인스턴스가 해야 하는 일을 대신 처리해 줌



한 문장으로 정리하면 프로토콜은 어떠한 규약을 정해놓고 그 규약을 채택하는 것? 정도로 생각하면 될 것 같다. Delegate pattern은 예전에 사용했던 것 같은데, 이에 대해서도 나중에 다시 공부해야겠다.

0개의 댓글