Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-c protocol to Swift class. unrecognized method

I'm trying to migrate some code from Objective-c to Swift, but i have problems at the beggining when i want to conform a Swift class with a Objective-c protocol and access this class from a objetive-c class. I'm doing something wrong, but i don't see it.

Objective-c protocol and obj-c class for testing (test_class.h)

#import <Foundation/Foundation.h>

@protocol test_delegate

-(void)returnData:(NSString*)data InMethod:(NSString*)method;
@end

@interface test_class : NSObject

@property (weak, nonatomic) id<test_delegate> delegate;
-(void)sendData:(NSString * )data;
@end

Objective-c implementation

#import "test_class.h"

@implementation test_class

-(id) init{
    self = [super init];
    if (self != nil){
        self.delegate= nil;
    }
    return self;
}
-(id) initWithDelegate:(id<test_delegate>) delegate{
    self = [super init];
    if (self != nil){
        self.delegate = delegate;

    }
    return self;
}


-(void)sendData:(NSString *)data{

    [self.delegate returnData:data InMethod:@"method test"];
}
@end

Objetive-c brigde file

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "test_class.h"

Swift file (FirstViewController.swift)

import UIKit

class FirstViewController: UIViewController, test_delegate {

    var test : test_class!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        test = test_class()
        test.delegate=self
        test.sendData("show in log")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func returnData(data: String!, inMethod method: String!) {
        NSLog(data)
    }


}

Finally the error compile gives me

2015-01-27 08:21:05.787 Test[4566:51164] -[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20
2015-01-27 08:21:05.792 Test[4566:51164] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Test.FirstViewController returnData:InMethod:]: unrecognized selector sent to instance 0x7fa6abd0aa20'

This code, using only Obj-c classes works fine. What I'm doing wrong?

Using Xcode 6.1.1 and deployment target iOS 8.0

Thank you in advance.

UPDATE: Swift has a weird behaviour when implement some method from a objective-c protocol: param's name must start with a tiny letter at the the begginig, else, compiler give you a compile-time error, and if not, give you a runtime error.

The unique solution I've found, is modify objective-c protocol method definition this way:

@protocol test_delegate

-(void)returnData:(NSString*)data InMethod:(NSString*)method;
@end

to:

@protocol test_delegate

-(void)returnData:(NSString*)data inMethod:(NSString*)method;
@end

Doing this change, it runs perfectly.

If anyone has an answer for this behaviour, is welcome to post and explain why this happens.

UPDATE 2: Thank you to @Adam Freeman to point out another weird issue of Swift with classes names and variables names. Copied (with his permission) the code here:

Another thing to watch out for that is if your protocol delegate method takes its class as one of its parameters. Using this example:

#import <Foundation/Foundation.h>

@class TestClass;
@protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
@end

@interface TestClass : NSObject

@property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
@end

This will cause problems with TestClass being found in your Swift code. The fix is:

#import <Foundation/Foundation.h>

@class TestClass;
@protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
@end

@interface TestClass : NSObject

@property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
@end

Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter.

like image 346
Neonamu Avatar asked Jan 27 '15 07:01

Neonamu


2 Answers

Change:

func returnData(data: String!, inMethod method: String!) {

to

func returnData(data: String!, InMethod method: String!) {

and it should work. You use a capital letter in inMethod.

like image 151
Robert Gummesson Avatar answered Nov 13 '22 12:11

Robert Gummesson


Another thing to watch out for that I ran into and this post helped me fix (thanX!) and that others may run into is if your protcol delegate method takes its class as one of its parameters. Such as ==>

#import <Foundation/Foundation.h>

@class TestClass;
@protocol TestDelegate
-(void)TestClass:(TestClass*)TestClass returnData:(NSString*)data inMethod:(NSString*)method;
@end

@interface TestClass : NSObject

@property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
@end

This will cause problems with TestClass being found in your Swift code. The fix is:

#import <Foundation/Foundation.h>

@class TestClass;
@protocol TestDelegate
-(void)testClass:(TestClass*)testClass returnData:(NSString*)data inMethod:(NSString*)method;
@end

@interface TestClass : NSObject

@property (weak, nonatomic) id<TestDelegate> delegate;
-(void)sendData:(NSString * )data;
@end

Incidentally the Swift spec states that such things as classes and delegates should start with an upper case letter and methods and parameter names should start with a lower case letter. I know this is nit-picky but test_class really should be TestClass according to Apple's spec.

like image 22
Adam Freeman Avatar answered Nov 13 '22 12:11

Adam Freeman