swift 4.0 #selector is wrong [duplicate] - swift

I'm trying to convert my project's source code from Swift 3 to Swift 4. One warning Xcode is giving me is about my selectors.
For instance, I add a target to a button using a regular selector like this:
button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)
This is the warning it shows:
Argument of '#selector' refers to instance method 'myAction()' in 'ViewController' that depends on '#objc' attribute inference deprecated in Swift 4
Add '#objc' to expose this instance method to Objective-C
Now, hitting Fix on the error message does this to my function:
// before
func myAction() { /* ... */ }
// after
#objc func myAction() { /* ... */ }
I don't really want to rename all of my functions to include the #objc mark and I'm assuming that's not necessary.
How do I rewrite the selector to deal with the deprecation?
Related question:
The use of Swift 3 #objc inference in Swift 4 mode is deprecated?

The fix-it is correct – there's nothing about the selector you can change in order to make the method it refers to exposed to Objective-C.
The whole reason for this warning in the first place is the result of SE-0160. Prior to Swift 4, internal or higher Objective-C compatible members of NSObject inheriting classes were inferred to be #objc and therefore exposed to Objective-C, therefore allowing them to be called using selectors (as the Obj-C runtime is required in order to lookup the method implementation for a given selector).
However in Swift 4, this is no longer the case. Only very specific declarations are now inferred to be #objc, for example, overrides of #objc methods, implementations of #objc protocol requirements and declarations with attributes that imply #objc, such as #IBOutlet.
The motivation behind this, as detailed in the above linked proposal, is firstly to prevent method overloads in NSObject inheriting classes from colliding with each other due to having identical selectors. Secondly, it helps reduce the binary size by not having to generate thunks for members that don't need to be exposed to Obj-C, and thirdly improves the speed of dynamic linking.
If you want to expose a member to Obj-C, you need to mark it as #objc, for example:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
}
#objc func foo() {
// ...
}
}
(the migrator should do this automatically for you with selectors when running with the "minimise inference" option selected)
To expose a group of members to Obj-C, you can use an #objc extension:
#objc extension ViewController {
// both exposed to Obj-C
func foo() {}
func bar() {}
}
This will expose all the members defined in it to Obj-C, and give an error on any members that cannot be exposed to Obj-C (unless explicitly marked as #nonobjc).
If you have a class where you need all Obj-C compatible members to be exposed to Obj-C, you can mark the class as #objcMembers:
#objcMembers
class ViewController: UIViewController {
// ...
}
Now, all members that can be inferred to be #objc will be. However, I wouldn't advise doing this unless you really need all members exposed to Obj-C, given the above mentioned downsides of having members unnecessarily exposed.

As Apple Official Documentation. you need to use #objc to call your Selector Method.
In Objective-C, a selector is a type that refers to the name of an
Objective-C method. In Swift, Objective-C selectors are represented by
the Selector structure, and can be constructed using the #selector
expression. To create a selector for a method that can be called from
Objective-C, pass the name of the method, such as
#selector(MyViewController.tappedButton(sender:)). To construct a selector for a property’s Objective-C getter or setter method, pass
the property name prefixed by the getter: or setter: label, such as
#selector(getter: MyViewController.myButton).

As of, I think Swift 4.2, all you need to do is assign #IBAction to your method and avoid the #objc annotation.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.cancel))
#IBAction func cancel()
{
self.dismiss(animated: true, completion: nil)
}

As already mentioned in other answers, there is no way to avoid the #objc annotation for selectors.
But warning mentioned in the OP can be silenced by taking following steps:
Go to Build Settings
Search for keyword #objc
Set the value of Swift 3 #objc interface to Off
below is the screenshot that illustrates the above mentioned steps:
Hope this helps

If you need objective c members in your view controller just add #objcMembers at the top of the view controller. And you can avoid this by adding IBAction in your code.
#IBAction func buttonAction() {
}
Make sure to connect this outlet in storyboard.

Related

#objc keyword extension subclass behaviour

Can someone explain why #objc keyword is needed here to compile the code?
As I understood this keyword is used in order to work ObjC message method dispatch. But this is not an NSObject instance.
class MyClass {
}
extension MyClass {
#objc func extensionMethod() { /// THIS LINE
print("A")
}
}
class SubClass: MyClass {
override func extensionMethod() {
print("B")
}
}
Does #objc keyword enable message dispatch as well as dynamic? Or not?
Does #objc keyword enable message dispatch as well as dynamic?
Not usually. Usually, the #objc attribute on its own just exposes a given class member to Objective-C – Swift is still free to dispatch to it either using table or static dispatch. You would need to mark the member as dynamic if you wanted Swift to use message dispatch when calling it.
However, for a non-final #objc class extension member, Swift will automatically infer it to be dynamic. Why? Because for interoperability reasons, Swift allows #objc extension members to override and be overridden (much like how you can override an Obj-C method in a subclass category). In order to achieve this behaviour, Swift relies on Obj-C message dispatch.
Therefore, in an extension, #objc infers dynamic. You cannot override an extension member without exposing it to the Obj-C runtime because extension members cannot currently be added to Swift class vtables (as Swift vtables currently cannot have members dynamically added to them at runtime).
But this is not an NSObject instance.
On Apple platforms (i.e those with Obj-C interop), all Swift classes are exposed to the Obj-C runtime, and all implicitly inherit from a special Obj-C base class called _SwiftObject, which conforms to NSObjectProtocol. So Swift classes are able to take advantage of message dispatch without having to inherit from NSObject.

Why do I have to use public to expose func with Swift 4 #objc inference

Using Swift 4, I have a simple class I wish to expose to Objective-C like this:
#objc class SomeClass : NSObject {
#objc class func foo(someDate : Date) -> Bool {
return true
}
}
After compile the autogenerated Obj-C bridging header does not contain the class. However, if I add the public modifier in front of the class and func and rebuild it will be in the bridging header. This seems to contradict the statement from Apple's doc that states (emphasis mine):
Private declarations are not exposed to Objective-C unless they are explicitly marked with #IBAction, #IBOutlet, or #objc
Why do I need to add the public modifier? Even the Swift docs show examples that leave 'public' off and simply use #objc to expose to Objective-C
I think these are two different things.
"Swift declarations marked with the private or fileprivate modifier do
not appear in the generated header.
is Followed by
Private declarations are not exposed to Objective-C unless they are explicitly marked with #IBAction, #IBOutlet, or #objc.
This can be read as saying a function must have the explicit marking to be exposed to Objective-C, and (separately) if marked private it will not be included in the header.
I'm not sure why something would need to be exposed without being visible, (I'm not familiar enough with the runtime to speak to that). However the closest Objective-C analogue to private is to not be included in the header, so it makes sense to have that behavior match.

Only classes that inherit from NSObject can be declared #objc

I have to set value to property by string name representation.
import Foundation
#objc class A:NSObject {
var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKey:"x")
print(a.x)
And getting strange errors during compilation:
main.swift:4:2: error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
~^~~~~
main.swift:13:1: error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKey:"x")
^ ~~~~~~~~
Does anyone know what is happening?
PS: reproducible on Swift 4.0 & 3.1.1 (Ubuntu 16.04.3 LTS)
Edited:
import Foundation
#objc class A:NSObject {
#objc dynamic var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKeyPath:"x")
print(a.x)
Output:
error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
error: property cannot be marked #objc because its type cannot be represented in Objective-C
#objc dynamic var x:String = ""
note: Swift structs cannot be represented in Objective-C
#objc dynamic var x:String = ""
error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKeyPath:"x")
EDIT 2:
Just trying like "c-style":
func set<T>(_ val:T, forKey key:String) {
print("SET:\(self) \(key) to \(val)")
let ivar: Ivar = class_getInstanceVariable(type(of: self), key)!
let pointerToInstanceField:UnsafeMutableRawPointer = Unmanaged.passRetained(self).toOpaque().advanced(by: ivar_getOffset(ivar))
let pointer = pointerToInstanceField.assumingMemoryBound(to: T.self)
pointer.pointee = val
}
It works well, but causes bad access in the recursive calls. Probably some retain/release issues. Will dig dipper. Also does not work on Linux (as mentioned in answers)
Documentation
Swift without the Objective-C Runtime: Swift on Linux does not depend
on the Objective-C runtime nor includes it. While Swift was designed
to interoperate closely with Objective-C when it is present, it was
also designed to work in environments where the Objective-C runtime
does not exist.
https://swift.org/blog/swift-linux-port/
Which is clear, provided that it states:
value of type 'A' has no member 'setValue'
It basically tells that there is no KVC mechanism underneath. setValue method comes from Objective-C runtime, which is absent on Linux. Thus, it's a no-go and what you're trying to accomplish is simply not possible.
Other than that, the following rule is applied on systems with Obj-C runtime environment:
Key-Value Coding with Swift
Swift objects that inherit from NSObject or one of its subclasses are
key-value coding compliant for their properties by default. Whereas in
Objective-C, a property’s accessors and instance variables must follow
certain patterns, a standard property declaration in Swift
automatically guarantees this. On the other hand, many of the
protocol’s features are either not relevant or are better handled
using native Swift constructs or techniques that do not exist in
Objective-C. For example, because all Swift properties are objects,
you never exercise the default implementation’s special handling of
non-object properties.
Also: Requiring Dynamic Dispatch
Swift APIs that are callable from Objective-C must be available
through dynamic dispatch. However, the availability of dynamic
dispatch doesn’t prevent the Swift compiler from selecting a more
efficient dispatch approach when those APIs are called from Swift
code.
You use the #objc attribute along with the dynamic modifier to require
that access to members be dynamically dispatched through the
Objective-C runtime. Requiring this kind of dynamic dispatch is rarely
necessary. However, it is necessary when using APIs like key–value
observing or the method_exchangeImplementations function in the
Objective-C runtime, which dynamically replace the implementation of a
method at runtime.
Declarations marked with the dynamic modifier must also be explicitly
marked with the #objc attribute unless the #objc attribute is
implicitly added by the declaration’s context. For information about
when the #objc attribute is implicitly added, see Declaration
Attributes in The Swift Programming Language (Swift 4).
Elements must also be declared dynamic in order to be KVO-compatible (for KVC, inheriting from NSObject is enough):
#objc dynamic var x:String = ""
If String doesn't work out, then try going with NSString.
If neither helps, this seems to be a Linux-specific issue, which doesn't appear to support KVC/KVO mechanism (which is also understandable).
P.S. With the code provided, your issue reproduced in Xcode on Mac, too.

How can I deal with #objc inference deprecation with #selector() in Swift 4?

I'm trying to convert my project's source code from Swift 3 to Swift 4. One warning Xcode is giving me is about my selectors.
For instance, I add a target to a button using a regular selector like this:
button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)
This is the warning it shows:
Argument of '#selector' refers to instance method 'myAction()' in 'ViewController' that depends on '#objc' attribute inference deprecated in Swift 4
Add '#objc' to expose this instance method to Objective-C
Now, hitting Fix on the error message does this to my function:
// before
func myAction() { /* ... */ }
// after
#objc func myAction() { /* ... */ }
I don't really want to rename all of my functions to include the #objc mark and I'm assuming that's not necessary.
How do I rewrite the selector to deal with the deprecation?
Related question:
The use of Swift 3 #objc inference in Swift 4 mode is deprecated?
The fix-it is correct – there's nothing about the selector you can change in order to make the method it refers to exposed to Objective-C.
The whole reason for this warning in the first place is the result of SE-0160. Prior to Swift 4, internal or higher Objective-C compatible members of NSObject inheriting classes were inferred to be #objc and therefore exposed to Objective-C, therefore allowing them to be called using selectors (as the Obj-C runtime is required in order to lookup the method implementation for a given selector).
However in Swift 4, this is no longer the case. Only very specific declarations are now inferred to be #objc, for example, overrides of #objc methods, implementations of #objc protocol requirements and declarations with attributes that imply #objc, such as #IBOutlet.
The motivation behind this, as detailed in the above linked proposal, is firstly to prevent method overloads in NSObject inheriting classes from colliding with each other due to having identical selectors. Secondly, it helps reduce the binary size by not having to generate thunks for members that don't need to be exposed to Obj-C, and thirdly improves the speed of dynamic linking.
If you want to expose a member to Obj-C, you need to mark it as #objc, for example:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.addTarget(self, action: #selector(foo), for: .touchUpInside)
}
#objc func foo() {
// ...
}
}
(the migrator should do this automatically for you with selectors when running with the "minimise inference" option selected)
To expose a group of members to Obj-C, you can use an #objc extension:
#objc extension ViewController {
// both exposed to Obj-C
func foo() {}
func bar() {}
}
This will expose all the members defined in it to Obj-C, and give an error on any members that cannot be exposed to Obj-C (unless explicitly marked as #nonobjc).
If you have a class where you need all Obj-C compatible members to be exposed to Obj-C, you can mark the class as #objcMembers:
#objcMembers
class ViewController: UIViewController {
// ...
}
Now, all members that can be inferred to be #objc will be. However, I wouldn't advise doing this unless you really need all members exposed to Obj-C, given the above mentioned downsides of having members unnecessarily exposed.
As Apple Official Documentation. you need to use #objc to call your Selector Method.
In Objective-C, a selector is a type that refers to the name of an
Objective-C method. In Swift, Objective-C selectors are represented by
the Selector structure, and can be constructed using the #selector
expression. To create a selector for a method that can be called from
Objective-C, pass the name of the method, such as
#selector(MyViewController.tappedButton(sender:)). To construct a selector for a property’s Objective-C getter or setter method, pass
the property name prefixed by the getter: or setter: label, such as
#selector(getter: MyViewController.myButton).
As of, I think Swift 4.2, all you need to do is assign #IBAction to your method and avoid the #objc annotation.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.cancel))
#IBAction func cancel()
{
self.dismiss(animated: true, completion: nil)
}
As already mentioned in other answers, there is no way to avoid the #objc annotation for selectors.
But warning mentioned in the OP can be silenced by taking following steps:
Go to Build Settings
Search for keyword #objc
Set the value of Swift 3 #objc interface to Off
below is the screenshot that illustrates the above mentioned steps:
Hope this helps
If you need objective c members in your view controller just add #objcMembers at the top of the view controller. And you can avoid this by adding IBAction in your code.
#IBAction func buttonAction() {
}
Make sure to connect this outlet in storyboard.

Why does adding a Swift protocol in some situations not require all the normally required methods?

For example, these are valid and will compile without (all) protocol stubs
public class ViewController: UIViewController, SFSpeechRecognizerDelegate {
}
class BLEController: CBCentralManager, CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
}
}
Edit: Solved! Creating a class without inheriting from UIViewController or CBCentralManager still does comply with the delegate protocols, but it does not comply with NSObjectProtocol. It just seems to be that I'm attempting to use the frameworks in an unintended way.
Why does my code compile without fulfilling all the protocol requirements?
What you are seeing here are optional protocol requirements. They are here because Swift code needs to interact with Objective-C, and Objective-C has them.
All methods except centralManagerDidUpdateState declared in CBCentralManagerDelegate are optional, from here:
The only required method is centralManagerDidUpdateState(_:); the central manager calls this when its state updates, thereby indicating the availability of the central manager.
And SFSpeechRecognizerDelegate, only contains one optional method, from here:
Use this protocol's optional method to track those changes and provide an appropriate response.
why does the code not compile if you remove the superclasses then?
This is because both of those protocols in your question also inherit from NSObjectProtocol, so they actually also have the additional requirements of NSObjectProtocol. UIViewController and CBCentralManager both inherits from NSObject, so they satisfy the NSObjectProtocol requirements, but your Swift class without a superclass doesn't.
Protocols with optional requirements don't have to inherit from NSObjectProtocol though. It's just that most of them are that way in the framework. You can for example do:
#objc protocol Foo {
#objc optional func foo()
}
class FooClass : Foo {
// compiles fine!
}
It compiles without needing the protocol stubs because all the requirement is optional. Check within the declaration of SFSpeechRecognizerDelegate it has only one requirement for a method called availabilityDidChange and you can see that it's optional from the keyword given at the beginning of the function.
In the second case, the class you have created doesn't inherit from NSObject but the first one does because it's a sub-class of UIViewController which in-turn is a sub-class of NSObject.