Objective-C initializer isn't visible to Swift

I'm probably missing something very obvious, but I have (very very simplified from the original code; it's open source, so I can point you the original if it helps):

SBOperation.h

@interface SBOperation : NSOperation {
    // other fields/props/methods removed
}

- (id)initWithManagedObjectContext:(NSManagedObjectContext*)mainContext;

@end

SBSubsonicParsingOperation.h

// forward declared
@class SBClientController;
// forward declared, this is an NSManagedObjectID on a Swift Core Data object
@class SBServerID;


typedef NS_ENUM(NSInteger, SBSubsonicRequestType) {
    // ...
}

@interface SBSubsonicParsingOperation : SBOperation <NSXMLParserDelegate> {
    // ...
}

- (id)initWithManagedObjectContext:(NSManagedObjectContext*)mainContext 
                            client:(SBClientController *)client
                       requestType:(SBSubsonicRequestType)type
                            server:(SBServerID *)objectID
                               xml:(NSData *)xml
                          mimeType:(NSString *)mimeType;
@end

That file is imported in my Swift bridging header.

However, if I try to initialize a class instance, I only get the initializer for its superclass that just takes an NSManagedObjectContext:

                // ERROR: Extra arguments at positions #2, #3, #4, #5, #6 in call
                if let operation = SBSubsonicParsingOperation(managedObjectContext: self.managedObjectContext,
                                                              client: server.clientController,
                                                              requestType: type,
                                                              server: server.id,
                                                              xml: data,
                                                              mimeType: response.mimeType) {

If I go into Xcode and hit "generated Swift 5 header" for the parsing operation class:

// ...
open class SBSubsonicParsingOperation : SBOperation, XMLParserDelegate {

    
    // ...
    
    public init!(managedObjectContext mainContext: NSManagedObjectContext!, client: SBClientController!, requestType type: SBSubsonicRequestType, server objectID: SBServerID!, xml: Data!, mimeType: String!)
}

However, that initializer isn't visible to Swift in any other classes.

I'm using Xcode 14.3.1.

1 Like

The importer currently doesn't support forward declarations. See Pitch: Importing Forward Declared Objective-C Classes and Protocols.

Ah, it was SBServerID. That's a bit annoying, especially considering it's coming from Swift and therefore has to be a forward declaration in an Objective-C header.

I could be misremembering, but I remember implementing something in the compiler to handle forward declarations that turn out to be Swift types; are you sure that providing SBClientController wasn’t enough?

Oddly, the forward declaration SBClientController was actually fine (which was written in Objective-C, then I rewrote that class in Swift and it still holds), it just didn't like SBServerID. I'm wondering if the fact that SBServerID/SBServer.ID is generated by Core Data magic might pose a problem.

Ah, possibly. Or maybe it’s that it’s a nested type that’s messing it up. (In that case I’m guessing that SBClientController is also in your bridging header / framework header and so it’s exposed to Swift at import time, even if through another file.) Sorry for the tricky setup here.

Yeah, SBClientController was in the bridging header (until I rewrote it in Swift). Changing SBServerID to NSManagedObjectID was sufficient to make generation happy.

What was the most confusing part is I never saw any warnings saying that a method couldn't be represented in Swift because of a forward declaration. I had assumed it wasn't generating code, especially since the generated interface preview was actually showing the initializer.

Yeah, that’s a design that dates all the way back to Swift 1: if there were non-importable methods in one of your dependencies, there would be no way to deal with it, and you’d have a warning in your project permanently. But people have asked for various workarounds over the years to do something better in certain circumstances, and that mostly hasn’t been done due to time as well concerns of someone accidentally depending on a particular declaration being absent. So no immediate good news on this, but maybe someone could pick up this effort again.

To me it got fixed in the 15.0 Beta (at least on no.3)