Sti Sti - 3 months ago 13
iOS Question

How to use the Swift 3 selectors?

Up until now I have had this code

if UIScreen.instancesRespondToSelector(Selector("scale")) {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale);
}else{...}


I didn't write this code, so I'm not sure what it's for, but it looks like they wanted to verify that
UIScreen.mainScreen()
in fact can have the variable
.scale
(?).

When looking at the
.scale
, it looks to me like this has been available since iOS 4.0. Since we support down to iOS 7, this shouldn't be necessary, right?

Anyway, this is not the current problem.
I am now having hundreds of warnings due to Xcode 7.3 towards Swift 3 with these new selector-instantiations or whatnot.

Xcode wants me to change this:

Selector("scale")


into

#selector(NSDecimalNumberBehaviors.scale)


Until now, all other selectors I have changed have been logical, like "change
Selector("hello")
into
#selector(MyClass.hello)
, but this
NSDecimal..
sounds a bit drastic. Can I trust Xcode to pick the right selector? I can't find
NSDecimalNumberBehaviors
anywhere connected to UIScreen.scale.. If I type
#selector(UIScreen.scale)
I get an error..

The only thing I know for sure is that if I CMD+click
scale
here:
NSDecimalNumberBehaviors.scale
and here:
UIScreen.mainScreen().scale
I end up in different places..

Answer

As noted in comments, this code is a vestigial remnant of attempts to support old iOS versions that not only are no longer relevant, but can't even be targeted when developing in Swift.

Just call UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.mainScreen().scale) directly — the scale property exists on all iOS versions that you can target with Swift, so there's no need to check for it.

In fact, in general it's not a good idea to test for API availability using selector checks. A selector may exist on a version below the one you're targeting, but be private API with different behavior — so your check will succeed, but your code will not behave correctly. This is why the @available and #available system was introduced in Swift 2.

(Another upside of version-based availability checking: when an OS version gets old enough for you to drop support, it's much easier to find all the sites in your code that you can clean up. You don't have to remember which version which method/property became universal in.)


If for some other reason you need to form a Selector for UIScreen.scale... you can't use the #selector expression to do that in Swift 2.2, because scale is a property, not a method. In Swift 2.2, #selector takes a function/reference, and there's no way to get a reference to the underlying getter or setter method of a property. You'd still need to construct that selector from a string. To get around the warning, stash the string literal in a temporary:

let scale = "scale"
let selector = Selector(scale)

Or do some other dance that passes a string, but doesn't directly pass a string literal, to the Selector initializer:

let selector = Selector({"scale"}())

In Swift 3 there'll be a special form of #selector for property getters/setters, but it hasn't landed yet.