Reputation: 4061
In objective-C we can (by importing the language's runtime header file) do the following:
//Pass a service (usually an object) and ANY protocol
- (void)registerService:(id)service forProtocol:(Protocol *)protocol
{
//Grab the protocol's name (that's why we import runtime.h, it contains the protocol_getname mehod)
NSString *protocolName = [NSString stringWithUTF8String:protocol_getName(protocol)];
//If the object we passed does not conform to the protocol, inform and break
if (![service conformsToProtocol:protocol])
{
NSLog(@"Service: %@ does not conform to protocol: %@", service, protocolName);
return;
}
//Else add service in a collection (array, dictionary) for later use
self.services[protocolName] = service;
}
I use this in obj-C as a "poor man's IOC container", a simple registry used for injecting dependencies.
//The interested party uses this method to obtain the dependency it needs by asking for the object that is registered as responsible for conforming to the Protocol parameter
- (id)serviceForProtocol:(Protocol *)protocol
{
id result;
NSString *protocolName = [NSString stringWithUTF8String:protocol_getName(protocol)];
//Look for the service that conforms to the protocol in the registry dictionary,
result = self.services[protocolName];
//if there is no object meeting the criteria, inform/alert
if (result == nil)
{
NSLog(@"No class registered for protocol: %@", protocolName);
}
//and return the result
return result;
}
Trying to replicate this behaviour in Swift, I found out that we don't have access to the language's equivalent "runtime" API like we do in obj-C (yet), and understandably so since swift is a work in progress and giving people this kind of access is undoubtedly risky.
But this also means that we can't use Protocol in the same way anymore i.e. in the sense of ANY protocol.
The first possible workaround that comes to mind is a mix of generics, Any and where, but that feels too much for something that used to be simple.
So, my question is: What are some proposed solutions for passing around Protocol (as in ANY Protocol) in Swift?
EDIT: I had some success using the Metatype Type introduced in Swift which makes sense in terms of language design but also does not (yet) provide the ability to provide a "String" representation of a Metatype that could be used as a key in a dictionary.
This is of course a feature that could be added as the language matures.
Upvotes: 7
Views: 3157
Reputation: 4582
I tried for a long time, with many different ways (pointers, Protocol
), and the only solution without @objc
and pure Swift that I found is with closure. Then, you need use a closure to use a protocol and return a value
protocol Proto { }
protocol Proto2 { }
class Foo: Proto { }
class Bar: Proto, Proto2 { }
class Baz: Proto2 { }
class Qux { }
func printConforms(classList: [AnyClass], protoCond: (AnyClass) -> Any?) {
for i in classList {
print(i, terminator: " -> ")
if protoCond(i) != nil {
print("is subscriber")
} else {
print("NOT IS subscriber")
}
}
}
let myClasses: [AnyClass] = [Foo.self, Bar.self, Baz.self, Qux.self]
printConforms(classList: myClasses, protoCond: { $0 as? Proto.Type })
More complete example: https://gist.github.com/brunomacabeusbr/eea343bb9119b96eed3393e41dcda0c9
Edit
Another better solution is using generics, for example:
protocol Proto { }
class Foo: Proto { }
class Bar: Proto { }
class Baz { }
func filter<T>(classes: [AnyClass], byConformanceTo: T.Type) -> [AnyClass] {
return classes.filter { $0 is T }
}
filter(classes: [Foo.self, Bar.self, Baz.self], byConformanceTo: Proto.Type.self)
// return [Foo.self, Bar.self]
Upvotes: 0
Reputation: 122449
What have you tried?
Does something like this not work:
import Foundation
func registerService(service: NSObjectProtocol, forProtocol prot: Protocol) {
let protocolName = NSStringFromProtocol(prot)
if (!service.conformsToProtocol(prot)) {
println("Service: \(service) does not conform to protocol: \(protocolName)")
return
}
//...
}
Upvotes: 1