먹고 기도하고 코딩하라

Constructing an object of class type with a metatype value must use a ‘required’ initializer 해결하기 본문

앱/Swift

Constructing an object of class type with a metatype value must use a ‘required’ initializer 해결하기

사과먹는사람 2024. 7. 6. 11:31
728x90
728x90

 

결론만 먼저 쓰자면, 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

 

 

728x90
반응형
Comments