HumanZero HumanZero
HumanZero HumanZero

Reputation: 154

How to implement an objective-C delegate function of an objective-C class in a swift extension

I'm trying to conform to an Objective-C delegate method on an Objective-C class via a swift extension on said class. The problem is I am given no autocomplete options when defining the body for this function in the swift extension below. I had to manually translate it from Objective-C and it still doesn't work.. I know the delegate is set up correctly as when I provide the delegate function body in the ObjectiveCConformingClass directly it works fine (and autocompletes when I write the function).

I have a swift class like this (what I need to fix I imagine):

extension ObjectiveCConformingClass {
    func delegateFunction(with index: Int, viewName: String, action: String, dictionary: [String : Any]) {
        //Never gets called.
    }
}

Extending a class like this:

Class that conforms to the delegate:

@interface ObjectiveCConformingClass : SuperClass <ObjectiveCDelegate>
    //Whatever
@end

Delegate:

@protocol ObjectiveCDelegate <NSObject>

@optional

- (void)delegateFunction:(NSInteger)index
                         view:(nonnull NSString *)view
                           action:(nonnull NSString *)action
                 dictionary:(nonnull NSDictionary<NSString *, id> *)dictionary;

@end

So to summarise: I need to conform to this delegate in a swift extension of the class, not the actual class. It works fine if I do it directly in the objective-C class. Any ideas why it is not working? Or if this is even possible?

Here are some reasonably similar questions that ask for different things so did not help me. (i.e. threads that this is not a duplicate of)

Upvotes: 1

Views: 1061

Answers (1)

Ol Sen
Ol Sen

Reputation: 3368

Take your time when reading..
Think of delegates as id<ProtocolName> "pointer to another object" that conforms to a protocol.

@protocol ObjectiveCDelegate <NSObject>
@optional
- (void)delegateMethod:(NSInteger)index
                  view:(nonnull NSString *)view
                action:(nonnull NSString *)action
            dictionary:(nonnull NSDictionary<NSString *, id> *)dictionary;
@end

Usually your @interface ClassName : NSObject that wants to make use of the delegate should have a property to keep it around or to set it nil (nullable) which implies why you want it weak.

@property (nonatomic, weak) id<ObjectiveCDelegate> delegate;

and the class (object) that will become your delegate must conform to this protocol, so you have to declare this in its interface as you did. In full beauty looks like..

@interface ObjectiveCConformingClass : SuperClass <ObjectiveCDelegate>
@property (nonatomic, weak) id<ObjectiveCDelegate> delegate;
-(void)invokeDelegate; //for testing.
@end

Because the protocol above has optional method declaration it will not throw warnings when you did not implement it. To avoid running in trouble when working with the delegate from within your class with ClassName (object) you will want to check if the delegate property is not nil and can respond to the desired method name.

@implementation ObjectiveCConformingClass
-(void)delegateMethod:(NSInteger)index view:(NSString *)view action:(NSString *)action dictionary:(NSDictionary<NSString *,id> *)dictionary {
   NSLog(@"original objc delegateMethod, called from %@", view);
}
-(void)invokeDelegate {
    if (_delegate) {
        // you can double check if the delegate is really an id<ProtocolName>
        if ([_delegate conformsToProtocol:@protocol(ObjectiveCDelegate)]) {
            // well you don't know if delegateMethod was implemented, it was optional
            // so you have to check, 
            // indeed it's implemented above, but it is safe this way.
            if ([_delegate respondsToSelector:@selector(delegateMethod:view:action:dictionary:)]) {
                //now you can call it safely
                [_delegate delegateMethod:0 view:@"original ObjectiveCConformingClass" action:@"a" dictionary:@{@"key":@"value"}];
            }
        }
    } else {
        NSLog(@"original ObjectiveCConformingClass delegate is nil");
    }
}
@end

so far its working in objective-c

now in swift you can make use of the delegate even in an extension

extension ObjectiveCConformingClass  {
    
    func extensionMethod() {
        if ((delegate) != nil) {
            if ((delegate?.responds(to: #selector(delegateMethod(_:view:action:dictionary:))))!) {
                delegate?.delegateMethod?(1,view: "extension ObjectiveCConformingClass",action: "world",dictionary: ["foo":"bar"])
            }
        } else {
            print("extension ObjectiveCConformingClass: delegate is nil")
        }
    }
    
    // following will make you extreme trouble..
    // see the missing _ so this is a different method then the objc variant
    // its selector is #selector(delegateMethod(index:view:action:dictionary:)
    func delegateMethod(index: Int, view: String, action: String, dictionary: [String : Any]) {
        print("swift extension func delegateMethod, called from",view)
    }

    // #selector() is heavily confused what method to select when uncommented
    // because selector is #selector(delegateMethod(_:view:action:dictionary:)
    // which is declared in objc and can not be directly extended in swift
    //func delegateMethod(_ index: Int, view: String, action: String, dictionary: [String : Any]) {
    //    print("swift extension func delegateMethod, called from",view)
    //}
}

let's check if swift extensions works properly when subclassed.

class ClassOtherName : ObjectiveCConformingClass {
    func subclassMethod() {
        if (delegate != nil) {
            // you still don't know if the method was implemented, so check
            if ((delegate?.responds(to: #selector(delegateMethod(_:view:action:dictionary:))))!) {
                delegate?.delegateMethod?(1, view:"subclass ClassOtherName", action: "action", dictionary: ["key" : "value"])
            } else {
                print("delegate seems not conforming to protocol")
            }
        } else {
            print("subclass ClassOtherName delegate is nil")
        }
    }
    // of course you can override in subclasses, even if this was a super protocol method
    // see the difference.. _ as argument used here
    // because Overriding non-@objc declarations from extensions is not supported
    override func delegateMethod(_ index: Int, view: String, action: String, dictionary: [String : Any]) {
        print("override func delegateMethod, called from",view)
    }
}

lets test

let a = ObjectiveCConformingClass() //extended version
a.extensionMethod()  // extension ObjectiveCConformingClass: delegate is nil
//a.subclassMethod() // does not exist in ObjectiveCConformingClass
a.invokeDelegate()   // original ObjectiveCConformingClass delegate is nil
        
let o = ClassOtherName()  // subclassed version of extension
o.delegate = a
o.extensionMethod()  // original objc delegateMethod, called from extension ObjectiveCConformingClass
o.subclassMethod()   // original objc delegateMethod, called from subclass ClassOtherName
o.invokeDelegate()   // original objc delegateMethod, called from original ObjectiveCConformingClass
        
o.delegate = nil
o.extensionMethod()  // extension ObjectiveCConformingClass: delegate is nil
o.subclassMethod()   // subclass ClassOtherName delegate is nil
o.invokeDelegate()   // original ObjectiveCConformingClass delegate is nil
        
o.delegate = o //aka o == self
o.extensionMethod()  // override func delegateMethod, called from extension ObjectiveCConformingClass
o.subclassMethod()   // override func delegateMethod, called from subclass ClassOtherName
o.invokeDelegate()   // override func delegateMethod, called from original ObjectiveCConformingClass

Hope this is not too confusing but you see who is calling who and what is invoked.

Upvotes: 0

Related Questions