46

I am integrating Swift into a large existing Objective C project and have run into what I think is a circular reference.

The classes in question are as follows:

Objective C Controller

#import "Hopscotch-Swift.h"

@interface MyController : UIViewController<MyProtocol>
   ...
@end

Swift Protocol

@objc protocol MyProtocol: NSObjectProtocol {
   ...
}

Bridging Header

#import "MyController.h"

This code fails to compile because the Hopscotch-Swift.h file will not generate.

I think this is due to a circular reference error as I can import Hopscotch-Swift.h into objective c headers that are not included in Hopscotch-Bridging-Header.h and it works fine.

Is there a workaround for this issue or should I file a radar with Apple?

Samantha John
  • 978
  • 1
  • 8
  • 18
  • Similar (same?) question here: [How can I add forward class references used in the -Swift.h header?](http://stackoverflow.com/questions/24098408/how-can-i-add-forward-class-references-used-in-the-swift-h-header) – Martin R Sep 04 '14 at 18:07
  • I saw that. It's not quite the same because that person is trying to stop two obj-c files from colliding while I am trying to import all of my swift files into an obj-c file that is required to run my swift files. – Samantha John Sep 04 '14 at 18:46
  • Did you file a radar or find a solution to this? – Maiaux Oct 12 '14 at 01:54
  • I might try Jochen's solution. The best workaround so far is to write the header in Objective C. I should file a radar but haven't had time yet. – Samantha John Nov 18 '14 at 16:43

5 Answers5

30

Forward declaration should work, in your case.

In your .h:

@protocol MyProtocol;

@interface MyController : UIViewController<MyProtocol>

@end

In your .m:

#import "HopScotch-Swift.h"

From How can I add forward class references used in the -Swift.h header? and the Swift interoperability guide:

If you use your own Objective-C types in your Swift code, make sure to import the Objective-C headers for those types prior to importing the Swift generated header into the Objective-C .m file you want to access the Swift code from.

Community
  • 1
  • 1
rudd
  • 846
  • 7
  • 17
  • This worked for me. In case it helps someone else, the compiler errors that led me to this answer after a lot of searching was: 'ProjectName-Swift.h' file not found" and "Failed to import bridging header '.../ProjectName-Bridging-Header.h'. – Clay Garrett Jul 13 '15 at 12:45
  • 9
    I'm not sure how this is working for you, @kleezy, since [you cannot forward declare a protocol that you implement in Objective-C](http://stackoverflow.com/a/11533804/35690). If you attempt to run the code above, you will get the warning `Cannot find protocol definition for 'MyProtocol'` – Senseful Oct 16 '15 at 20:57
  • Same here, I have a protocol defined in Swift that I'm using in Objective-C with `id ` and I can't for the life of me forward declare that. Removing the (probably ill-advised) protocol is going to be a significant cross-cutting rewrite of the app. – Alper Mar 09 '18 at 12:25
14

I ran into this when trying to use Swift classes inside Objective-C protocols, where the protocol was also implemented by another Swift class. It reeked of circular references and I guessed that it might be a problem trying to circularly generate the bridging headers, rather than a 'normal' circular include problem.

The solution, for me, was to just use forward declarations before the protocol declaration:-

// don't include the MyProject-Swift.h header

// forward declaration of Swift classes used
@class SwiftClass;

@protocol MyProtocol <NSObject>
- (SwiftClass *)swiftClass;
@end
Echelon
  • 7,306
  • 1
  • 36
  • 34
  • 1
    Forward declarations work if you need Swift objects or protocols in method signatures but you can neither inherit from a class that has only been forwarded declared, nor can you implement a protocol that has only been forward declared. And the later one is the case the question is all about. – Mecki Aug 30 '19 at 18:14
3

The forward declaration by itself didn't work for me. It compiled without errors but still had warnings that the protocol couldn't be found. I treat all warnings as errors, so this isn't good enough.

I was able to fix it by moving the protocol implementation into another category header.

So here's what worked for me:

In my MyOtherSwiftFile.swift:

@objc protocol MyProtocol: class {
func viewController(didFinishEditing viewController: MyViewController)
}

In my MyViewController.h:

@interface MyViewController // Removed protocol implementation declaration here
@end

Added MyViewController+MyProtocol.h to project, and put this in there:

@interface MyViewController (MyProtocol) <MyProtocol>
@end

The methods themselves can stay where they are if you want.

After you implement the above and compile, you'll get compiler warning(s) somewhere in your code that requires that MyViewController implements MyProtocol. In that file, you will #import "MyViewController+MyProtocol.h"

Chris Garrett
  • 4,824
  • 1
  • 34
  • 49
  • This is a fabulous solution. Thanks. The key thing here I believe is the ability to import the `-MyProtocol.h file` *after* you've imported your project's `-Swift.h` file in those files in the final step, where importing the `-MyProtocol.h` file is required... – Duncan Babbage Jun 15 '19 at 21:31
  • Please note that you will also explicitly have to implement the category with `@implementation SomeClass (SomeProto)` when you do this, as if you just implement the protocol methods in your main implementation, the compiler will be happy and it even works at runtime (calling the methods will succeed), yet when you ask a class instance `conformsToProtocol:`, it will say `NO`, unless it finds an implementation for this category and this can cause issues in some places. – Mecki Aug 30 '19 at 18:30
  • 1
    Also using this trick, Swift will not see that your class conforms to the protocol, as you cannot import `MyViewController+MyProtocol.h` into the bridging header since then you are back to the cyclic reference the question is all about. – Mecki Aug 30 '19 at 18:41
0

Alternatively you can convert your protocol to an Objective-C protocol MyProtocol.h and then use it in Swift by including MyProtocol.h in your bridging header.

Juri Noga
  • 4,363
  • 7
  • 38
  • 51
-1

You could something like this in the .h file you suspect to trigger the circular reference:

#ifndef MY_HEADER_H
#define MY_HEADER_H
your header file
#endif
Jochen Bedersdorfer
  • 4,093
  • 24
  • 26
  • but that's what `#import` does in the first place, and why it's better than `#include` – Mazyod Nov 30 '14 at 13:35
  • the submitter had trouble with #import specifically. It is unknown what the root cause is. Yes, you would expect @#import to detect circular references – Jochen Bedersdorfer Dec 01 '14 at 02:08