The proxy pattern is very useful when you need to forward all calls from one object to another. In our case, we could have implemented CachedNetworking as a proxy on URLSession, effectively giving additional caching capabilities to URLSession.
When debugging your applications, it may be useful to log information about networking calls, such as duration, whether they come from the cache, and so on.
The proxy should have the exact same signature as the object being proxied. We can either subclass or use a common interface in order to achieve this:
class LoggingCachedNetworking: CachedNetworking {
private func log(request: URLRequest) {
// TODO: implement proper logging
}
private func log(response: URLResponse?,
data: Data?,
error: Error?,
fromCache: Bool,
forRequest: URLRequest) {
// TODO: implement proper logging
}
override func run(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?, Bool) -> Void) {
self.log(request: request)
super.run(with: request) { (data, response, error, fromCache) in
self.log(response: response,
data: data,
error: error,
fromCache: fromCache,
forRequest: request)
completionHandler(data, response, error, fromCache)
}
}
}
And now, for example, at application initialization, we may want to leverage compilation directives like this:
#if DEBUG
let networking = LoggingCachedNetworking()
#else
let networking = CachedNetworking()
#endif
Depending on whether your program is built with the DEBUG flag set, this will use (or not) the proxied CachedNetworking.
Coupled with techniques such as dependency injection, the proxy pattern let you design your programs by enhancing an existing object's features without changing code that is known to be working. This unobtrusive pattern is very interesting if you cannot or do not want to change the implementation of the underlying features.