19

I have a protocol defined:

protocol Usable {
    func use()
}

and a class that conforms to that protocol

class Thing: Usable {
    func use () {
        println ("you use the thing")
    }
}

I would like to programmatically test whether or not the Thing class conforms to the Usable protocol.

let thing = Thing()

// Check whether or not a class is useable
if let usableThing = thing as Usable { // error here
    usableThing.use()
}
else {
    println("can't use that")
}

But I get the error

Bound value in a conditional binding must be of Optional Type

If I try

let thing:Thing? = Thing()

I get the error

Cannot downcast from 'Thing?' to non-@objc protocol type 'Usable'

I then add @objc to the protocol and get the error

Forced downcast in conditional binding produces non-optional type 'Usable'

At which point I add ? after the as, which finally fixes the error.

How can I achieve this functionality with conditional binding with a non-@objc protocol, the same as in the "Advanced Swift" 2014 WWDC Video?

JuJoDi
  • 14,627
  • 23
  • 80
  • 126
  • I'm sure this is a contrived case, but it doesn't make sense. The compiler already knows explicitly that thing abides by the Usable protocol. There is no need to test it as it as a test would never fail. – drewag Jun 08 '14 at 21:35
  • @drewag what if I had a bunch of classes, some which conform, and some which do not. Then I might want to be able to check whether or not each class conforms. Anyway, this was just a demonstration of the error, it's obvious that the Thing class conforms. – JuJoDi Jun 08 '14 at 22:39
  • Ya, if you use AnyObject as the type for thing, I cannot find a way to check if it implements Usable (which is why I didn't provide an answer). Neither answers below compile if thing is AnyObject without Usable being an objc protocol – drewag Jun 08 '14 at 22:44

5 Answers5

33

You can get it to compile by making the cast as Usable? instead of as Usable, like this:

// Check whether or not a class is useable
if let usableThing = thing as Usable? { // error here
    usableThing.use()
}
else {
    println("can't use that")
}
Connor Pearson
  • 63,902
  • 28
  • 145
  • 142
  • This does not work in a scenario that actually makes sense like `let thing : AnyObject = Thing()` Then you get a downcast error – drewag Jun 08 '14 at 22:46
  • 3
    This should be ```thing as? Usable``` instead of ```thing as Usable?``` – aryaxt Jul 20 '15 at 17:31
1

As metioned in the Swift doc, the is operator is the guy you need for the job:

The is operator checks at runtime to see whether the expression is of the specified type. If so, it returns true; otherwise, it returns false.

The check must not be known to be true or false at compile time.

Therefore, the following test would normally be what you need:

if thing is Usable { 
    usableThing.use()
} else {
    println("can't use that")
}

However, as the doc specifies, Swift can detect at compile time that the expression is always true and declares an error to help the developer.

Jean Le Moignan
  • 22,158
  • 3
  • 31
  • 37
1

This works for me in the playground

protocol Usable {
    func use()
}

class Thing: Usable {
    func use () {
        println ("you use the thing")
    }
}

let thing = Thing()
let testThing : AnyObject = thing as AnyObject

if let otherThing = testThing as? Thing {
    otherThing.use()
} else {
    println("can't use that")
}
David Arve
  • 804
  • 1
  • 7
  • 10
  • Verified, but I found that you don't need the cast "as AnyObject" in declaring "testThing" - anyone check? – Andrew Wolfe Jun 25 '14 at 04:31
  • Upvoted because this is the idiom I want to use. Note the variable you use must be a superclass of the protocol or class you want. – Andrew Wolfe Jun 25 '14 at 04:33
0

You are getting

Bound value in a conditional binding must be of Optional Type

because thing as Usable must return an optional type so making it as? should solved the problem. Unfortunately, the error still persisted for some odd reason. Anyway, a workaround I found to get it to work is to extract out the variable assignment inside the if statement

let thing = Thing()

let usableThing = thing as? Usable

if useableThing { 
    usableThing!.use()
}
else {
    println("can't use that")
}
Infinity
  • 3,695
  • 2
  • 27
  • 35
  • This does not work in a scenario that actually makes sense like `let thing : AnyObject = Thing()` Then you get a downcast error for it not being an objc protocol – drewag Jun 08 '14 at 22:45
  • Well, then he should do `thing.use?()` – Infinity Jun 08 '14 at 22:56
  • Then one gets the error AnyObject does not have a member named use() – drewag Jun 08 '14 at 23:00
  • Actually no, it returns nil if `use()` doesn't exist. – Infinity Jun 08 '14 at 23:01
  • Just as objective-c, you would need to inherit NSObjectProtocol from your protocol for `respondToSelector` to work. Sorry for not explicitly mentioned it. – Infinity Jun 08 '14 at 23:11
  • Ok ya, agreed, but the question asked "How can I achieve this functionality with conditional binding with a non-@objc protocol". I guess the answer is, you can't with swift as is. – drewag Jun 08 '14 at 23:12
0

swift protocols does not work in Playgrounds in the first beta, try to build a real project instead.