clamp
Clamp
clamp
글쓰기 관리
전체 방문자
오늘
어제
  • 분류 전체보기 (509)
    • IOS (85)
    • SwiftUI+TCA+Combine (9)
    • RxSwift + MVVM (56)
    • Clean Architecture (12)
    • SWIFT (56)
    • iOS - TDD (2)
    • 디자인패턴 (4)
    • CS (56)
      • 알고리즘 (29)
      • 운영체제 (15)
      • 자료구조 (2)
      • 네트워킹 (4)
      • 기타 (6)
    • 회고 (0)
    • Firebase (18)
    • SwiftUI (10)
    • iOS - UIKit (11)
    • iOS - 오픈소스 (6)
    • 코딩테스트 (166)
      • 프로그래머스 (164)
    • 정보처리기사 (14)
    • GitHub (2)
글쓰기 / 관리자

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • uikit
  • Swift
  • Q
  • ㅅ

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
clamp

Clamp

SWIFT

[Swift] 제네릭(Generic)

2023. 4. 17. 11:18

제네릭

  • 제네릭을 이용해 코드를 구현하면 어떤 타입에도 유연하게 대응할 수 있다.
  • 제네릭으로 구현한 기능과 타입은 재사용하기 쉽고, 코드의 중복을 줄일 수 있다.
  • 타입 파라미터<T>는 함수 내부에서  타입의 이나 리턴형으로 사용된다.
  • 관습적으로 T를 사용하지만 사용하지만 다른문자를 사용해도 되면 Upper camel case를 사용한다.
  • <T, A>처럼 2개이상을 선언해도 된다.

 

제네릭이 필요한 이유

var numA = 10
var numB = 20

print(numA, numB) // 10 20

// 두 숫자를 서로 교환하는 함수
func swapInt(_ a: inout Int, _ b: inout Int){
    let tmp = a
    a = b
    b = tmp
}

swapInt(&numA, &numB)

print(numA, numB) // 20 10

두 숫자를 교환하는 함수를 이렇게 정의할 수 있다.

근데 만약 String이나 Double을 교환하려면 모든 타입에 해당하는 함수를 다시 작성해야한다.

// 두 문자를 서로 교환하는 함수
func swapInt(_ a: inout String, _ b: inout String){
    let tmp = a
    a = b
    b = tmp
}

// 두 실수를 서로 교환하는 함수
func swapInt(_ a: inout Double, _ b: inout Double){
    let tmp = a
    a = b
    b = tmp
}

이런 번거로움과 코드 재사용성을 높이기 위해 제네릭이란 문법이 필요하다.

func printArray<T>(array: [T]){
    for element in array{
        print(element)
    }
}

func swapInt<T>(_ a: inout T, _ b: inout T){
    let tmp = a
    a = b
    b = tmp
}

 

실제로 Array와 Dictionary는 제네릭으로 생성되어있고, 다음과같이 작성되어있다.

struct Array<Element>
struct Dictionary<Key, Value> where Key : Hashable

제네릭의 활용

struct Stack<T> {
    var items = [T]()
    mutating func push(_ item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

var intStack: Stack<Int> = Stack<Int>()
var doubleStack: Stack<Double> = Stack<Double>()

intStack.push(1)
intStack.push(2)
intStack.push(3)

print(intStack)     //Stack<Int>(items: [1, 2, 3])

doubleStack.push(1.0)
doubleStack.push(2.0)
doubleStack.push(3.0)

print(doubleStack)      //Stack<Double>(items: [1.0, 2.0, 3.0])

FILO(First-In-Last-Out)방식으로 동작하는 스택을 제네릭을 활용해 구현할 수 있다.

여기서 Element는 타입임을 나타내는 표시일 뿐이고, 함수의 인풋, 아웃풋으로 사용할 수 있으며 타입을 생성할수도 있다.


제네릭의  확장

제네릭을 extension(확장)에도 적용할 수 있다.

확장을 할때에는 <T>를 적지 않고 본체에서만 Placeholder를 정의할 수 있다.

extension Stack{  // Stack<Element> (X)
    
    // 랜덤한 1개를 리턴하는 메서드
    func returnRandom() -> T{
        return items.randomElement()!
    }
}

타입제약

  • 타입 제약은 타입이 가져야할 제약사항을 지정할 수 있는 방법이다.
  • 타입 제약은 클래스 타입 또는 프로토콜로만 줄 수 있다.
  • 제네릭에 타입에 제약을 주고싶으면 타입 매개변수 뒤에 콜론을 붙히고, 원하는 클래스 타입 또는 프로토콜을 명시하면 된다.
  • 여러 제약을 추가하고싶다면  콤마로 구분하지 않고 where절을 이용한다.
func swapInt<T: BinaryInteger>(_ a: inout T, _ b: inout T){
    let tmp = a
    a = b
    b = tmp
}

func swapFloatingpoint<T: BinaryInteger>(_ a: inout T, _ b: inout T) where T: FloatingPoint{
    let tmp = a
    a = b
    b = tmp
}
// Int타입에만 적용되는 확장과, returnRandom()메서드
extension Stack where T == Int{  // Stack<Element> (X)
    
    // 랜덤한 1개를 리턴하는 메서드
    func returnRandom() -> T{
        return items.randomElement()!
    }
}

// Int가 아닌 타입(Double, String...) 에는 이 확장이 적용되지 않아 returnRandom()메서드가 존재하지 않는다.
// T(타입)은 Equatable프로토콜을 채택한 타입만 함수에서 사용가능하다는 제약⭐️
func findIndex<T: Equatable>(item: T, array: [T]) -> Int?{
    for (index, value) in array.enumerated(){
        if item == value{
            return index
        }
    }
    return nil
}

// 구체화된 함수도 구현 가능하다. 항상 제네릭을 적용시킨 함수만 사용하게되면 불편함이 있을 수 있다.
// 제네릭이 존재하더라도 동일한 함수이름에 구체적인 타입을 명시하면 해당 구체적인 타입의 함수가 실행된다.⭐️

// 문자열의 경우 대소문자를 무시하고 비교하고 싶다면 아래처럼 구현 가능
func findIndex(item: String, array: [String]) -> Int?{
    for (index, value) in array.enumerated(){
        if item.caseInsensitiveCompare(value) == .orderedSame{
            return index
        }
    }
    return nil
}

 


열거형에서의 제네릭

  • 열거형에서 연관값을 가질 때 제네릭으로 정의할 수 있다.
  • 어차피 케이스는 자체가 선택항목중에 하나일 뿐이고, 타입으로 정의할 일은 없다.
enum Pet<T>{
    case dog
    case cat
    case etc(T)
}

let snake = Pet.etc("뱀")
let 무거운동물 = Pet.etc(130)
저작자표시 비영리 동일조건 (새창열림)
    'SWIFT' 카테고리의 다른 글
    • 객체간의 인터페이스를 추상화한다?(Swift)
    • [Swift] - 프로토콜의 연관타입(associatedtype)과 제네릭
    • @escaping, @autoclosure
    • [Swift] - 클로저(Clousre)
    clamp
    clamp
    주니어 iOS개발자의 발악!!!!!!!

    티스토리툴바