일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Python3
- SwiftUI
- 유학토플
- 노드JS
- 스위프트
- 파이썬웹크롤링
- swift
- IOS프로그래밍
- 파이썬중급강의
- 인프런강의
- 파이썬
- 카카오톡채팅봇
- 인프런오리지널
- 리프2기
- 자바스크립트
- 웹크롤링
- 인프런파이썬강의
- 교환학생토플
- nodeJS
- JS
- 인프런파이썬
- 토플
- rxswift
- 인프런
- 프로그래머스
- 토플공부수기
- 우리를위한프로그래밍
- 파이썬중급
- IOS
- uikit
- Today
- Total
먹고 기도하고 코딩하라
Constructing an object of class type with a metatype value must use a ‘required’ initializer 해결하기 본문
Constructing an object of class type with a metatype value must use a ‘required’ initializer 해결하기
사과먹는사람 2024. 7. 6. 11:31
결론만 먼저 쓰자면, metatype이 되는 그 클래스의 init에 required를 붙여주면 해결되는 문제다.
그런데 왜 그래야 할까? 이유는 맥락과 차근차근 설명하는 걸로~
기존에 쓰던 통신 코드를 async-await처럼 사용할 수 있을까 고민하다가 기존 Requester 객체에서 complete 컴플리션 핸들러와 failed 컴플리션 핸들러를 continuation resume하는 것으로 변경하면 되겠다는 생각에 그렇게 수정했다. (추후 포스팅 예정)
통신에 앞서, 먼저 필요한 옵션을 생성하도록 되어 있다. 이 옵션은 프로토콜을 준수한 구체 클래스를 생성해서 집어넣는다. 엔드포인트, 쿼리, 바디 등 여러 가지 옵션을 포함한다. 그런데 엔드포인트만 지정하는 기본 옵션이라면 API 클래스마다 이 옵션까지 공통화할 수는 없을까? 하는 생각이 들어 묶어놓는 작업을 시작했다.
처음에 작성한 초안은 이렇다. 참고로, BaseRequestOption은 상술한 것과 같이 엔드포인트, 쿼리 등을 포함하고 있어 통신에 꼭 필요한 객체이며 클래스 타입이다.
protocol APIProtocol: AnyObject {
associatedtype DataType: Codable
static func getRequester() -> Requester<DataType>
}
class API<DataType: Codable>: APIProtocol {
class RequestOption: BaseRequestOption { }
class func getRequester() -> Requester<DataType> {
Requester(sender, option: self.RequestOption())
}
}
그리고 API를 상속받는 커스텀 API들이 있다. 이런 식으로 사용한다.
class UserApi: API<User> {
class RequestOption: BaseRequestOption {
override func endPoint() -> String { "/user" }
}
// API 클래스마다 중복으로 사용하는 이 함수를 없애는 것이 목표
override class func getRequester() -> Requester<User> {
return Requester(option: RequestOption())
}
}
만든 것까진 좋은데, UserApi 클래스의 getRequester 오버라이드 메소드를 지우고 요청했을 때 원하는 결과가 나오지 않는다. 이유는 UserApi 클래스에서 새로 생성한 RequestOption을 사용하는 것이 아니라 수퍼 클래스인 API 클래스의 빈 RequestOption을 사용하기 때문이다.
이 경우, getRequester 시그니처 매개변수에 Option을 추가하는 방식으로 풀 수는 있다.
하지만 그렇게 하면 이미 사용 중인 메소드들을 전부 변경해야 하기 때문에 해야 할 일이 조금 더 늘어난다. 목표는 함수 시그니처를 변경하지 않고 원래대로 사용할 수 있도록 하는 것이다.
문제 해결을 위해 APIProtocol의 associatedtype을 하나 더 추가했다.
protocol APIProtocol: AnyObject {
associatedtype DataType: Codable
associatedtype RequestOptionType: BaseRequestOption
static func getRequester(_ sender: Requestable, _ option: RequestOptionType) -> Requester<DataType>
}
class API<DataType: Codable, RequestOptionType: BaseRequestOption>: APIProtocol {
class func getRequester(_ sender: Requestable, _ option: RequestOptionType = RequestOptionType()) -> Requester<DataType> {
Requester(sender, option: option)
}
}
이렇게 변경해주면, 하위 클래스에서 getRequester를 호출했을 때 하위 클래스에 선언한 RequestOption을 생성하여 Requester를 구성할 수 있다. (참고로, BaseRequestOption은 클래스 타입이다)
그런데 이 때, RequestOptionType = RequestOptionType()으로 생성하려고 할 때에는 init에 required가 반드시 붙어야 한다. RequestOptionType은 BaseRequestOption을 상속하는 타입이라면 어떤 것이든 될 수 있다. 만약 만들려는 RequestOptionType()에 생성자가 없다면? 베이스 클래스 생성자라도 상속받아야 하지 않는가?
required 자체의 의미는 어떤 클래스의 모든 서브클래스들이 해당 생성자를 구현해야 한다는 것을 의미한다고 문서에 적혀 있다. required를 달면, 모든 서브 클래스들이 그 생성자를 구현해야 한다. 하지만 수퍼클래스의 생성자를 상속받는 것으로도 구현이 가능하다면 추가 구현은 면제된다.
그러니까 이 required 키워드의 진짜 의미는, 컴파일러에게 어떤 클래스가 갖는 서브클래스들이 생성자를 상속받거나 같은 생성자를 새로 구현할 것이라고 고지해주는 것이다. 서브 클래스가 자신만의 생성자를 갖고 있다면 수퍼 클래스의 생성자는 상속받지 않는 것이다. 그렇기 때문에 수퍼클래스는 생성자가 필요하고, 서브클래스는 꼭 필요하지는 않은 것이다(상속받으면 되니까).
References
- Why must constructing an object of class type 'someClass' with a metatype value use a 'required' initializer?
- Why use required initializers in Swift classes?
- Intiailization
'앱 > Swift' 카테고리의 다른 글
UICollectionViewFlowLayout 설정으로 같은 줄의 셀 높이를 동일하게 맞추기 (0) | 2024.08.26 |
---|---|
Swift Concurrency 속 Continuation의 쓰임 (feat. 컴플리션 핸들러 → async-await) (1) | 2024.07.22 |
[iOS] 실무에 Privacy manifest를 적용하자 (0) | 2024.03.28 |
레이아웃 업데이트를 위한 메소드 setNeedsLayout(), ifLayoutNeeded() (0) | 2024.03.17 |
.subscribe(on:)과 .observe(on:) (0) | 2024.03.16 |