Search code examples
swiftvariablesgraphqlprotocolsinit

Swift question: how can we init an non-optional variable in a if else block if it's of protocol type?


In Swift, we can init a non-optional variable not immediately but later on in a if else block, for example:

let result: Bool
if something {
   result = computeSomething()
} else {
   result = computeSomethingElse()
}

But what if my variable is of protocol type? (in my example, I'd like to do this with a GraphQLMutation which is a protocol):

let mutation: GraphQLMutation
if something {
   mutation = StartMutation()
} else {
   mutation = StopMutation()
}
self.graphQLDataSource.set(mutation: mutation)

Swift compiler error says: Protocol 'GraphQLMutation' can only be used as a generic constraint because it has Self or associated type requirements

Any idea to be able to do this and avoid code repetition?


Solution

  • It does work with protocols:

    protocol Foo {}
    struct A: Foo {}
    class B: Foo {}
    
    let x: Foo
    if Bool.random() {
        x = A()
    } else {
        x = B()
    }
    

    It just doesn't work with protocols that have an associated type. You can only use it in a generic function. Here's some code showcasing it:

    protocol Foo {
        associatedtype T
    }
    struct A: Foo {
        typealias T = Int
    }
    class B: Foo {
        typealias T = String
    }
    
    func x<Foo>(_ test: Bool) -> Foo? {
        let x: Foo?
        if test {
            x = A() as? Foo
        } else {
            x = B() as? Foo
        }
        return x
    }
    
    let a1: A? = x(true)  // => A
    let a2: A? = x(false) // => nil
    
    let b1: B? = x(true)  // => nil
    let b2: B? = x(false) // => B
    
    • For a1 we get an instance of A as the cast A() as? Foo worked because it has a type of Foo with an associated type Int required by the let a1: A?.

    • For a2 we get nil as the cast B() as? Foo fails because it cannot be cast into Foo with an associated type Int required by the let a2: A?.

    • For b1 we get nil as the cast A() as? Foo fails because it cannot be cast into Foo with an associated type String required by the let b1: B?.

    • For b2 we get an instance of B as the cast B() as? Foo worked because it has a type of Foo with an associated type String required by the let b2: B?.