Swift/ObjC circular import - objective-c

I am working on an existing large codebase that is predominately Objective-C but is in the process of converting to Swift.
New classes are being implemented in Swift, but some of these classes need to be accessed from existing ObjC code. In an attempt to follow both ObjC and Swift best practices, the new classes do not have a prefix, but are defined with a prefix for ObjC.
/**
Create contrived class that is named XXClassA in ObjC and ClassA in Swift.
*/
#objc(XXClassA) class ClassA: NSObject {
let foo = "bar"
}
So far this has been working great; Swift code uses ClassA() and ObjC uses [[XXClassA alloc] init]. Once all ObjC code that references XXClassA is removed, #objc(XXClassA) can also be removed from the Swift class.
Unfortunately this breaks down if you have an ObjC class reference XXClassA and then the Swift code attempts to use that new class inside of ClassA.
Say I create an ObjC class, named XXClassC and it instantiates itself using an XXClassA (which is really the Swift class ClassA)
//
// XXClassC.h
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#class XXClassA;
#interface XXClassC : NSObject
-(instancetype)initWithA:(XXClassA *)classA;
#end
NS_ASSUME_NONNULL_END
The circular reference is now in place. If I attempt to use XXClassC from inside of the ClassA, the initializer is not available.
If I redefine ClassA as this instead, all is well again.
class XXClassA: NSObject {
let foo = "bar"
}
I understand why this is happening and the fix I have in place, however I want to continue to use this pattern of prefixed ObjC classes. Any ideas on how to avoid the circular import, but also keep the naming convention?
Full code sample here: https://gist.github.com/justAnotherDev/78483f9d94e40fd90c38

I'm having the same problem. My workaround is:
Use an objc-style prefixed name for the Swift class.
Add a typealias: typealias ClassA = XXClassA.
Always use the unprefixed name from Swift code.
With this pattern it's at least easy to remove the prefix when the last objc dependency is gone.

Related

Obj-C classes inheriting from Swift classes cannot be used by Swift code?

I have a class that is defined in Swift:
// SwiftClass.swift
#objc
class SwiftClass: NSObject {
// ...
}
An Objective-C class can inherit from that Swift class:
// ObjCObject.h
#import "MyModule-Swift.h"
#interface ObjCObject: SwiftClass
// ...
#end
However, when doing so, I cannot use that class in Swift code anymore.
To use it in Swift, I have to add it to the bridging header:
// MyModule-BridgingHeader.h
#import "ObjCObject.h"
as only classes referenced by the bridging header are visible to Swift code. And this creates a cyclic reference, as the bridging header must be processed prior to compiling the Swift source code yet the generated Swift header (MyModule-Swift.h) only exists after compiling the Swift source code and thus won't exist when the bridging header is being processed.
If it was just for method arguments, I could use forward declarations but an Obj-C class cannot inherit from a forward declared class.
So am I just missing something here or is it really the case that you can use Obj-C classes in Swift and Swift classes in Obj-C but you cannot use an Obj-C class in Swift if it inherits from a Swift class?

Resolving Swift.h and Bridging-Header.h circular references involving enums

I have an Objective-C header that has to be used by a Swift class. However, this header has to use the Swift.h file for an enum declared in a Swift file. In other words, the setup is as follows:
MPViewController.h
#import "MyProject-Swift.h"
#interface MPViewController: UIViewController
#property (nonatomic, assign) MPSomeEnum theEnum;
...
#end
MyProject-Bridging-Header.h
...
#import "MPViewController.h"
...
SomeEnum.swift
#objc enum MPSomeEnum: Int {
...
}
When compiling the code, I get three errors:
'MyProject-Swift.h' file not found
Failed to emit precompiled header [Xcode DerivedData folder]/[...]/MyProject-Bridging-Header-swift_[...].pch for bridging header [Project folder]/MyProject-Bridging-Header.h
Unknown type name 'MPSomeEnum'
Am I correct to assume that this stems from the circular reference between MyProject-Swift.h and the bridging header MyProject-Bridging-Header.h? From looking at a similar question one solution is to use forward declaration. However, it doesn't seem possible to forward declare an enum, so perhaps the only way to do this is to move the enum definition to an Objective-C file altogether?
TL&DR; As you suspected, you need to either move the enum declaration to Objective-C, or migrate the class to Swift.
Forward declarations of enums is possible in Objective-C:
#property SomeEnum someProperty;
- (void)doSomethingWithEnum:(enum SomeEnum)enumValue;
However correct Cocoa enums are typedefs to NSInteger: typedef NS_ENUM(NSInteger, MyEnum), and the enum keyword doesn't hold enough information for how much space to allocate when using it, so you'll get into all kind of compiler error when you want to use declarations like this. Thus an enum declared in Swift is not forward declarable in Objective-C.
Now, if you really want to keep the enum definition in Swift, you could use a workaround, and declare it as NSInteger in Objective-C, while providing a specialized property in Swift:
// NS_REFINED_FOR_SWIFT imports this in Swift as __theEnum
#property(nonatomic, assign) NSInteger theEnum NS_REFINED_FOR_SWIFT;
extension MPViewController {
// we provide a wrapper around the Objective-C property
var theEnum: MPSomeEnum {
// this uses a forced unwrap, beware :)
return MPSomeEnum(rawValue: theEnum)!
}
}

Implementing NSCoding protocol

I'm working on a obj-c project and I want to implement the NSCoding protocol for a class, but I can't get it right. The code looks like this:
#include <Foundation/Foundation.h>
class Object: NSObject, NSCoding {
//Somecode
}
And I get the error: "Base specifier must name a class" and "Expected class name". What am I doing wrong?
You are declaring a C++ class, not a Objective-C one, and you cannot have inheritance from one language to another. You'll need to change your class declaration to something like
#interface Object: NSObject <NSCoding> {
// iVar declarations
}
// method and property declarations
#end
Although not sure how much it will help if your class already has defined C++ methods, as you'll need to port those one to Objective-C definitions.
I highly recommend you go through the link I posted in my comments, and read Apple's documentation on working with classes and objects. This will help you with the transition.

Declaring method prototypes in header and implementation

I am learning object orientated programming from the online Stanford courses there is a part I am unsure of regarding declarations. I thought that you must always declare the prototype in the header and then write the code in the implementation file, but the professor wrote a method in the implementation without a declaration prototype in the header file, how come?
Also, may someone please clear the difference between private and public and if the method without a prototype is public or private? The method without a prototype is not from a super class.
That is a perfectly legal way to declare methods that are not to be used outside the class implementation itself.
The compiler will find methods in the implementation file as long as they precede the method in which they are used. However that will not always be the case, as the new LLVM compiler allows methods to be declared in any order and referenced from a given file.
There are a couple of different styles for declaring methods inside an implementation file:
//In the Header File, MyClass.h
#interface MyClass : NSObject
#end
//in the implementation file, MyClass.m
//Method Decls inside a Private Category
#interface MyClass (_Private)
- (void)doSomething;
#end
//As a class extension (new to LLVM compiler)
#interface MyClass ()
- (void)doSomething;
#end
#implementation MyClass
//You can also simply implement a method with no formal "forward" declaration
//in this case you must declare the method before you use it, unless you're using the
//latest LLVM Compiler (See the WWDC Session on Modern Objective C)
- (void)doSomething {
}
- (void)foo {
[self doSomething];
}
#end
If you write the method in you header file it is public and accessible for other classes / objects. If you do not declare it in the header file the method is a private method meaning that you can access it internally in you class but no other class can use this method.

Objective-C use of #import and inheritance

I have a hypothetical UIViewController class named "foo". foo inherits from class bar and class bar #import's "Class A", a class which foo uses extensively. The problem is, when I'm using an instance of class A in foo, I don't get any compiler errors, but I do get a warning for instance, that an instance of Class A does not respond to a particular method. Do I have to explicitly #import ClassA.h into class 'foo'? even though class foo extends extends bar, which already imports it?
Hope that's not too confusing. Let me know if I need to clear anything up.
It sounds like you have a circular dependency issue. In order to resolve it, yes, each imlementation file (.m) needs to #import the proper header file. However, if you try to have the header files #import each other, you'll run into problems.
In order to use inheritance, you need to know the size of the superclass, which means you need to #import it. For other things, though, such as member variables which are pointers, or methods which take as a parameter or return the other type, you don't actually need the class definition, so you can use a forward reference to resolve the compiler errors.
// bar.h
#class A; // forward declaration of class A -- do not to #import it here
#interface bar : UIViewController
{
A *member; // ok
}
- (A) method:(A)parameter; // also ok
#end
// bar.m
#import "bar.h"
#import "A.h"
// can now use bar & A without any errors or warnings