What Happened to NSMethodSignature?

UPDATE: We’ve added the Request.playground file to this post so you can download it and easily experiment with the code yourself.

Bringing the Cocoa frameworks to Swift gave us a unique opportunity to look at our APIs with a fresh perspective. We found classes that we didn't feel fit with the goals of Swift, most often due to the priority we give to safety. For instance, some classes related to dynamic method invocation are not exposed in Swift, namely NSInvocation and NSMethodSignature.

We recently received a bug report from a developer who noticed this absence. This developer was using NSMethodSignature in Objective-C to introspect the types of method arguments, and in the process of migrating this code to Swift, noticed that NSMethodSignature is not available. The code being migrated could accept HTTP handlers of varying signatures, such as:

func handleRequest(request: HTTPRequest, queryStringArguments: [String: String]) { }
func handleRequest(request: HTTPRequest, jsonBody: JSON) { }

In Objective-C, NSMethodSignature can be used to determine that the API of the first method would require a [String: String] argument, and the second method would require a JSON value. However, Swift is a powerful language and can easily handle this scenario without using NSMethodSignature, and in a way that doesn't undermine the help that the compiler provides for type and memory safety.

Here is an alternative way to solve the same problem in Swift:

struct HTTPRequest {
	// ...
}

protocol HTTPHandlerType {
	typealias Data

	/// :returns: true if the request was handled; false otherwise
	func handle(request: HTTPRequest, data: Data) -> Bool
}

First, we'll use a protocol to define that whatever is going to handle our HTTPRequest does so via this interface. This protocol is very simple, with only a single method.

Why use a protocol here, instead of subclassing an HTTPHandler class? Because protocols give the flexibility of leaving the implementation details up to the clients of this code. If we were to make an HTTPHandler class, we would require clients to also use classes, forcing upon them the semantics of reference types. However, by using a protocol, clients can decide for themselves the appropriate type to use in their code, whether it be class, struct, or even enum.

class HTTPServer {
	func addHandler<T: HTTPHandlerType>(handler: T) {
		handlers.append { (request: HTTPRequest, args: Any) -> Bool in
			if let typedArgs = args as? T.Data {
				return handler.handle(request, data: typedArgs)
			}
			return false
		}
	}

	// ...
}

Next, our HTTPServer class has a generic method that accepts an HTTPHandlerType as a parameter. By using the handler's associated type, it can perform the conditional downcast of the args parameter to determine if this handler should be given an opportunity to handle the request. Here we can see the benefit of defining HTTPHandlerType as a protocol. The HTTPServer doesn't need to know how the handler is reacting to the request, nor does it even need to care about the nature of the handler itself. All it needs to know is that the value can handle requests.

class HTTPServer {
	// ...

	private var handlers: [(HTTPRequest, Any) -> Bool] = []

	func dispatch(req: HTTPRequest, args: Any) -> Bool {
		for handler in handlers {
			if handler(req, args) {
				return true
			}
		}
		return false
	}
}

When our HTTPServer receives a request, it will iterate through its handlers and see if any can deal with the request.

Now we can easily create a custom HTTPHandlerType with varying argument types and register it with the HTTPServer:

class MyHandler : HTTPHandlerType {
	func handle(request: HTTPRequest, data: Int) -> Bool {
		return data > 5
	}
}

let server = HTTPServer()
server.addHandler(MyHandler())
server.dispatch(HTTPRequest(...), args: "x") // returns false
server.dispatch(HTTPRequest(...), args: 5)   // returns false
server.dispatch(HTTPRequest(...), args: 10)  // returns true

With a combination of protocols and generics, we have written Swift code to elegantly create and register HTTP handlers of varying types. This approach also lets the compiler guarantee type safety, while ensuring excellent runtime performance.

All Blog Posts