How can I call a static function on a protocol in a generic way?
Asked Answered
S

8

39

Is there a point to declaring a static function on a protocol? The client using the protocol has to call the function on a type conforming to the protocol anyway right? That breaks the idea of not having to know the type conforming to the protocol IMO. Is there a way to call the static function on the protocol in a way where I don't have to know the actual type conforming to my protocol?

Scheffler answered 13/5, 2016 at 21:5 Comment(1)
Highly recommend you to see this question and its accepted answerFrohne
B
34

Nice question. Here is my humble point of view:

Is there a point to declaring a static function on a protocol?

Pretty much the same as having instance methods declared in a protocol.

The client using the protocol has to call the function on a type conforming to the protocol anyway right?

Yes, exactly like instance functions.

That breaks the idea of not having to know the type conforming to the protocol IMO.

Nope. Look at the following code:

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()

I don't know the real type inside the variable feline. I just know that it does conform to Feline. However I am invoking a static protocol method.

Is there a better way to do this?

I see, you would like to call a static method/function declared in a protocol without creating a value that conforms to the protocol.

Something like this:

Feline.createRandomFeline() // DANGER: compiler is not happy now

Honestly I don't know the reason why this is not possible.

Bamboozle answered 13/5, 2016 at 21:30 Comment(5)
Yes, I understood what you mean after having posted my answer. I updated my last answer but unfortunately I cannot provide a reason behind this design decision.Bamboozle
Maybe if I can store a reference to ProtocolName's type I can call the static function on that type?? I'll mess with it real quick.Scheffler
Wooooooooow, that works. I'll update my question with some code.Scheffler
The reason why I don't really like the last two lines starting with let feline: Feline ... is that I really don't want or need to have an instance of Feline in order to call the static function. The only reason that has to be done is to find out the underlying type conforming to that protocol.Scheffler
Declare static func createRandomFeline() -> Feline as static func createRandomFeline() -> Self. Now, do you see the reason? :) A protocol has no self, therefore you cannot call methods on it.Photoconduction
C
16

yes this is possible:

Swift 3

protocol Thing {
  static func genericFunction()
}

//... in another file

var things:[Thing] = []

for thing in things {
  type(of: thing).genericFunction()
}
Crustaceous answered 11/6, 2017 at 4:28 Comment(3)
this is not a generic function.Frizzly
You're calling it on instances. I think the idea is to have a static method that creates the instances.Inflexible
This should be marked as the correct answerPrevailing
S
8

Thank you @appzYourLife for the help! Your answer inspired my answer.

@appzYourLife answered my question. I had an underlying issue I was trying to resolve and the following code resolves my issue, so I'll post this here, maybe it helps someone with my same underlying question:

 protocol MyProtocol {
     static func aStaticFunc()
 }

 class SomeClassThatUsesMyProtocolButDoesntConformToIt {

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

     func aFunction() {
         myProtocolType.aStaticFunc()
     }
 }
Scheffler answered 13/5, 2016 at 22:11 Comment(2)
How do you create an instance of SomeClassThatUsesMyProtocolButDoesntConformToIt?Allotrope
@Allotrope Assuming you have (eg.) struct S: MyProtocol {…}, you simply call SomeClassThatUsesMyProtocolButDoesntConformToIt(protocolType: S.self)Helicline
C
5

I created another solution for this case. IMHO this is quite clean and simple.

First, create a protocol for accessing instance type.

protocol TypeAccessible {
    func type() -> Self.Type
}

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

then create your concrete class as here. The point is your protocol should conform to TypeAccessible protocol.

protocol FooProtocol: TypeAccessible {
    static func bar()
}

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

On call site use it as

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

For further use cases, just make sure your protocols conform to TypeAccessible and that's all.

Caboodle answered 10/12, 2019 at 21:30 Comment(0)
M
1

A little late to the party on this one.

Here's my solution for "adding" static properties/functions/types to a protocol using typealias.

For example:

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 }

}

Then you can do this anywhere in your code:

func postNotification() {
    let scope: Property.Scope = .all

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

}
Markup answered 27/2, 2018 at 17:2 Comment(0)
E
0

Using protocols like Java interfaces is rarely a good idea. They are meta types, meant for defining contracts, which is an entirely different kind of thing.

That being said, just for the point of understanding, I find the most simple and effective way for creating the equivalent of a static factory method of a protocol to write a free function.

It should contain the protocol's name, hoping that that will prevent name clashes, and improve discoverability.

In other languages, createP would be a static member of P, named create and be called as P.create(...), which would drastically improve discoverability and guarantee to prevent name clashes.

In swift, though, this is not an option for protocols, so if protocols are for some reason really actually used as a replacement for interfaces, at least including the protocol's name in the function's name is an ugly workaround that's still slightly better than nothing.

P.S. in case the goal is actually to achieve something like an inheritance hierarchy with structs, union style enums are the tool that's meant to serve that purpose :)

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
}
Edgeways answered 15/12, 2016 at 9:3 Comment(0)
T
0

This isn't an answer so much as it is an extension to the question. Say I have:

@objc public protocol InteractivelyNameable: Nameable {

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

And I have a generic view controller that manages various types (generic type is .fetchableObjectType... basically NSFetchResult). I need to check if a specific object type conforms to the protocol, and if so, invoke it.

something like:

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

        // not valid swift code
        if let alert = (self.dataSource.fetchableObjectType as InteractivelyNameable).alertViewForNaming(....)
    }
Tmesis answered 15/9, 2017 at 7:0 Comment(0)
S
0

I had a situation where I need to create same DomainModel object from 2 different response. so this (static method in protocol helped me) approach helped me.

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)
Surf answered 23/1, 2019 at 15:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.