프로토콜(Protocol)
- API를 좀 더 간결하게 표현하기 위한 방법
- 프로토콜은 단순히 메소드와 프로퍼티 선언의 모음
- 프토토콜은 어떤 다른 타입이 사용 되는 거의 대부분의 장소에서 사용될 수 있다. 즉, 변수, 함수, 파라미터 등
장점
- API를 좀 더 유연하고 표현력 있게 만들 수 있다.
- 뷰와 컨트롤러 사이의 구조적인 블라인드 통신(델리게이션)
- 행위를 지히가능( e.g 딕션너리의 키는 해싱될 수 있어야한다)
- 이질적인 타입들 사이에 기능 공유하기 (String, Array, CountableRange는 모두 Collection)
- (데이터가 아닌, 기능의) 다중 상속
세가지 측면
- 프로토콜 선언(메소드와 프로퍼티가 그 프로토콜 안에 있다)
- 프로토콜을 구현하도록 요구하는 class, struct 혹은 enum 선언
- 프로토콜을 구현하는 class, struct 혹은 enum(or extension) 내부 코드
프로토콜 내부의 옵셔널 메소드
- 보통 프로토콜 구현자는 프로토콜 내 모든 메소드 혹은 프로퍼티를 구현 (but, 옵셔널로 표기 가능) 단, 기본 ?? 과는 다름
- 옵셔널 메소드를 가지는 프로토콜은 반드시 @objc를 표시
- 옵셔널 프로토콜을 구현하는 어떤 클래스는 반드시 NSObject를 상속
- 이러한 종류의 프로토콜은 종종 iOS에서 델리게이션을 위해 사용
프토토콜 선언
//protocol protocol이름: 슈퍼protocol(상속)
protocol SomeProtocol : InheritedProtocol1, InheritedProtocol2 {
//SomeProtocol 구현자는 InheritedProtocl1과 2를 역시 구현
var someProperty: Int { get set } //get 만이면 접근만 가능 set까지면 설정 변경가능
func aMethod(arg1: Double, anotherArgument: String) -> SomeType
mutating func changeIT() //수신자(구현자)를 변경할 것으로 예상되는 함수를 mutating을 표시
init(arg: Type)//구현자가 이니셜라이저를 구현해야 한다고 명시 가능
}
프로토콜을 class로 제약을 두고 싶으면 class삽입
-> protocol SomeProtocol: class, InheritedProtocol1, InheritedProtoocl2
,를 추가함으로써 여러 프로토콜 구현 가능
클래스에서, init은 required로 표시되어야 한다.
extension 을 통해 프로토콜을 일치를 추가하는 것이 허용
프로토콜을 타입처럼 사용
protocol Moveable {
mutating func move(to point: CGPoint)
}
class Car : Moveable {
func move(to point: CGPoint) {...}
func change0il() {...}
}
struct Shape : Moveable {
mutating func move(to point: CGPoint) {...}
func draw(){...}
}
let prius: Car = Car()
square: Shape = Shape()
Delegation
- 프로토콜의 한 가지 매우 중요한 사용
- 프로토콜은 어떤 뷰와 그 컨트롤러 사이의 "블라인드 통신"을 구현하는 한 방법 - 어떻게 진행하는가
- 어떤 뷰가 델리게이션 프로토콜을 선언 (뷰가 뷰 자신을 위한 컨트롤러 허가를 원함)
- 그 뷰의 API는 델리게이션 프로토콜 타입의 weak delegate 프로퍼티를 가진다.
- 그 뷰는 delegate 프로퍼티를 사용해서 뷰가 소유할 수 없는 것을 가져오거나 뷰 자신에 대한 제어를 수행
- 컨트롤러가 그 뷰의 프로토콜을 구현한다고 선언
- 컨트롤러는 위 2번의 프로퍼티를 사용해 뷰의 delegate 를 컨트롤러 자신으로 지정
- 컨트롤러는 프로토콜을 구현 (그 프로토콜을 내부의 많은 옵셔널 메소드를 가질 것)
- 이제 뷰는 컨트롤러의 연결
- 그러나 뷰는 여전히 자신의 컨트롤러의 세부사항은 잘 모른다. 그래서 그 뷰는 제너릭으로 / 재사용 가능하도록 남게 된다. - 이 메커니즘은 iOS 전체를 통해 발견
- 그러나, 델리게이션은 Swift 에서 클로저보다 먼저 디자인되었다. 클로저는 때때로 더 나은 선택 - UIScrollView는 하나의 delegate 프로퍼티를 가진다.
weak var delegate: UIScrollViewDelegate? //UIScrollView는 하나의 delegate를 가진다.
- UIScrollViewDelegate 프로토콜은 다음과 같다.
@objc protocol UIScrollViewDelegate {
optional func scrollViewDidScroll(scrollView: UIScrollView)
optional func viewForZooming(in scrollView: UIScrollView) -> UIView
...and many more ...
}
//내부에 UIScrollView를 가진 뷰의 컨트롤러는 다음과 같이 선언될 것이다.
class MyViewController: UIViewController, UIScrollViewDelegate {...}
//스크롤 뷰를 위해 @IBOutlet didSet 안에서, 컨트롤러는 다음을 수행할 것이다.
scrollView.delegate = self
Dictionary Key
- 딕셔너리에서 키는 유일해야 함
- 키가 유일하기 위해서는 확률적으로 유일한 어떤 Int(해시)를 제공하여 두 키가 실제로 같은지 알아보는 동등성 테스트를 구현해야함
//딕셔너리의 키들이 Hashable 프로토콜을 구현하도록 요구
Protocol Hashable: Equatable {//Hashable이 Equatable에 상속됨에 주목
var hashValue: Int { get }
}
//Hashalbe이 되기위해, 역시 Equatable도 구현해야 함
Protocol Equatable{
static func == (lhs: Self, rhs: Self) -> Bool //lhs: Self 는 Equatable과 같은 타입
}
- Equatable에 일치하는 타입은 == 이라는 타입 함수(static...)를 가져야 한다.
- == 두인자는 동일한 타입으로 타입 그 자신
- == 연산자 역시 그 구현을 제공하는 static 메소드를 찾는다.
- 그 때, 딕셔너리는 다음과 같이 선언
Dictionary<Key: Hashable, Value>
//이것은 키가 Hashable에 일치하도록 제약