在Swift中将协议类型作为参数传递

时间:2021-05-07 11:52:16

In objective-C we can (by importing the language's runtime header file) do the following:

在objective-C中,我们可以(通过导入语言的运行时头文件)执行以下操作:

//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.

我在obj-C中使用它作为“穷人的IOC容器”,一个用于注入依赖项的简单注册表。

//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.

试图在Swift中复制这种行为,我发现我们无法像在obj-C中那样访问该语言的等效“运行时”API,并且可以理解,因为swift是一项正在进行的工作并且给予了人们这种访问无疑具有风险。

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.

我想到的第一个可能的解决方法是混合使用泛型,Any和where,但对于过去简单的东西来说,感觉太过分了。

So, my question is: What are some proposed solutions for passing around Protocol (as in ANY Protocol) in Swift?

所以,我的问题是:在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.

编辑:我使用Swift中引入的Metatype类型取得了一些成功,这在语言设计方面是有意义的,但也没有提供能够提供元数据类型的“字符串”表示的能力,该元数据类型可用作字典。

This is of course a feature that could be added as the language matures.

这当然是一种可以在语言成熟时添加的功能。

2 个解决方案

#1


1  

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
  }

  //...
}

#2


0  

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

我尝试了很长时间,有许多不同的方法(指针,协议),并且我找到的唯一没有@objc和纯Swift的解决方案是关闭的。然后,您需要使用闭包来使用协议并返回一个值

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

更完整的例子: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]

#1


1  

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
  }

  //...
}

#2


0  

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

我尝试了很长时间,有许多不同的方法(指针,协议),并且我找到的唯一没有@objc和纯Swift的解决方案是关闭的。然后,您需要使用闭包来使用协议并返回一个值

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

更完整的例子: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]