Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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..

like image 254
Sti Avatar asked Apr 25 '16 13:04

Sti


Video Answer


2 Answers

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.

like image 99
rickster Avatar answered Oct 21 '22 16:10

rickster


my two cents:

  • 1) swift has a new sntax for selectors
  • 2) compiler can safely detect methods based on:
    • n. of params
    • type of every param
    • result type (ONLY in pure swift, so let's forget it now)

let see 4 cases:

A)

..
        closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
    }



func doIt(_ sender: UIButton) {
    print("touched")
}


func doIt() {
    print("touched2 ")

}

Ambiguous use of 'doIt'

B)

closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
}


//  func doIt(_ sender: UIButton) {
//      print("touched")
//  }


func doIt() {
    print("touched2 ")

}

it works, as compiler is able to detect the ONLY method that can match the signature

C)

closeBtn.addTarget(self, action: #selector(doIt(_:)), for: .touchUpInside)
}

func doIt(_ sender: UIButton) {
    print("touched")
}


func doIt() {
    print("touched2 ")
}

it works, as compiler is able to detect the ONLY method that can match the signature.

D)

closeBtn.addTarget(self, action: #selector(doIt), for: .touchUpInside)
}


func doIt(_ sender: UIButton) {
    print("touched")
}

/*
func doIt() {
    print("touched2 ")

}
*/

it works and:

  • compiler is able to detect the ONLY method that can match the
    signature
  • in debugger You will get the correct button reference too, as the UIButton CODE (in iOS libs) pushes the button address on the
    stack, as compiler does create a stack frame.
like image 28
ingconti Avatar answered Oct 21 '22 17:10

ingconti