如何以通用的方式调用协议中的静态函数?

39

在协议上声明静态函数有没有意义?使用该协议的客户端仍然必须在符合该协议的类型上调用函数,对吗?这破坏了不需要知道符合协议的类型的想法。有没有一种方式可以调用协议上的静态函数,而不需要知道实际符合协议的类型?


1
强烈推荐您查看此问题及其被接受的答案 - mfaani
8个回答

34

好问题。以下是我的个人观点:

在协议中声明静态函数是否有意义?

与在协议中声明实例方法基本相同。

使用协议的客户端无论如何都必须在符合协议的类型上调用该函数,对吗?

是的,就像实例方法一样。

这打破了不必知道符合协议的类型的想法,我认为是这样吧。

不是的。看下面的代码:

protocol Feline {
    var name: String { get }
    static func createRandomFeline() -> Feline
    init()
}

extension Feline {
    static func createRandomFeline() -> Feline {
        return arc4random_uniform(2) > 0 ? Tiger() : Leopard()
    }
}

class Tiger: Feline {
    let name = "Tiger"
    required init() {}
}

class Leopard: Feline {
    let name = "Leopard"
    required init() {}
}

let feline: Feline = arc4random_uniform(2) > 0 ? Tiger() : Leopard()
let anotherFeline = feline.dynamicType.createRandomFeline()

我不知道变量feline里面的真正类型是什么,我只知道它符合Feline。但是我正在调用一个静态协议方法。

有更好的方法吗?

我明白了,您想要调用在协议中声明的静态方法/函数,而不创建符合该协议的值。

像这样:

Feline.createRandomFeline() // DANGER: compiler is not happy now
坦白说,我不知道为什么这是不可能的。

是的,我在发布了我的答案后明白了你的意思。我更新了我的最后一条回答,但不幸的是,我无法给出这个设计决策的原因。 - Luca Angeletti
也许如果我能存储对“ProtocolName”类型的引用,我可以调用该类型上的静态函数?我会快速尝试一下。 - SirRupertIII
哇塞,太棒了。我会用一些代码来更新我的问题。 - SirRupertIII
我不太喜欢以 let feline: Feline ... 开头的最后两行的原因是,我真的不想或者说不需要实例化 Feline 就能调用静态函数。唯一必须这样做的理由就是找到符合该协议的底层类型。 - SirRupertIII
7
static func createRandomFeline() -> Feline声明为static func createRandomFeline() -> Self。现在,你明白原因了吗? :) 由于协议没有self,因此您不能在其上调用方法。 - Sulthan

16

是的,这是可能的:

Swift 3

protocol Thing {
  static func genericFunction()
}

//... in another file

var things:[Thing] = []

for thing in things {
  type(of: thing).genericFunction()
}

2
这不是一个通用函数。 - Martin
你正在对实例进行调用。我认为这个想法是创建一个静态方法来创建实例。 - SmileBot
1
这应该被标记为正确答案。 - Zain

8
感谢@appzYourLife的帮助!你的回答启发了我的答案。
@appzYourLife回答了我的问题。我有一个潜在的问题需要解决,以下代码解决了我的问题,所以我会在这里发布,也许可以帮助与我相同潜在问题的人:
 protocol MyProtocol {
     static func aStaticFunc()
 }

 class SomeClassThatUsesMyProtocolButDoesntConformToIt {

     var myProtocolType: MyProtocol.Type
     init(protocolType: MyProtocol.Type) {
         myProtocolType = protocolType
     }

     func aFunction() {
         myProtocolType.aStaticFunc()
     }
 }

1
你如何创建一个使用了我的协议但并未遵循该协议的 SomeClassThatUsesMyProtocol 的实例? - KoCMoHaBTa
1
假设你有一个结构体S,它遵循了MyProtocol协议。你可以通过调用SomeClassThatUsesMyProtocolButDoesntConformToIt(protocolType: S.self)来使用这个不符合协议的结构体。 - Frizlab

5
我对此案例创建了另一种解决方案。我认为这很简洁明了。
首先,创建一个用于访问实例类型的协议。
protocol TypeAccessible {
    func type() -> Self.Type
}

extension TypeAccessible {
    func type() -> Self.Type {
        return Swift.type(of: self)
    }
}

然后按照以下方式创建您的具体类。关键是您的协议应符合TypeAccessible协议。

protocol FooProtocol: TypeAccessible {
    static func bar()
}

class Foo: FooProtocol {
    static func bar() { }
}

在呼叫现场将其用作

let instance: FooProtocol = Foo()
instance.type().bar()

如果需要更多的使用案例,只需确保您的协议符合TypeAccessible即可。


1
我稍微晚了一步。
这是我为使用typealias“添加”静态属性/函数/类型到协议的解决方案。
例如:
enum PropertyScope {
    case all
    case none
}

struct PropertyNotifications {

    static var propertyDidChange = 
                         Notification.Name("propertyDidChangeNotification")

}

protocol Property {

    typealias Scope = PropertyScope

    typealias Notifications = PropertyNotifications

    var scope: Scope { get set }

}

然后你可以在代码的任何地方这样做:
func postNotification() {
    let scope: Property.Scope = .all

    NotificationCenter.post(name: Property.Notifications.propertyDidChange,
                            object: scope)

}

0

这不是一个答案,而更像是对问题的扩展。假设我有以下代码:

@objc public protocol InteractivelyNameable: Nameable {

    static func alertViewForNaming(completion:@escaping((_ success: Bool, _ didCancel: Bool, _ error: Error?) -> Void)) -> UIAlertController?
}

我有一个通用的视图控制器,可以管理各种类型(通用类型是.fetchableObjectType...基本上是NSFetchResult)。我需要检查特定对象类型是否符合协议,如果符合,则调用它。

类似这样:

    // valid swift code
    if self.dataSource.fetchableObjectType is InteractivelyNameable {

        // not valid swift code
        if let alert = (self.dataSource.fetchableObjectType as InteractivelyNameable).alertViewForNaming(....)
    }

0

使用像Java接口这样的协议很少是一个好主意。它们是元类型,用于定义合同,这是完全不同的事情。

话虽如此,仅仅为了理解,我发现创建协议的静态工厂方法的等效方式最简单和有效的方法是编写一个自由函数。

它应该包含协议的名称,希望这将防止名称冲突,并提高可发现性。

在其他语言中,createP将是P的静态成员,命名为create,并被称为P.create(...),这将大大提高可发现性并保证防止名称冲突。

然而,在Swift中,对于协议来说,这不是一个选项,因此,如果协议出于某种原因真正地被用作接口的替代品,至少在函数名称中包含协议的名称是一个丑陋的解决方法,但仍然比没有好一点。

P.S. 如果目标实际上是实现类的继承层次结构,则联合样式枚举是旨在服务于此目的的工具 :)

protocol P
{
    var x: Int { get }
}

func createP() -> P
{
    if (todayIsMonday())
    {
        return A()
    }
    else
    {
        return B()
    }
}

class A: P
{
    var x = 5
}

class B: P
{
    var x = 7
}

0

我遇到了这样一种情况,需要从两个不同的响应中创建相同的DomainModel对象。所以这种(在protocol中的静态方法帮助了我)方法对我很有帮助。

protocol BaseResponseKeyList: CodingKey {
   static func getNameKey()->Self
}

enum FirstResponseKeyList: String, BaseResponseKeyList {
    case name

    func getNameKey()->FirstResponseKeyList {
       return .name
    }
}

enum SecondResponseKeyList: String, BaseResponseKeyList {
    case userName

    func getNameKey()->SecondResponseKeyList {
       return .userName
    }
}

struct MyDomainModel<T:BaseResponseKeyList> : Decodable {
    var name:String?

    required init(from d:Decoder) {
       do {
            let container = try d.container(keyedBy:T.self)
            name = try container.decode(String.self, forKey:T.getNameKey())
        }catch(_) {
            print("error")
        }
    }
}

let myDomainModel = try JSONDecoder().decode(MyDomainModel <FirstResponseKeyList>.self, from: data)
let myDomainModel2 = try JSONDecoder().decode(MyDomainModel <SecondResponseKeyList>.self, from: data2)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接