NSInvocation에 대해 찾아보다가 Swift 공식 블로그에서 찾은 글을 간단히 번역해본다.
https://developer.apple.com/swift/blog/?id=19
What Happened to NSMethodSignature?
코코아 프레임워크를 Swift로 옮기는 것은 우리 스스로가 우리의 API를 새로운 관점에서 볼 수 있는 좋은 기회가 되었습니다. 우리는 Swift의 목표에 맞지 않는다고 생각되는 클래스들을 찾았고, 우리의 우선순위는 주로 안전성에 맞췄습니다. 예를 들어 동적인 메소드 호출(dynamic method invocateion)과 관련된 클래스들은 Swift에 반입되지 않습니다. 이러한 클래스에는 NSInvocation
과 NSMethodSignature
가 있지요.
우리는 최근에 이 클래스들이 빠져있음을 발견한 한 개발자로부터 버그 리포팅을 받았습니다. 이 개발자는 Objctive-C에서 메소드 인자들의 타입을 검사하는데 NSMethodSignature
를 사용하고 있었고, Swift로 마이그레이션하는 과정에서 이 클래스를 사용할 수 없다는 것을 알았습니다. 실제 그 코드는 인자 타입이 정해지지 않은 HTTP 핸들러릘 받도록 되어 있었습니다. 예를 들면 이런 것들이죠.
func handlerRequest(request: HTTPRequest,
queryStringArguments: [String: String]) { }
func handlerRequest(request: HTTPRequest, jsonBody: JSON) { }
Objective-C에서는 NSMethodSignature
를 사용하여 첫번째 메소드는 [String: String]
타입의 인자를 필요로하며, 두 번째 메소드는 JSON
타입의 인자를 필요로 한다는 것을 알아낼 수 있습니다. 하지만 Swift는 충분히 강력한 언어이며, 언어가 제공하는 기능만으로도 이러한 시나리오를 다룰 수 있습니다.
아래는 같은 문제를 해결하는 Swift 코드입니다.
struct HTTPRequest { /*...*/ }
protocol HTTPHandlerType {
typealias Data
func handle(request: HTTPRequest, data: data) -> Bool
}
먼저 우리는 HTTPRequest
를 다룰 수 있는 핸들러를 위한 프로포토콜을 하나 정의합니다. 이 ㅍ로토콜은 매우 심플하고 하나의 메소드만을 필요로합니다. 왜 프로토콜일가요? 프로토콜은 실제 구현을 클라이언트(여기서는 해당 프로토콜을 따르는 타입)에 이관하여 큰 유연성을 확보할 수 있기 때문입니다. 만약 HTTPHandler
라는 클래스를 만든다면, 클라이언트쪽에서는 클래스를 만들어도 되지만 구조체나 열거체를 써도 무방합니다.
class HTTPServer {
func addHandler<T: HTTPHandlerType>(handler: T) {
handlers.append { (request: HTTPRequest, args: Any) -> Bool in
if let typedArgs = args as? T.Data {
return hander.handle(request, data: typedArgs)
}
return false
}
}
...
}
또, HTTPServer
클래스는 HTTPHandlerType
을 파라미터로 받는 제네릭 메소드를 갖고 있습니다. 핸들러의 연관타입을 이용하여 args
파라미터는 조건에 따라 특정한 타입으로 다운캐스팅할 수 있습니다. 여기에 프로토콜의 강점이 있습니다. HTTPServer는 핸들러가 요청에 대해 어떤식으로 반응하는지에 대해서는 몰라도 되며, 핸들러 그 자체의 어떤 특성에 대해서도 알 필요가 없습니다. 우리가 알 필요가 있는 것은 그 값이 요청을 핸들링할 수 있다는 것 뿐입니다.
위 예에서 addHandler는 두 번째 인자의 타입이 일치하는 경우에 핸들러 호출값을 리턴하는 클로저를 배열에 추가해줍니다.
class HTTPServer {
//...
private var handlers: [(HTTPRequest, Any) -> Bool)] = []
func dispatch(req: HTTPRequest, args: Any) -> Bool {
for handler in handlers {
if hanlder(req, args) {
return true
}
}
return false
}
}
서버 객체가 요청을 받으면 서버는 핸들러중에서 주어진 데이터 타입을 처리할 수 있는 핸들러가 있고, 그 중에서 true를 내는 핸들러가 하나라도 있다면 true를 그렇지 않다면 false를 리턴할 것입니다.
그러면 커스텀 핸들러를 만들어보겠습니다.
class MyHandler: HTTPHandlerType {
func handle(request: HTTPRequest, data: Int) -> Bool {
return data > 5
}
}
let server = HTTPServer()
server.addHandler(MyHandler())
server.dispatch(HTTPRequest(...), args: "x") // --> false
server.dispatch(HTTPRequest(...), args: 5) // --> false
server.dispatch(HTTPRequest(...), args: "10") // --> false
server.dispatch(HTTPRequest(...), args: 10) // --> true
프로토콜과 제네릭을 결합하는 테크닉을 동원하여 다양한 타입의 HTTP 핸들러를 등록하는 서버를 생성하는 Swift 코드를 작성할 수 있습니다. 이러한 접근은 컴파일 시점에 타입 안정성을 보장받을 수 있으며, 실행 성능또한 매우 좋습니다.