RyanJM
RyanJM

Reputation: 7068

Swift protocol in Objective-C category

I have a protocol defined in Swift:

@objc public protocol UploadProtocol: class {
    func isUploaded() -> Bool
    ...
}

I'm importing my ProjectName-Swift.h file in my ProjectName_Prefix.pch file (old project).

In Objective-C I have a category on a class which implements the protocol. I'm trying to figure out how to let the compiler know about that implementation. This is what I have been doing:

// Person+Upload.h
#import "Person.h"

@interface Person (Upload)
- (BOOL)isUploaded;
@end


// Person+Upload.m
#import "Person+Upload.h"
@implementation Person (Upload)
...
@end

This gives me:

Cannot find protocol definition for 'UploadProtocol'

I can only think of three other ways of setting this up and all of them have problems. What am I missing?

First Way (Apple Recommended)

In Using Swift with Cocoa and Objective-C (Swift 2.2), Apple says, "An Objective-C class can adopt a Swift protocol in its implementation (.m) file by importing the Xcode-generated header for Swift code and using a class extension." But their example is for a standard class. If I try to do this with my category:

// Person+Upload.h
#import "Person.h"

@interface Person (Upload)
- (BOOL)isUploaded;
@end


// Person+Upload.m
#import "Person+Upload.h"

@interface Person (Upload) <UploadProtocol>
@end

@implementation Person (Upload)
...
@end

This results in:

Duplicate definition of category 'Upload' on interface 'Person'

Second Way

Ok, so I want to avoid defining the upload class twice. So I'll remove it from the .h.

// Person+Upload.h
#import "Person.h"

@interface Person ()
- (BOOL)isUploaded;
@end


// Person+Upload.m
#import "Person+Upload.h"

@interface Person (Upload) <UploadProtocol>
@end

@implementation Person (Upload)
...
@end

In this case I get:

Category is implementing a method which will also be implemented by its primary class

I'm currently declaring the method in the .h so that other classes can call some of those methods without casting to the protocol to access that method.

Third Way

If I leave the category name in the .h but take it off in the .m

// Person+Upload.h
#import "Person.h"

@interface Person (Upload)
- (BOOL)isUploaded;
@end


// Person+Upload.m
#import "Person+Upload.h"

@interface Person () <UploadProtocol>
@end

@implementation Person (Upload)
...
@end

Then I get:

Category is implementing a method which will also be implemented by its primary class


Is the only solution to cast it to the protocol every time? Adding this type of line in multiple places seems like a code smell:

Person <Upload> *s = (Person <Upload> *)self;

Any other solutions?

Upvotes: 1

Views: 2009

Answers (1)

Anatoli P
Anatoli P

Reputation: 4891

Thanks for the interesting question!

Your initial approach will actually work with an addition of a forward declaration of the protocol:

// Person+Upload.h
#import "Person.h"

@protocol UploadProtocol; // NOTE THIS!!!

@interface Person (Upload) <UploadProtocol>
//- (BOOL)isUploaded;  // This is not really needed
@end


// Person+Upload.m
#import "Person+Upload.h"
#import "...-Swift.h"  // IMPORTANT!!!
@implementation Person (Upload)
// HERE you implement isUploaded, of course.
...
@end

This will still cause a compiler warning Cannot find protocol definition for 'UploadProtocol', but it should work.

The Apple-recommended way also works, the Duplicate definition of category is just a warning, but Person won't be recognized as conforming to the protocol by Swift code, even though its conformance will be recognized by Objective-C. The initial approach will be fine in Swift, too.

Upvotes: 1

Related Questions