안녕하세요! 짭짭이 입니다.
정말 오랜만에 글씁니다. ㅎㅎㅎ
최근에 너무 바빠서 글을 포스팅 할 시간이 없었습니다.
스스로에게 반성합니다. 훨씬 더 성장해야합니다.
아직 완벽한 것은 아닌데, 제너릭에 대해 공부한 것을 적어보려 합니다.
** Generics란 **
Apple Document를 보면 Generic은 여러분이 정의한 타입에 대해 유연성을 가능하게 해주는 겁니다.
아래와 같이 두개의 대상을 Swap 하는 코드가 있다고 할 때, 평소와 같이 한다면 하나의 타입에 대해서만 적용이 됩니다.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
하지만 만약 제너릭을 사용한다면 특정 타입에 제한 되는 것이 아니라, 아래 처럼 타입에 관계없이 사용 할 수 있습니다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
기본적인 구조는 정말 간단합니다.
func swap< 제너릭 타입 이름 >(item1 : inout 제너릭 타입 이름 , item2 : inout 제너릭 타입 이름){
let temp = item1
item1 = item2
item1 = temp
}
<> 안에 제너릭 이름을 넣어주면 됩니다.
보통 이름이 생각 안난다면 T, U, V 를 사용한다고 합니다 ㅎㅎㅎ.
그렇다면 아래와 같이 작성 했을 때, 결과도 어떨지 한번 보도록 하겠습니다.
//Generic Type Function - Swap
func swap<Element>(item1 : inout Element , item2 : inout Element){
let temp = item1
item1 = item2
item1 = temp
}
var A : String = "ChapChap"
var B : String = "Hururuek"
print("\(A) , \(B)") //결과 : ChapChap , Hururuek
swap(&A, &B)
print("\(A) , \(B)") //결과 : Hururuek , ChapChap
이렇게 제너릭을 사용한다면 유연한 코드를 작성 할 수 있게 됩니다.
** Genric with Struct **
제너릭은 함수 뿐만 아니라 클래스 프로토콜 , 구조체 등 다양한 부분에서 적용할 수 있습니다.
일단 구조체에 사용된 구조체를 보도록 하겠습니다.
struct Stack<Element> {
var items : [Element] = []
mutating func push(_ item : Element){
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
제너릭을 적용한 구조체를 만들 때는 구조체이름<제너릭 타입 이름> 이렇게 해주면 됩니다.
그러면 <>에 해당하는 제너릭 타입에 맞게 타입을 생성하게 됩니다.
그러면 사용하는 방법도 필요합니다.
var stack : Stack<String> = Stack<String>()
stack.push("Hello")
stack.push("World")
print(stack.items) //결과 : ["Hello", "World"]
stack.pop() // 결과 : "World"
사용 할 때는 구조체이름<사용할 타입>() 이렇게 해줘야합니다.
보통은 구조체이름() 이렇게 됐지만 제너릭이 들어가기 때문에 구조체이름<>() 이런 형식이 되어야 합니다.
뿐만 아니라 아래 처럼 Extension을 사용해서 확장 할 수도 있습니다.
이때, 제너릭 타입을 그대로 사용할 수 있다는 특징이 있습니다.
extension Stack{
var topItem : Element?{
return items.isEmpty ? nil : items.last
}
}
print(stack.topItem ?? "비었습니다") // 결과 : Hello
** Genric with Type Constraint **
유연한 타입 사용을 위해서 제너릭에 타입에 관계없이 사용할 수 있다는 장점이 있지만
특정 상황에서만 사용해야할 제너릭 타입이 필요 할 때가 있을 겁니다.
그럴 때 제약조건을 제너릭에 줄 수 있습니다.
func compareFunction< Element : Equatable>(item : Element , list : [Element]) -> Int? {
for (index , value) in list.enumerated(){
if value == item {
return index
}
}
return nil
}
let list : [Int] = [3,1,4,5,1,5,1,0,9,5]
print(compareFunction(item: 1, list: list) ?? "없습니다") // 결과 : 1
기본에 사용 했던 제너릭 타입과 차이점은
제너릭 타입 이름 선언하는 <> 부분에
< 제너릭이름 : 제약조건 > 이렇게 들어갔습니다.
이 의미는 해당하는 제너릭은 제약조건을 만족해야 사용할 수 있는 것을 의미합니다.
그리고 이 제약 조건은 클래스와 프로퍼티가 가능합니다.
위의 코드는 프로퍼티에 해당하는 제약 조건을 걸어 준 것이고
클래스의 제약 조건은 아래와 같이 줄 수 있습니다.
class Animal {
var name : String
init(name : String){
self.name = name
}
}
class Person : Animal {
var age : Int
init(name : String , age : Int){
self.age = age
super.init(name: name)
}
}
func printName <Element : Animal>(input : Element){
if let item = input as? Person {
print("name : \(item.name) age : \(item.age)")
}
else{
print("name : \(input.name)")
}
}
let animal : Person = Person(name: "ChapChap", age: 27)
printName(input: animal) // 결과 : name : ChapChap age : 27
** associatedtype 이란? **
Associated Type은 Protocol에 제너릭 타입을 지정해주는 거라 생각 하면 됩니다.
위에 정의문에서도 associated type 은 protocol의 한 부분으로 사용되는 타임에 이름을 주는 것이라고 되어 있습니다.
예를들면 다음 Protocol에 Item이 제너릭 타입이고 그 Item을 각 함수에서 사용하고 있습니다.
protocol Container {
associatedtype Item
mutating func append(_ item : Item)
var count : Int {get}
}
그럼 위에 Protocol을 각 구조체에 상속 받도록 하겠습니다.
struct IntStack : Container {
var items : [Int] = []
mutating func push(_ item : Int){
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int{
return items.count
}
}
이렇게 하면 typealias 라는 이름으로 associatedType이 생성이 됩니다.
그리고 그 부분에 Int를 넣어주면 함수에 item : Item 부분이 Int로 바뀌게 됩니다.
이렇게 typealias로 associatedtype을 지정해 줄 수 있지만
아래 처럼 <제너릭 이름>을 해주면 typealias가 생기지 않고
자동으로 associatedtype을 알아차립니다.
struct Stacks<Element> : Container {
var items = [Element]()
mutating func push(_ item : Element){
items.append(item)
}
mutating func pop() -> Element{
return items.removeLast()
}
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int{
return items.count
}
}
** Generic에 where로 제약 조건 추가 **
현제 제너릭에 제약 조건을 추가할 때는 <제너릭 : 제약조건> 이렇게 하나 밖에 할 수 없지만
where를 사용하면 여러개를 추가 할 수 있습니다.
protocol Rules {
associatedtype Item
var list : [Item] {get set}
var count : Int {get}
}
func allItemsMatch<C1: Rules, C2: Rules>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item : Equatable {
// Check that both containers contain the same number of items.
if someContainer.count != anotherContainer.count {
return false
}
// Check each pair of items to see if they're equivalent.
for i in 0..<someContainer.count {
if someContainer.list[i] != anotherContainer.list[i] {
return false
}
}
// All items match, so return true.
return true
}
이렇게 Rule이라는 Protocol을 만들고
allItemsMatch 함수를 보면 각 제너릭은 Rules를 상속 받으면서
제너릭의 associatedType인 C1.Item과 C2.Item은 같아야 하며, 해당하는 associatedType이 Equatable을 준수해야합니다.
이렇게 where를 사용하면 제너릭에 여러가지 제약 조건을 추가 할 수 있습니다.
만약 아래 처럼 제약 조건을 위배 했을 경우에는 , 아래 처럼 오류가 발생 합니다.
** Extension으로 확장 하기 **
제너릭도 역시 Extension으로 확장 할 수 있는데, 이때 where로 제약 조건을 걸어주면
Extension에 존재하는 함수들을 사용 할 때는 where 조건을 만족할 때만 사용 할 수 있다.
extension Stacks where Element : Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
위에 처럼 Stack과 같은 구조체나 클래스에 extension을 해서 확장 한 것 처럼
Protocol도 extension으로 확장 할 수 있다.
//조건을 넣어줄 때 재료 : 조건
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1
}
}
//Protocol일 경우에는 associatedtype에 대해서 제한을 줄 수 있다.
extension Container where Item == Int{
func average() -> Double {
return Double(count) / Double(2)
}
}
만약 Protocol에 associatedtype을 사용 했다면 위에 처럼 Item에 where로 제약사항을 넣어 줄 수 있다.
이렇게 extension을 통해 제너릭을 확장 할 수 있고
특정 부분에 where로 제약조건을 걸어서 좀 더 다양하게 활용 할 수 있다.
지금 까지 제너릭에 대해 공부 했는데,,,,
사실 아직 부족한 부분이 많다.
이 외에도 서브스크립트 와 associatedtype에 제약조건을 걸어주는 것 등 많지만
그 부분에 대해서 나중에 좀더 깊게 공부 해봐야겠다.
감사합니다
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) 런타임 프로토콜 AND 컴파일 프로토콜 (클로저 잠깐,,) (1) | 2021.04.25 |
---|---|
PlayGround) Generic - Closure의 확장 (1) | 2021.04.11 |
PlayGround) Protocol에 대해서 좀 더 깊게 알아가기 (0) | 2021.02.14 |
PlayGround) Method Chaining와 Optional Chaining이란! (0) | 2021.02.13 |
PlayGround) TypeCasting과 Meta Type이 멀까요? (0) | 2021.02.11 |
댓글