Can I prevent extensions from being exported to Swift header for Objective-C?

I have an Objective-C project with 1 Swift class. This class is using a framework that is also written in Swift. (Used CocoaPods to include the framework)


My problem is that the -Swift.h file is exporting my extensions that adhere to protocols in the framework. Now when I try to import the -Swift.h file in the Objective-C, it complains that that the protocol definitions cannot be found.


I don't want these extensions exported. They are only used in this class. I can't use private or fileprivate for extensions that declare protocol conformances. I also tried adding @nonobjc before the extension declaration (which cascaded warnings into my methods) and it was still exported.


Here are my extensions:


extension MessagingExperience: MessagingDelegate {
  ...
}

extension MessagingExperience: MessagingNotificationDelegate {
  ...
}


And generated header:


@interface MessagingExperience (SWIFT_EXTENSION(Reference_App)) <MessagingDelegate>
- (void)MessagingObseleteVersion:(NSError * _Nonnull)error;
- (void)MessagingError:(NSError * _Nonnull)error;
@end

@interface MessagingExperience (SWIFT_EXTENSION(Reference_App)) <MessagingNotificationDelegate>
- (BOOL)shouldShowMessagingNotificationWithNotification:(MessagingNotification * _Nonnull)notification SWIFT_WARN_UNUSED_RESULT;
- (void)messagingNotificationTapped:(MessagingNotification * _Nonnull)notification;
- (UIView * _Nonnull)customMessagingNotificationViewWithNotification:(MessagingNotification * _Nonnull)notification SWIFT_WARN_UNUSED_RESULT;
@end


Errors produced by including the -Swift.h in an Objective-C class:

Cannot find protocol declaration for 'MessagingDelegate'

Cannot find protocol declaration for 'MessagingNotificationDelegate'


Is there a way to prevent this from being in the header?


Thanks.

Answered by QuinceyMorris in 287486022

I found three statements in the official Swift docs that seem to have some relevance here. The first 2 are from


https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html


under "Importing Code from Within the Same App Target" -> "Importing Swift into Objective-C":


[1] "By default, the generated header contains interfaces for Swift declarations marked with the public or open modifier. It also contains those marked with the internal modifier if your app target has an Objective-C bridging header."


[2] "Note that the Swift interfaces in the generated header include references to all of the Objective-C types used in them. If you use your own Objective-C types in your Swift code, make sure to import the Objective-C headers for those types before importing the Swift generated header into the Objective-C .m file"


and the third is from


https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html


under "Access Control" -> "Extensions":


[3] "You can’t provide an explicit access-level modifier for an extension if you’re using that extension to add protocol conformance. Instead, the protocol’s own access level is used to provide the default access level for each protocol requirement implementation within the extension."


The statements are not very specific, but #3 suggests to me that you should be able to get rid of the errors by @import'ing the framework into your Obj-C code that uses the MessagingExperience class.


#1 and #2 also suggest to me that the reason the protocols are being exposed in the generated header is that they are (I'm guessing) declared public in the framework, and Swift currently has an unresolved design issue where are class that conforms to a public protocol cannot conform to it privately (i.e. without advertizing its conformance).


One workaround might be to add the protocol conformance to a private subclass of MessagingExperience, and using that subclass internally in the Swift code. The objects can still be shared with Obj-C code. All you need to do (as a safety check) is make sure that instances of the base class can't be created directly.


If no one else jumps in with a more straightforward answer, you should probably ask this question over of the "swift-users" mailing list at swift.org. The answer may require the expertise of the Swift team to say exactly what it causing your problem, and whether there's a fix.

Accepted Answer

I found three statements in the official Swift docs that seem to have some relevance here. The first 2 are from


https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html


under "Importing Code from Within the Same App Target" -> "Importing Swift into Objective-C":


[1] "By default, the generated header contains interfaces for Swift declarations marked with the public or open modifier. It also contains those marked with the internal modifier if your app target has an Objective-C bridging header."


[2] "Note that the Swift interfaces in the generated header include references to all of the Objective-C types used in them. If you use your own Objective-C types in your Swift code, make sure to import the Objective-C headers for those types before importing the Swift generated header into the Objective-C .m file"


and the third is from


https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html


under "Access Control" -> "Extensions":


[3] "You can’t provide an explicit access-level modifier for an extension if you’re using that extension to add protocol conformance. Instead, the protocol’s own access level is used to provide the default access level for each protocol requirement implementation within the extension."


The statements are not very specific, but #3 suggests to me that you should be able to get rid of the errors by @import'ing the framework into your Obj-C code that uses the MessagingExperience class.


#1 and #2 also suggest to me that the reason the protocols are being exposed in the generated header is that they are (I'm guessing) declared public in the framework, and Swift currently has an unresolved design issue where are class that conforms to a public protocol cannot conform to it privately (i.e. without advertizing its conformance).


One workaround might be to add the protocol conformance to a private subclass of MessagingExperience, and using that subclass internally in the Swift code. The objects can still be shared with Obj-C code. All you need to do (as a safety check) is make sure that instances of the base class can't be created directly.


If no one else jumps in with a more straightforward answer, you should probably ask this question over of the "swift-users" mailing list at swift.org. The answer may require the expertise of the Swift team to say exactly what it causing your problem, and whether there's a fix.

> I also tried adding @nonobjc before the extension declaration (which cascaded warnings into my methods) and it was still exported.


...it doesn't solve the problem because class 'DispatchQueue' is still exported to 'OS_dispatch_queue' which is unknown to Objective-C.


Otherwise...quoting off the 'net:


"So what I would recommend is building that protocol, if you need to use it from Objective-C, inside Objective-C, and then importing back into Swift. That would work as the best resort. It turns out that the delegate from my use case, and from my experience, delegate as a protocol, or as a construct, in object-oriented programming doesn’t really work amazingly in Swift. Specifically, with Swift protocols, because all of the methods become required at once, and unless it’s an Objective-C protocol, nothing can be optional. So, using a delegate in Objective-C code is a good practice. Create one in Objective-C, use it from Swift, migrate everything to Swift when you can get rid of it."

A private class in the file is perfect. Thanks.

Can I prevent extensions from being exported to Swift header for Objective-C?
 
 
Q