Swift 3.0.1 가이드에 대응하는 정리글을 작성하였습니다!!!
Protocols 정리 최신버전 > http://wlaxhrl.tistory.com/57
Apple 제공 Swift 프로그래밍 가이드(2.2)의 Protocols 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.
들어가며
프로토콜은 특정 task나 functionality를 위한 메서드, 프로퍼티, 그 외 요구사항들의 청사진을 정의한다. 클래스, 구조체, ENUM은 프로토콜을 적용하여 그 요구사항들의 실제 구현을 제공한다. 어떤 타입이 특정 프로토콜의 요구사항을 만족시키면, 그 타입이 프로토콜을 따른다conform고 말한다.
필요 시 프로토콜을 확장하여 그것을 따르는 타입에게 도움이 되는 기능을 제공하거나 할 수도 있다.
문법
프로토콜의 정의 문법은 클래스, 구조체, ENUM과 매우 유사하다.
protocol SomeProtocol {
// 프로토콜 정의가 여기에 온다
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 구조체 정의가 여기에 온다
// 이 구조체는 FirstProtocol과 AnotherProtocol을 따르고 있다
}
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 클래스 정의가 여기에 온다
// 슈퍼클래스가 존재할 경우 프로토콜보다 먼저 나오도록 위치시킨다
}
프로퍼티 요구사항
프로토콜은 인스턴스 프로퍼티/타입 프로퍼티를 요구할 수 있다.
프로퍼티가 stored인지 computed인지는 명시하지 않는다.
프로퍼티의 이름과 타입을 명시해야 한다.
프로퍼티가 읽기/쓰기 속성인지 읽기 전용인지 명시해야 한다.
프로토콜이 읽기/쓰기용 프로퍼티를 요구한다면 -> 읽기/쓰기가 가능한 프로퍼티로만 요구사항을 만족시킬 수 있다. (읽기전용 프로퍼티로는 안 된다는 말)
프로토콜이 읽기전용 프로퍼티를 요구한다면 -> 어떤 종류의 프로퍼티라도 요구사항을 만족시킬 수 있다. (읽기/쓰기용 프로퍼티도 된다는 말)
프로퍼티 요구사항은 항상 변수로 선언되어야 한다. (var 키워드)
타입 프로퍼티 요구사항 앞에는 static 키워드를 붙인다. 클래스가 이 프로토콜을 따를 때에는 그 타입 프로퍼티 앞에 class 또는 static 키워드를 붙이도록 한다.
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doseNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
예제를 살펴보자.
// 인스턴스 프로퍼티 요구사항 1개만 가진 프로토콜
protocol FullyNamed {
var fullName: String { get }
}
// 프로토콜을 따르는 간단한 구조체
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// 프로토콜을 따르는 조금 복잡한 클래스
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
// computed 프로퍼티로 구현했다
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
메서드 요구사항
프로토콜은 인스턴스 메서드/타입 메서드를 요구할 수 있다.
메서드 요구사항은 프로토콜 정의부에서 일반적인 메서드와 같은 방식으로 작성하면 된다. 단, 대괄호와 메서드 본문은 뺀다.
가변 파라미터Variadic parameters도 허용된다.
파라미터의 디폴트 값을 명시할 수 없다.
타입 메서드 요구사항 앞에는 static 키워드를 붙인다. 클래스가 이 프로토콜을 따를 때에는 그 타입 메서드 앞에 class 또는 static 키워드를 붙이도록 한다.
protocol SomeProtocol {
static func someTypeMethod()
}
예제를 살펴보자.
// 인스턴스 메서드 요구사항 1개만 가진 프로토콜 (난수생성 프로토콜)
protocol RandomNumberGenerator {
func random() -> Double
}
// 프로토콜을 따르는 클래스
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("랜덤넘버: \(generator.random())")
// Prints "랜덤넘버: 0.37464991998171"
print("한 번 더 랜덤넘버: \(generator.random())")
// Prints "한 번 더 랜덤넘버: 0.729023776863283"
Mutating 메서드 요구사항
값 타입(구조체, ENUM)의 인스턴스 메서드 안에서 자신(인스턴스)을 수정modify, mutate할 필요가 있다면 메서드 앞에 mutating 키워드를 붙였었다. 이 룰은 프로토콜 정의부에서도 똑같이 적용된다.
예제를 보자.
// mutating 메서드 요구사항 1개만 가진 프로토콜
protocol Togglable {
mutating func toggle()
}
// 프로토콜을 따르는 ENUM
enum OnOffSwitch: Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
// lightSwitch 는 이제 On 상태
<note> 프로토콜의 요구사항으로 mutating 메서드를 선언했더라도, 클래스가 이 프로토콜을 따를 경우에는 메서드 앞에 mutating 키워드를 쓰지말라. mutating 키워드는 오직 값 타입(구조체, ENUM)을 위한 것이다.
이니셜라이저 요구사항
프로토콜은 특정 이니셜라이저를 요구할 수 있다.
이니셜라이저 요구사항은 프로토콜 정의부에서 일반적인 이니셜라이저와 같은 방식으로 작성하면 된다. 단, 대괄호와 이니셜라이저 본문은 뺀다.
protocol SomeProtocol {
init(someParameter: Int)
}
<이니셜라이저 요구사항을 클래스에서 구현하기>
프로토콜에 정의된 이니셜라이저를 클래스에서 구현할 수 있다. designated 이니셜라이저 또는 convenience 이니셜라이저로 구현할 수 있는데, 두 경우 모두 반드시 required 수식어를 붙여야 한다.
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 이니셜라이저 구현이 여기에
}
}
required 수식어를 붙임으로써 이 클래스를 상속받은 서브클래스들 역시 프로토콜에 정의된 이니셜라이저를 구현함을 보장받을 수 있다. (* 클래스의 이니셜라이저 앞에 required 수식어를 붙이면 모든 서브클래스는 해당 이니셜라이저를 구현해야 한다. 자세한 것은 Initialization 포스팅 3편에서 다뤘다. 보러가기)
<note> final 수식어가 붙은 클래스의 경우에는 required 수식어를 붙일 필요가 없다. final 클래스는 어차피 서브클래싱이 불가능하기 때문이다.
만약 서브클래스가 슈퍼클래스로부터 designated 이니셜라이저를 오버라이드하고, 그 이니셜라이저와 매칭되는 이니셜라이저가 정의된 프로토콜을 따르고 있다면, required와 override 수식어를 둘 다 이니셜라이저 구현에 붙인다. 예제를 보자.
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance;
// "override" from SomeSuperClass
required override init() {
}
}
<Failable 이니셜라이저 요구사항>
프로토콜은 failable 이니셜라이저(init?)를 요구할 수 있다.
failable 이니셜라이저 요구사항은 -> failable 이니셜라이저나 nonfailable 이니셜라이저(= 일반 이니셜라이저)로 만족시킬 수 있다. (* failable 이니셜라이저를 서브클래스에서는 nonfailable 이니셜라이저로 오버라이드할 수 있는 것과 같다. 이에 대해서는 Initialization 포스팅 3편에서 다뤘다. 보러가기)
nonfailable 이니셜라이저 요구사항은 -> nonfailable 이니셜라이저나 암시적 언랩핑 failable 이니셜라이저(init!)로 만족시킬 수 있다.
타입으로서의 프로토콜
프로토콜 그 자체는 어떤 기능도 구현하고 있지 않다. 그러나 모든 프로토콜은 코드 상에서 다른 타입과 같은 역할을 할 수 있다.
즉, 프로토콜은 타입으로서 쓰일 수 있다. (fully-fledged type)
함수, 메서드, 이니셜라이저에서 파라미터 타입이나 리턴 타입으로 쓰일 수 있다.
상수, 변수, 프로퍼티의 타입으로 쓰일 수 있다.
배열, 딕셔너리, 그 외 컨테이너 타입에 들어있는 아이템 타입으로 쓰일 수 있다.
<note> 프로토콜도 타입으로 쓰일 수 있으니 프로토콜의 이름은 대문자로 시작하라. (Swift에서 타입의 이름은 대문자로 시작함, Int, String, Double 등)
그럼 프로토콜을 타입으로 쓰고 있는 예제를 보자.
protocol RandomNumberGenerator {
func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
}
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("주사위 : \(d6.roll())")
}
// 주사위 : 3
// 주사위 : 5
// 주사위 : 4
// 주사위 : 5
// 주사위 : 4
이번 편에서는 프로토콜의 간단한 사항들을 알아보았다. 다음 2편에서는 Delegation부터 시작해서 나머지 사항들에 대해 알아보자.
2편을 작성하였습니다. http://wlaxhrl.tistory.com/29
'Swift 공식 가이드 > Swift 2' 카테고리의 다른 글
Protocols (3) (0) | 2016.06.19 |
---|---|
Protocols (2) (0) | 2016.06.19 |
Extensions (0) | 2016.06.12 |
Nested Types (0) | 2016.06.07 |
Type Casting (0) | 2016.06.07 |