How to prevent circular reference when Swift bridging header imports a file that imports Hopscotch-Swift.h itself
Asked Answered
S

5

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?

Seabee answered 4/9, 2014 at 16:19 Comment(4)
Similar (same?) question here: How can I add forward class references used in the -Swift.h header?Bullfighter
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.Seabee
Did you file a radar or find a solution to this?Actinochemistry
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.Seabee
C
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.

Calipash answered 4/2, 2015 at 19:13 Comment(3)
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'.Selfregard
I'm not sure how this is working for you, @kleezy, since you cannot forward declare a protocol that you implement in Objective-C. If you attempt to run the code above, you will get the warning Cannot find protocol definition for 'MyProtocol'Britzka
Same here, I have a protocol defined in Swift that I'm using in Objective-C with id <Protocol> 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.Bennir
A
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
Aspire answered 30/1, 2015 at 10:33 Comment(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.Dynah
K
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"

Karynkaryo answered 22/3, 2018 at 15:10 Comment(3)
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...Biodegradable
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.Dynah
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.Dynah
E
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.

Exigible answered 3/9, 2022 at 8:13 Comment(0)
S
-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
Simplicidentate answered 30/9, 2014 at 11:30 Comment(2)
but that's what #import does in the first place, and why it's better than #includeBergerac
the submitter had trouble with #import specifically. It is unknown what the root cause is. Yes, you would expect @#import to detect circular referencesSimplicidentate

© 2022 - 2024 — McMap. All rights reserved.