27

Im in a situation where I need to use Objective-C category to extend a Swift class. I've done something as follows:

In "SomeClass.swift":

class SomeClass: NSObject {
}

In "SomeClass+Extension.h":

#import "Project-Swift.h"
@interface SomeClass (Extension) 
-(void)someMethod();
@end

This has worked well. And if I try to use the SomeClass extension in my Objective C code, it is fine.

The problem is, if I want to use someMethod() in a another Swift class, I will need to put the SomeClass+Extension.h file into my ObjC-BridgingHeader.h file.

But doing this will cause a circular dependency, because SomeClass+Extension.h also imports Project-Swift.h.

Does anyone have a good way to get around this?

Please note that simply forward declaring the class in the category header will not work, as categories cannot use forward declarations for it's own implementation as so:

@class SomeClass without importing Project-Swift.h will give a compile error.

Cœur
  • 37,241
  • 25
  • 195
  • 267
stephen
  • 1,617
  • 3
  • 20
  • 27
  • http://stackoverflow.com/questions/27761407/swift-class-using-objective-c-class-using-swift-class?rq=1 – tktsubota Apr 12 '16 at 04:20
  • @TroyT the solution you posted will not work, because this is a objective-C category, in a category you cannot simply forward declare a class, you must import the header file of the class your extending. – stephen Apr 12 '16 at 04:31
  • First of all try to understand the difference between @class and #import you can find an excellent answer here http://stackoverflow.com/questions/322597/class-vs-import – Johnykutty Apr 12 '16 at 05:17
  • @Johnykutty I know the difference between @class and #import. But in this case @class will not work because it's a category. In a category you must explicitly import the header file of the class your extending, if you try to do forward declaration you will get a `class undefined error`. So unforunately your solution will not work. – stephen Apr 12 '16 at 06:53
  • even if its a class @ class will not work, because @ class directive says the compiler only that it is a class, no other informations like its properties, methods, superclass etc. If you want to use them you should import the corresponding header file – Johnykutty Apr 12 '16 at 06:57
  • Where exactly can I import the header file? As mentioned in the question the only place you could put the header file for Swift to use is in the bridging header. However if you do that you will have a circular dependency, because the category itself must import the `Project-Swift.h` header. Is there some pseudocode you could write and show me? – stephen Apr 12 '16 at 12:20

3 Answers3

21

The Bad

i too have been fighting this issue a bunch. unfortunately the documentation pretty explicitly states that this pattern is not allowed:

To avoid cyclical references, don’t import Swift code into an Objective-C header (.h) file. Instead, you can forward declare a Swift class or protocol to reference it in an Objective-C interface.

Forward declarations of Swift classes and protocols can only be used as types for method and property declarations.

also throughout the the linked page you will notice it keeps mentioning to import the generated header specifically into the .m file:

To import Swift code into Objective-C from the same target

Import the Swift code from that target into any Objective-C .m file within that target

The Good

one solution that may work for you is to create a swift extension that redefines each method you need in the category. it is fragile and ugly, but arguably the cleanest solution.

/**
 Add category methods from objc here (since circular references prohibit the ObjC extension file)
 */
extension SomeClass {
    @nonobjc func someMethod() {
        self.performSelector(Selector("someMethod"))
    }
}
  • adding the @noobjc to the front allows the same method signature to be used w/o overriding the ObjC implementation
  • now the import "SomeClass+Extension.h" from the bridging header can be removed

if support for more than two input params is needed, or tighter type coupling is desired i would recommend using the runtime to call the underlying function. a great description is here.

Casey
  • 6,531
  • 24
  • 43
  • seems like there's no really easy way to do it, but thanks for clearing it up with official documentation. Accepting as correct answer + awarding for bounty – stephen Apr 21 '16 at 07:04
  • I would like to improve a little on this answer: The method may have one parameter passed to it using the label `with:`. The method signature than needs a colon at the end `Selector("someMethod:")`. A return value is given as an unretained untyped value which you can retrieve calling the `takeUnretainedValue()` method on the return value of `perform` and safe-casting it to the desired value. – TAKeanice Jan 09 '23 at 20:12
4

From the Interoperability guide, we cannot directly access the subclassed / categorized / extensioned Objc-objects for the .swift [SomeClass] class.

But as a turn-around, we can do this:

For Variables , we can do this:

extension Class {
    private struct AssociatedKeys {
        static var DescriptiveName = "sh_DescriptiveName"
    }

    var descriptiveName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
        }

        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.DescriptiveName,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }
}

For Methods, we can use method_swizzling which is not recommended.

itechnician
  • 1,645
  • 1
  • 14
  • 24
2

As one simple solution, you can move the extension to your Swift code. Then you won't have any dependency problems.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 10
    This is not really what I need as the whole point is to reuse a lot of Objective C code without needing to rewrite it in Swift. – stephen Apr 19 '16 at 06:45