The Swift Language Guide (번역본)
Swift) 제네릭(Generic) 정복하기
프로토콜 | 야곰 swift 블로그
protocol 프로토콜 이름 {
}
protocol Talkable {
// 프로퍼티 요구
// get은 읽기만 가능해도 상관 없다는 뜻이며
// get과 set을 모두 명시하면
// 읽기 쓰기 모두 가능한 프로퍼티여야 한다
var topic: String { get set }
var language: String { get }
// 메서드 요구
func talk()
// 이니셜라이저 요구
init(topic: String, language: String)
}
// Person 구조체는 Talkable 프로토콜을 "채택"
struct Person: Talkable {
// 프로퍼티 요구 "준수"
var topic: String
let language: String
// 메서드 요구 "준수"
func talk() {
print("\(topic)에 대해 \(language)로 말합니다")
}
// 이니셜라이저 요구 "준수"
init(topic: String, language: String) {
self.topic = topic
self.language = language
}
프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다.
프로토콜 상속 문법은 클래스의 상속 문법과 유사하나 프로토콜은 클래스와 다르게 다중상속이 가능.
protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
}
<프로토콜 상속의 예시>
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
func speak()
}
struct SomeType: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
}
클래스에서 상속과 프로토콜 채택을 동시에 하려면
1. 상속받으려는 클래스를 먼저 명시하고
2. 그 뒤에 채택할 프로토콜 목록을 작성
class SuperClass: Readable {
func read() { }
}
class SubClass: SuperClass, Writeable, ReadSpeakable {
func write() { }
func speak() { }
}
is, as 연산자를 사용해서 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있다.
// SuperClass : Readable
// SubClass : SuperClass, writeable, ReadSpeakable
let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()
var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false
someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true
someAny = sup
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
} // read
if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
someReadSpeakable.speak()
} // 동작하지 않음
someAny = sub
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
} // read
타입에 의존하지 않는 범용 코드를 작성할 때 사용
제네릭은 더 유연하고 재사용 가능한 함수와 타입의 코드를 작성하는 것을 가능케한다.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
(C언어에서의 템플릿과 비슷한 흐름인 듯 하다)
만약 내가 함수를 작성을 했는데, 내용은 같지만 상황에 따라 매개변수 타입이 각각 다르게 넣고 싶다면?
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
Swift는 타입에 예민한 언어이기 때문에 매개변수 타입에 따라 함수를 계속해서 오버로딩해야할 것이다
☞ 제너릭 을 통해서 타입에 제한을 두지않게 만든다.
실제 함수를 호출할 때마다 해당 매개변수 타입으로 바뀌게 된다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
// 사용할 이름 뒤에 <T>를 붙임.
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
제너릭은 함수 뿐만 아니라 구조체, 클래스, 열거형에서도 사용이 가능하다.
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
제네릭 함수를 선언할 때
파라미터 뒤에 상속 받아야 하는 클래스를 선언하거나 반드시 따라야 하는 프로토콜을 명시할 수 있다.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
class Apple { }
class Fruit { }
class Banana: Human { }
func sayIt<T: Human>(_ a: T) { }
let apple = Apple.init()
let fruit = Fruit.init()
let banana = Banana.init()
SayIt(bird) // error