[Swift] Generics

Inwoo Hwang·2021년 8월 26일
0

Swift

목록 보기
1/8
post-thumbnail

Warning: 이해한 부분을 최대한 남기고 정리하려 남긴 글 입니다. 틀린 부분이 있을 수 있습니다. 이점 유의하고 읽어주시면 감사할 것 같습니다. 그리고 틀린 부분 알려주시면 바로바로 고치도록 하겠습니다.

지네릭[Generics]

지네릭[Generics]은?

*애플 공식 문서에 따르면 Generics는 더 유연하고 재사용 가능한 함수와 타입의 코드를 작성하는 것을 가능하게 해준다고 합니다.

Generics은 Swift가 제공하는 가장 강력한 기능 중 하나이고 Swift 공부를 하면서 우리가 자주 접하던 Array 또는 Dictionary 또한 Generics code로 설계되었다고 해요. Int 타입 그리고 String타입과 같이 다양한 타입의 Array를 선언할 수 있는 이유 그리고 모든타입을 딕셔너리에 저장할 수 있는 이유는 언급했던 자료구조가 모두 Generics Code로 설계되었기에 가능한 것이었습니다.

어떠한 타입에 모두 대응할 수 있게 해 준다는 것이 Generics를 사용하는 것의 가장 큰 메리트입니다.

지네릭[Generics]은 어떻게 활용할 수 있을까?

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"

swapTwoInts() 메서드를 활용하여 someIntanotherInt 의 값을 바꿔줄 수 있습니다. 해당 메서드는 Int 타입의 값을 가진 변수만 인자값으로 받을 수 있게 제한 되어 있습니다.

그래서 만약에 String값을 가진 두 변수를 해당 메서드에 넣게 되면 타입이 다르기 때문에 작동이 되지 않습니다. 그렇기 때문에 String 타입에 맞는 메서드를 하나 더 생성해야 합니다.

이렇게 되면 중복되는 메서드를 한 번 더 작성해야 하고 그러면 코드길이도 늘어나기에 그렇게 좋지 않은 방법입니다. 이런 중복을 피하기 위해 Generic Function을 사용하면 됩니다.

Generics Function

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

함수명 뒤에 placeholder를 선언하고 파라미터를 T로 선언합니다.

여기서 사용된 플레이스홀더는 swift에게 타입을 런타임 단계에서 정의할 것이라고 알려주는 것입니다. 그러면 사용자는 매개변수 정의나 반환타입 또는 함수 자체에 있는 타입 정의 대신에 플레이스홀더 타입을 사용할 수 있습니다.

한 가지 명심해야 할점은 placeholder를 어떠한 타입으로 한번 정의하고 나면 다른 모든 placeholder는 해당 타입으로 간주한다는 점입니다. 따라서 placeholder로 정의된 변수나 상수는 반드시 해당 타입의 인스턴스가 됩니다.

위 함수를 사용하면 다른타입에 필요한 메서드를 추가로 생성하지 않고 해당 메서드를 재사용할 수 있습니다.

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
var a = 5
var c = "My String 1"
swapGeneric(a: &a, b:&c)
// cannot convert values of type String to expected argument type Int

여기서 함수가 Integer타입을 기대하는 이유는 함수에 전달한 첫 번째 매개변수가 Integer 타입의 인스턴스이기 때문입니다. 이 때문에 placeholder T로 정의된 함수 안의 모든 제네릭 타입은 Integer 타입이 되는 것입니다.

여러 개의 지네릭 타입을 사용할 수도 있습니다.

func testGeneric<T,E>(a:T, b:E) {
	// 코드 구현
}

이렇게 하면 placeholder T, E 에서 각각 다른 타입을 설정 할 수 있습니다. 해당 함수는 서로 다른 타입의 매개변수를 받기 때문에 값을 서로 교환할 수는 없습니다.

Generic의 제약사항

func genericEqual<T>(a: T, b: T) -> Bool {
  return a == b
}

위 코드는 문제가 없어 보이지만 binary operator '==' cannot be applied to two 'T' operands 라는 오류가 발생하게 된다. 그 이유는 위에서 설명한 것 처럼 T placeholder는 swift에서 런타임시 타입이 정의가 되고 이 때문에 코드가 컴파일 되는 시점에는 인자의 타입을 모르기 때문에 swift는 타입의 동등 연산자를 사용할 수 있는지를 알지 못하고 오류로 이어지게 됩니다. 스위프트에게 해당 타입이 어떠한 기능을 갖고 있을 것이라는 것을 알려주는 방법은 타입 제약 을 활용하는 것입니다.

타입제약[Type Constraint]

타입 제약에서는 generic type은 반드시 구체적인 클래스를 상속하거나 특정 프로토콜을 채택해야 한다고 명시합니다. 타입 제약은 지네릭 타입에서 부모 클래스나 프로토콜에 정의된 메서드나 프로퍼티를 사용할 수 있게 해줍니다. 이를 토대로 위 코드를 수정 해 보자면

func genericEqual<T: Comparable>(a: T, b: T) -> Bool {
  return a == b
}

위와 같이 T placeholder 뒤에 타입 또는 프로토콜 제약을 위치 시키면 오류 없이 메서드를 구현할 수 있습니다.

func testGenericWithLimit<T: CertainClass, E: CertainProtocol>(a: T, b: E) {
  
}

지네릭 타입

struct List<T> {  var items = [T]()    mutating func add(item: T) {    items.append(items)  }    func getItemsAtIndex(index: Int) -> T? {    if items.count > index {      return items[index]    } else  {      return nil    }  }}

위와 같이 여러가지 타입으로 인스턴스화 될 수 있는 List 구조체를 만들 수 있다.

위에 구현된 구조체를 다양한 타입을 담는 List로서 활용할 수 있습니다. 아래와 같이 말이죠.

var stringList = List<String>()var intList = List<Int>()stringList.add(item: "I am String")intList.add(item: 1)

연관 타입

프로토콜에서 선언한 지네릭 타입을 연관 타입이라 부릅니다.

연관 타입은 프로토콜 내에서 타입 대신에 사용될 수 있는 placeholder명을 정의합니다.

실제로 사용되는 타입은 프로토콜에 채택되기 전까지는 명시되지 않습니다.

연관타입은 associatedtype 키워드를 사용해 명시합니다.

예를 들면 아래와 같이 연관타입을 사용할 수 있습니다.

protocol ListMakeable {  associatedtype E  var items: [E] { get set }  mutating func add(item: E)}

해당 프로토콜을 따르는 타입은 아래와 같이 생성해 볼 수 있습니다.

struct MyIntList: ListMakeable {  var items: Int = []  mutating func add(item: Int) {    items.append(item)  }}

참조:

지네릭 (Generics) - The Swift Language Guide (한국어) (gitbook.io)

Swift ) Generic (tistory.com)

profile
james, the enthusiastic developer

0개의 댓글