[UIKit] Opaque Type vs Protocol & Associated Type & Generics

Junyoung Park·2022년 12월 31일
0

UIKit

목록 보기
141/142
post-thumbnail

https://www.youtube.com/watch?v=SPhATsEQR74

Opaque Type vs Protocol & Associated Type & Generics

Generics

  • 어떤 타입이더라도 적용 가능한 유연성을 보장하는 방법
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
        let tempA = a
        a = b
        b = tempA
    }
  • 들어오는 데이터의 타입을 모르더라도 받아들일 수 있음
  • 특정 프로토콜을 따르는 데이터 타입만을 받아들일 수도 있음(Decodable 등을 따르는 데이터 타입만을 파라미터로 받아들여 디코딩한 결과를 리턴하는 게 대표적인 예시)
	class someClass { ... }
    
    protocol someProtocol { ... }  
    
    func someFunction<T: someClass, U: someProtocol>(someT: T, someU: U) {
        // handle T and U
    }
  • 스위프트가 제공하는 거의 모든 유용한 공통 오퍼레이터가 제네릭을 통해 구현
public init<H>(_ base: H) where H: Hashable
  • 위와 같이 Hashable 프로토콜을 따르는 타입을 통해 이니셜라이즈한다는 뜻

Associated Types

  • 프로토콜을 보다 유연하게 사용하고자 할 때 사용 가능
protocol someProtocol {
    associatedtype SomePlaceholder
    
    func someFunctionUsing(type: SomePlaceholder)
    func someOtherFunctionReturning() -> SomePlaceholder
}
  • 특정 프로토콜 내부에서 associatedtype을 통해 연관 타입을 선언한다면, 해당 프로토콜을 따르는 구조체/클래스에서 해당 타입을 명확하게 지정 가능
  • 현 시점에서는 불분명한 타입에 지나지 않음
struct someStruct: someProtocol {
    typealias SomePlaceholder = Int
    
    func someOtherFunctionReturning() -> Int {
        // return SomePlaceholder as Int
    }
    
    func someFunctionUsing(type: Int) { }
}
  • typealias를 통해 해당 프로토콜이 지정한 associatedtype을 구체적으로 지정
  • 해당 타입을 리턴/파라미터로 사용하는 함수에 현 시점의 구체적인 타입을 그대로 적용 가능
protocol stackable {
    associatedtype stackType
    var stack: [stackType] { get set }
    func pop() -> stackType
    func push(_ item: stackType)
}

struct stringStack: stackable {
    var stack: [String]
    
    func pop() -> String {
        // return pop
    }
    func push(_ item: String) {
        // handle push
    }
    typealias stackType = String
}
  • 연관 타입을 해당 프로토콜을 따르는 구체적인 구조체/클래스에서 typealisas를 통해 직접 설정 가능하다는 게 핵심
protocol Constainer {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
  • 컨테이너 프로토콜은 Equatable을 따르는 타입을 연관 타입으로 받아, mutating, subscript 등 특정 함수를 내재하는 프로토콜

Opaque + associatedtype

  • 프로토콜이 사용한 연관타입은 오파크와 함께 사용할 시 큰 시너지
enum CardType {
    case gold
    case platinum
}

protocol CardNumberProtocol { }

extension String: CardNumberProtocol { }
  • 특정한 프로토콜이 존재한다고 가정
protocol Card {
    associatedtype CardNumber: CardNumberProtocol
    var type: CardType { get set }
    var limit: Int { get set }
    var number: CardNumber { get set }
    func validateCardNumber(number: CardNumber)
}
  • 카드 프로토콜의 연관 타입으로 선언된 CardNumber는 위의 CardNumberProtocol을 준수하는 타입임
  • 타입, 리미트, 넘버 등 프로토콜 내 변수 및 validateCardNumber와 같은 연관 타입을 인자로 쓰는 함수 역시 존재
struct VisaCard: Card {
    typealias CardNumber = String
    var type: CardType = .gold
    var limit: Int = 100_000
    var number: String = "1111 2222 3333 4444"
    func validateCardNumber(number: String) {
        
    }
}

struct MasterCard: Card {
    typealias CardNumber = String
    var type: CardType = .platinum
    var limit: Int = 500_000
    var number: String = "1111 2222 3333 4444"
    func validateCardNumber(number: String) {
        
    }
}
  • 위의 프로토콜을 따르는 구체적인 구조체
  • 연관타입이 각 구조체 내부에서 typealias로 선언되어 구체화되어 있음
func getLoadEligibility() -> Bool {
    getUserCard().limit >= getLoanEligibilityCard().limit
}

func getUserCard() -> some Card {
    MasterCard()
}

func getLoanEligibilityCard() -> some Card {
    VisaCard()
}
  • 함수의 리턴 타입으로 카드 프로토콜 자체를 리턴하려면 컴파일러가 연관타입으로 감춰놓은 실제 타입을 알지 못하기 때문에 컴파일러 에러가 발생
  • some이라는 오파크 타입 키워드를 통해 해당 타입을 '감싸'줄 때 비로소 리턴할 수 있는데, 실제로 제네릭의 완전히 반대 방향으로 작동
  • 실제 리턴하는 구체적인 실제 타입을 Card로는 알아차리지 못하기 때문에(연관 타입의 실제 표현은 각 구조체 내부에서 하도록 설정되었음), some 키워드를 통해 어떤 타입인지는 모르겠으나 해당 프로토콜을 따르는 구조체라서 인지 가능
  • SwiftUI 프레임워크의 모든 UI를 그리는 구조체가 some View를 따르는 연산 프로퍼티인데, 이 또한 같은 맥락
profile
JUST DO IT

0개의 댓글