Swift: Using protocol extension results in "unrecognized selector sent to instance" - ios

I'm trying to add a on-tap functionality to all UIViewControllers where they conform to protocol MyProtocol.
Below is how i'm doing it:
import UIKit
protocol MyProtocol: class{
var foo: String? {get set}
func bar()
}
extension MyProtocol where Self: UIViewController {
func bar() {
print(foo)
}
}
class TestViewController: UIViewController, MyProtocol{
var foo: String?
override func viewDidLoad() {
super.viewDidLoad()
foo = "testing"
let tapGesture = UITapGestureRecognizer(target: self, action: "bar")
}
Which results in following when screen is tapped:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: unrecognized selector sent to instance
I understand the error but don't know how to fix it. Can anyone suggest how this can be done?

The problem is that Objective-C knows nothing of protocol extensions. Thus, you cannot use a protocol extension to inject a method into a class in such a way that Objective-C's messaging mechanism can see it. You need to declare bar in the view controller class itself.
(I realize that this is precisely what you were trying to avoid doing, but I can't help that.)
A sort of workaround might be this:
override func viewDidLoad() {
super.viewDidLoad()
foo = "testing"
let tapGesture = UITapGestureRecognizer(target: self, action: "baz")
self.view.addGestureRecognizer(tapGesture)
}
func baz() {
self.bar()
}
We are now using Objective-C's messaging mechanism to call baz and then Swift's messaging mechanism to call bar, and of course that works. I realize it isn't as clean as what you had in mind, but at least now the implementation of bar can live in the protocol extension.
(Of course, another solution would be to do what we did before protocol extensions existed: make all your view controllers inherit from some common custom UIViewController subclass containing bar.)

Matt's answer is correct. You can play in your playground to see exactly how it works. In next snippet you can to see that even though some object can perform objc selector, if not properly used, it could crash your code :-)
//: Playground - noun: a place where people can play
import Foundation
protocol Bar {}
extension Bar {
func bar()->Self {
print("bar from protocol Bar extension")
dump(self)
return self
}
}
class C: NSObject {
func dummy() {
print("dummy from C")
dump(self)
}
}
extension NSObject {
func foo()->NSObject {
print("foo from NSObject extension")
dump(self)
return self
}
}
let c = C()
c.respondsToSelector("foo") // true
c.respondsToSelector("dummy") // true
c.foo()
let i = c.performSelector("foo")// prints the same and DON'T crash
c.dummy() // works
//c.performSelector("dummy") // if uncommented then
/* prints as expected
dummy from C
▿ C #0
- super: <__lldb_expr_517.C: 0x7fcca0c05a60>
*/
// and crash!!! because performSelector returns Unmanaged<AnyObject>!
// and this is not what we return from dummy ( Void )
let o = NSObject()
o.foo()
extension NSObject: Bar {}
// try to uncomment this extension and see which version is called in following lines
/*
extension NSObject {
func bar() -> Self {
print("bar implemented in NSObject extension")
return self
}
}
*/
c.bar() // bar protocol extension Bar is called here
o.bar() // and here
o.respondsToSelector("foo") // true
o.respondsToSelector("bar") // false as expected ( see matt's answer !! )
o.performSelector("foo") // returns Unmanaged<AnyObject>(_value: <NSObject: 0x7fc40a541270>)
There is a big gotcha with performSelector: and friends: it can only be used with arguments and return values that are objects! So if you have something that takes e.g. a boolean or return void you're out of luck.
I would like to suggest further reading for everybody who use mixture of Swift and ObjectiveC

Related

How to use #objc protocol with optional and extensions at the same time?

This code does not compile and might sound stupid as it is, but i'll explain why it's so important!
#objc protocol p {
optional func f1()
func f2()
}
extension p {
func f1() { }
func f2() { }
}
class foo: p {
}
Compiler says Type c does not conform to protocol 'p' and that's maybe because you can not use #objc optional and extensions at the same time (and does not make sence in this scenario either). But consider the following example:
I want to set a selector on a non-optional method defined in protocol in my extension (main reason i used #objc):
func f1() { } -> func f1() { ... #selector(Self.f2) ... }
And i also want my f2() function to have default behaviour. If i mark f2() as optional, it can not be used in #selector because compiler does not know if this method actually exists in the case of need. Sure there're lots of nasty workarounds like global methods, sending Selectors to methods as input and etc, but is there a clean way to achieve it?
This is the practical issue
#objc
protocol Refreshable {
weak var refreshControl: UIRefreshControl? { get set }
optional func setupRefreshControl()
func refresh()
}
#objc
protocol ContentLoader {
func load(reset: Bool)
}
extension Refreshable where Self: ContentLoader {
func refresh() {
delay(0.75) { [weak self] in
self?.load(true)
}
}
}
extension Refreshable where Self: UICollectionViewController {
func setupRefreshControl() {
let newRefreshControl = UIRefreshControl()
newRefreshControl.tintColor = UIColor.grayColor()
newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged)
collectionView?.addSubview(newRefreshControl)
refreshControl = newRefreshControl
}
}
Now if a ViewController implements Refreshable and ContentLoader, it does not find the default refresh function, but it does find setupRefreshControl. So i figured let's mark refresh as optional too, but by doing that, you can not send it to selector any more.
I even tried this:
func refresh() -> optional func refresh()
and
let str = "refresh"
let sel = Selector(str)
It silents the compiler yes, but does not work either... rises unrecognized selector sent to instance....
I think this is not possible in swift (because of the way it bridges to #objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.
fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil
func trigger() {
closure?()
}
}
// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"
protocol CustomProtocol: class {
func setup()
}
extension CustomProtocol where Self: NSObject {
fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}
set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}
let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)
// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)
self.associatedObject = object
}
func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}
class CustomClass: NSObject, CustomProtocol {}
let instance = CustomClass()
instance.setup()
I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.

Trailing where clause for extension of non-generic type

I have the following code:
func registerNotification(name:String, selector:Selector)
{
NSNotificationCenter.defaultCenter().addObserver(self, selector: selector, name: name, object: nil)
}
func registerKeyboardNotifications()
{
let isInPopover = navigationController?.popoverPresentationController != nil
let ignore = isInPopover && DEVICE_IS_IPAD
if !ignore {
registerNotification(UIKeyboardWillShowNotification, selector: Selector("keyboardWillShow:"))
registerNotification(UIKeyboardWillHideNotification, selector: Selector("keyboardWillHide:"))
}
}
in an extension to UIViewController. This code is reused by many viewcontroller to register for keyboard notifications. However with Swift 2.2 it produces a warning. I like the new #selector syntax but not sure how to implement it in this case.
I think the correct solution is to make a protocol and extend UIViewController only for instances that conform to that protocol. My code so far:
#objc protocol KeyboardNotificationDelegate
{
func keyboardWillShow(notification: NSNotification)
func keyboardWillHide(notification: NSNotification)
}
extension UIViewController where Self: KeyboardNotificationDelegate
{
func registerKeyboardNotifications()
{
let isInPopover = navigationController?.popoverPresentationController != nil
let ignore = isInPopover && DEVICE_IS_IPAD
if !ignore {
registerNotification(UIKeyboardWillShowNotification, selector: #selector(KeyboardNotificationDelegate.keyboardWillShow(_:)))
registerNotification(UIKeyboardWillHideNotification, selector: #selector(KeyboardNotificationDelegate.keyboardWillHide(_:)))
}
}
}
However this get me the error
trailing where clause for extension of non-generic type
on the extension row. Any ideas?
The solution was simple to switch the order in the extension clause:
extension UIViewController where Self: KeyboardNotificationDelegate
should be
extension KeyboardNotificationDelegate where Self: UIViewController
extension Foo where ... can only be used if Foo is
a generic class or structure: extend with a default implementation for generics conforming to some type constraint,
a protocol containing some associated types, extend with a default implementation for when an associated type conform to some type constraint
a protocol where we extend with a default implementation for when Self is of a specific (object/reference) type, or conforms to some type constraint.
E.g.
// 1
class Foo<T> { }
extension Foo where T: IntegerType {}
struct Foz<T> {}
extension Foz where T: IntegerType {}
// 2
protocol Bar {
associatedtype T
}
extension Bar where T: IntegerType {}
// 3
protocol Baz {}
extension Baz where Self: IntegerType {}
class Bax<T>: Baz {}
extension Baz where Self: Bax<Int> {
func foo() { print("foo") }
}
let a = Bax<Int>()
a.foo() // foo
In your case, UIViewController is a non-generic class type, which does not conform to any of the two above.
As you've written in your own answer, the solution is to extend your delegate protocol with a default implementation for cases where Self: UIViewController, rather than attempting to extend UIViewController.

How to use dynamic types conforming to protocols

I'm just trying to get a grasp as to why this isn't possible. Why can I not use a dynamic type with this setup. (I put this in playgrounds):
protocol SomeProtocol {
init()
}
class SomeClass : SomeProtocol {
required init() { }
}
let x: SomeProtocol.Type = SomeClass.self
x()
When this runs, playgrounds will crash, and if you try to put code like this in Xcode it will throw a
Command failed due to signal: Segmentation fault: 11
If I, however, never call x(), I can print out x to be of the correct SomeClass.Type.
I realize this is a weird setup, so I understand the confusion of "why would you ever want to do that?". But that aside; is it possible? not possible? Is it a bug? Am I not understanding how protocols really work?
It works fine. I tried your code in Xcode 7 and it compiles and runs. Of course, in real code you can't say x() at top level. And in Swift 2 you have to say x.init(), not x(). But when you do, it's fine.
protocol SomeProtocol {
init()
}
class SomeClass : SomeProtocol {
required init() { }
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let x: SomeProtocol.Type = SomeClass.self
x.init()
}
}

Using Swift Protocol Inheritance

I am trying to create a delegate that uses traditional polymorphism to compensate for a device being bluetooth LE, bluetooth, etc and can't seem to get the syntax right for casting.
Here is my parent protocol and class:
#objc protocol DeviceDelegate
{
func didConnectToDevice(name:String)
func didFailToConnectToDevice(name:String)
func didDisconnectFromDevice(name:String)
func didWriteData(data:NSData)
func didReceiveData(data:NSData)
}
class Device: NSObject
{
var delegate: DeviceDelegate?
}
Now here is the child class and protocol simplified down:
protocol BluetoothDelegate : DeviceDelegate
{
func didFindService(name:String)
func didFindCharacteristic(name:String)
}
class BLE: Device
{
func someFunc()
{
let bluetoothDelegate = (delegate as? BluetoothDelegate)
bluetoothDelegate.didFindService(UUIDString)
}
}
It throws the following error on the first line of that function:
Cannot downcast from 'DeviceDelegate?' to non-#objc protocol type 'BluetoothDelegate'
This doesn't make sense to me since it should allow casting to a child like a usual object does.
If I put #objc in front of BluetoothDelegate I get the following error:
#objc protocol 'BluetoothDelegate' cannot refine non-#objc protocol 'DeviceDelegate'
Anybody have any ideas on this?
When I copy your code and paste it directly into a playground and add #objc in front of your BluetoothDelegate definition, I get a message on this line:
bluetoothDelegate.didFindService("asdf")
'BluetoothDelegate?' does not have a member named 'didFindService'
Because you have used as?, there is a chance that bluetoothDelegate is nil. You should be using optional chaining here. Replacing with the following line reports no errors in the playground, indicating that you may have done something else in your code that you're not showing us.
bluetoothDelegate?.didFindService("asdf")
Alternatively, you could use this:
if let bluetoothDelegate = delegate as? BluetoothDelegate {
bluetoothDelegate.didFindService(UUIDString)
}
The message you're seeing about DeviceDelegate not being an objc protocol indicates to me that you have written these in two different files and maybe forward-declared DeviceDelegate incorrectly.

Compiler error when assigning the Delegate for a Protocol in Swift iOS

I have a problem assigning the delegate for an object that is an instance of a class that defines a protocol in Swift as follows:
I simplified the code to the bare bones to exemplify the issue:
This is the class with the protocol
protocol TheProtocol {
func notifyDelegate()
}
class ClassWithProtocol: NSObject {
var delegate: TheProtocol?
fire() {
delegate?.notifyDelegate()
}
}
This is the class the conforms to the Protocol
class ClassConformingToProtocol: NSObject, TheProtocol {
var object: ClassWithProtocol?
func notifyDelegate() {
println("OK")
}
init() {
object = ClassWithProtocol()
object?.delegate = self // Compiler error - Cannot assign to the result of this expression
object?.fire()
}
}
I have tried all sort of alternatives to assign the delegate without success. Any idea what I am missing?
The Known Issues section of the Release Notes says:
You cannot conditionally assign to a property of an optional object.
(16922562)
For example, this is not supported:
let window: NSWindow? = NSApplication.sharedApplication.mainWindow
window?.title = "Currently experiencing problems"
So you should do something like if let realObject = object { ... }

Resources