Objective-C compatible Swift code
Although Apple wrote a seemingly detailed documentation on how to use the “Swift” code inside an “Objective-C” application (and vice versa), but when it comes to that, for some reason this is not enough. When I first felt the need to ensure that the framework written entirely in “Swift” was compatible with the “Objective-C” application, the Apple documentation for some reason raised more questions than it answered (well, or at least left a lot of spaces) . The intensive use of search engines has shown that this topic is covered rather poorly on the Web: a couple of questions on StackOverflow , a couple of introductory articles(on English resources, of course) - that’s all that was found.
This article is a generalization of the information found, as well as the experience gained. I emphasize that it does not pretend to be called, as they say, good practice, but only suggests possible actions in the circumstances described or is a kind of academic experiment.
Last updated February 2019.
So, we have an “Objective-C” project and some Swift code that we want to use in this project. For example, let it be a third-party “Swift” framework that we add to the project, say, using “CocoaPods” . As usual, add the desired dependency to the "Podfile" , execute
To import a framework into an “Objective-C” file, you do not need to register the
If after importing the “Swift” header you were able to simply use some class or method in the “Objective-C” project, you are very lucky - it means that someone will take care of compatibility before you. The fact is that “Objective-C” “digests” only descendant classes
If we import our own “Swift” code, then of course we have the opportunity to “inherit” anything from it and an annotation (or attribute)
Suppose the imported framework contains the following class we need:
We create our own “Swift” file, import the external framework into it, create our own class inherited from
(Class access
For obvious reasons, we cannot use the same class and method names in declarations. And here the abstract comes to the rescue
Now, when calling from the "Objective-C" code, the names of classes and methods will look exactly the way we would like to see them - as if we were writing the corresponding names from an external class:
Unfortunately, not all (public) Swift methods can simply be tagged
For example, default settings will have to be discarded. This method:
... inside the "Objective-C" code it will look like this:
(
Objective-C has its own system by which the Swift method will be named in the Objective-C environment. In most simple cases, it is quite satisfactory, but often requires our intervention to become readable. For example, the name of the method in the spirit of
If the “Swift” method is marked
The use of this method will be in the spirit of “Objective-C” (so to speak):
If the parameter values or the return value of the "Swift" function does not use the standard "Swift" type, which is not automatically transferred to the "Objective-C" environment, this method will not work again in the "Objective-C" environment ... if do not “conjure” him.
If this “Swift” type is the heir
Wrap for him:
Use inside "Objective-C":
As an example, let’s take, of course, a protocol in which the parameters or return values of methods use “Swift” types that cannot be used in “Objective-C:
Will have to wrap again. To get started -
Next, we write our protocol, similar
Next, the most interesting: we declare a “Swift” class that adapts the “Swift” protocol we need. It will be a kind of bridge between our protocol, which we wrote for adaptation in the “Objective-C” project and the “Swift” method, which accepts the object of the original “Swift” protocol. The class members will include an instance of the protocol that we described. And class methods in protocol methods will call the methods of the protocol we wrote:
Unfortunately, you cannot do without wrapping a method that accepts a protocol instance:
Not the easiest chain? Yes. Although, if the classes and protocols used have a tangible number of methods, the wrapper will no longer seem so disproportionately voluminous with respect to the source code.
Actually, using the protocol in the “Objective-C” code itself will look quite harmonious. Implementation of protocol methods:
And using the method:
When using the enumerated “Swift” types in “Objective-C” projects, there is only one caveat: they must have an integer Raw Type . Only after that we can annotate
What if we cannot change the type
That, perhaps, is all that I wanted to report on this topic. Most likely, there are other aspects of the integration of the “Swift” code into the “Objective-C”, but I am sure that it is quite possible to deal with them using the logic described above.
This approach, of course, has its drawbacks. In addition to the most obvious (writing a significant amount of additional code), there is one more important one: the “Swift” code is transferred to the “Objective-C” runtime and will most likely not work as fast or, at least, differently. Although the difference in many cases with the naked eye will not be noticeable.
This article is a generalization of the information found, as well as the experience gained. I emphasize that it does not pretend to be called, as they say, good practice, but only suggests possible actions in the circumstances described or is a kind of academic experiment.
Last updated February 2019.
TL; DR. To use the “Swift” code inside “Objective-C”, you will have to compromise some “features” of “Swift” and write a wrapper over the code that will not use incompatible with “Objective-C” methods ( “structures” , “generics” , «an enum the associated values» , «the extensions protocol» and so forth.), and will be based on klassah- heirsNSObject
.
Start
So, we have an “Objective-C” project and some Swift code that we want to use in this project. For example, let it be a third-party “Swift” framework that we add to the project, say, using “CocoaPods” . As usual, add the desired dependency to the "Podfile" , execute
pod install
, open the "xcworkspace" file . To import a framework into an “Objective-C” file, you do not need to register the
import
entire framework, as we used to do in “Swift”, or try to import individual files of the public framework API , as we used to do in “Objective-C”. In any file in which we need access to the functionality of the framework, we import a file with the name<НазваниеПроекта>-Swift.h
- This is an automatically generated header file that is a conductor of "Objective-C" files to the public "API" contained in the imported "Swift" files. It looks something like this:#import "YourProjectName-Swift.h"
Using Swift Classes in Objective-C Files
If after importing the “Swift” header you were able to simply use some class or method in the “Objective-C” project, you are very lucky - it means that someone will take care of compatibility before you. The fact is that “Objective-C” “digests” only descendant classes
NSObject
and sees only public “APIs”. And inside classes, the necessary public properties , initializers, and methods must be annotated @objc
. If we import our own “Swift” code, then of course we have the opportunity to “inherit” anything from it and an annotation (or attribute)
@objc
add. But in this case, probably, we have the opportunity and the necessary code to write on "Objective-C". Therefore, it makes more sense to focus on the case when we want to import someone else's “Swift” code into our project. In this case, most likely, we don’t have the opportunity to add any inheritance to the required classes or anything else. What to do in this case? It remains to write wrappers ! Suppose the imported framework contains the following class we need:
public class SwiftClass {
public func swiftMethod() {
// Implementation goes here.
}
}
We create our own “Swift” file, import the external framework into it, create our own class inherited from
NSObject
, and in it we declare a private member of the external class type. In order to be able to call methods of the outer class, we define methods in our class that inside ourselves will call the corresponding methods of the outer class through a private member of the class (it sounds confusing, but I think everything is clear from the code):import Foundation
import SwiftFramework
public class SwiftClassObjCWrapper: NSObject {
private let swiftClass = SwiftClass()
public func swiftMethod() {
swiftClass.swiftMethod()
}
}
(Class access
NSObject
and annotations @objc
appear after importing Foundation .) For obvious reasons, we cannot use the same class and method names in declarations. And here the abstract comes to the rescue
@objc
:@objc(SwiftClass)
public class SwiftClassObjCWrapper: NSObject {
private let swiftClass = SwiftClass()
@objc
public func swiftMethod() {
swiftClass.swiftMethod()
}
}
Now, when calling from the "Objective-C" code, the names of classes and methods will look exactly the way we would like to see them - as if we were writing the corresponding names from an external class:
SwiftClass *swiftClass = [SwiftClass new];
[swiftClass swiftMethod];
Features of using “Swift” methods in “Objective-C” files
Unfortunately, not all (public) Swift methods can simply be tagged
@objc
and used inside Objective-C. “Swift” and “Objective-C” are different languages with different capabilities and different logic, and quite often when writing a “Swift” code we use its capabilities that “Objective-C” does not have or which are implemented fundamentally differently. For example, default settings will have to be discarded. This method:
@objc
public func anotherSwiftMethod(parameter: Int = 1) {
// Implementation goes here.
}
... inside the "Objective-C" code it will look like this:
[swiftClassObject anotherSwiftMethodWithParameter:1];
(
1
- this is the value we passed, the default value of the argument is missing.)Method Names
Objective-C has its own system by which the Swift method will be named in the Objective-C environment. In most simple cases, it is quite satisfactory, but often requires our intervention to become readable. For example, the name of the method in the spirit of
do(thing:)
“Objective-C” will turn into doWithThing:
, which, perhaps, does not coincide with our intention. In this case, again, the annotation comes to the rescue @objc
:@objc(doThing:)
public func do(thing: Type) {
// Implementation goes here.
}
Methods Throwing Exceptions
If the “Swift” method is marked
throws
, then “Objective-C” will add one more parameter to its signature - an error that the method may throw. For instance:@objc(doThing:error:)
public func do(thing: Type) throws {
// Implementation goes here.
}
The use of this method will be in the spirit of “Objective-C” (so to speak):
NSError *error = nil;
[swiftClassObject doThing:thingValue error:&error];
if (error != nil) {
// Handle error.
}
Using Swift Types in Parameters and Return Values
If the parameter values or the return value of the "Swift" function does not use the standard "Swift" type, which is not automatically transferred to the "Objective-C" environment, this method will not work again in the "Objective-C" environment ... if do not “conjure” him.
If this “Swift” type is the heir
NSObject
, then, as mentioned above, there is no problem. But most often it turns out that this is not so. In this case, the wrapper helps us again. For example, the original "Swift" code:class SwiftClass {
func swiftMethod() {
//
}
}
class AnotherSwiftClass {
func anotherSwiftMethod() -> SwiftClass {
return SwiftClass()
}
}
Wrap for him:
@objc(SwiftClass)
public class SwiftClassObjCWrapper: NSObject {
private let swiftClassObject: SwiftClass
init(swiftClassObject: SwiftClass) {
self.swiftClassObject = swiftClassObject
super.init()
}
@objc
public func swiftMethod() {
swiftClassObject.swiftMethod()
}
}
@objc(AnotherSwiftClass)
public class AnotherSwiftClassWrapper: NSObject {
private let anotherSwiftClassObject = AnotherSwiftClass()
@objc
func anotherSwiftMethod() -> SwiftClassObjCWrapper {
return SwiftClassObjCWrapper(swiftClassObject: anotherSwiftClassObject.anotherSwiftMethod())
}
}
Use inside "Objective-C":
AnotherSwiftClass *anotherSwiftClassObject = [AnotherSwiftClass new];
SwiftClass *swiftClassObject = [anotherSwiftClassObject anotherSwiftMethod];
[swiftClassObject swiftMethod];
Implementation of “Swift” Protocols by “Objective-C” Classes
As an example, let’s take, of course, a protocol in which the parameters or return values of methods use “Swift” types that cannot be used in “Objective-C:
public class SwiftClass { }
public protocol SwiftProtocol {
func swiftProtocolMethod() -> SwiftClass
}
public func swiftMethod(swiftProtocolObject: SwiftProtocol) {
// Implementation goes here.
}
Will have to wrap again. To get started -
SwiftClass
:@objc(SwiftClass)
public class SwiftClassObjCWrapper: NSObject {
let swiftClassObject = SwiftClass()
}
Next, we write our protocol, similar
SwiftProtocol
, but using wrapped versions of classes:@objc(SwiftProtocol)
public protocol SwiftProtocolObjCWrapper {
func swiftProtocolMethod() -> SwiftClassObjCWrapper
}
Next, the most interesting: we declare a “Swift” class that adapts the “Swift” protocol we need. It will be a kind of bridge between our protocol, which we wrote for adaptation in the “Objective-C” project and the “Swift” method, which accepts the object of the original “Swift” protocol. The class members will include an instance of the protocol that we described. And class methods in protocol methods will call the methods of the protocol we wrote:
class SwiftProtocolWrapper: SwiftProtocol {
private let swiftProtocolObject: SwiftProtocolObjCWrapper
init(swiftProtocolObject: SwiftProtocolObjCWrapper) {
self.swiftProtocolObject = swiftProtocolObject
}
func swiftProtocolMethod() -> SwiftClass {
return swiftProtocolObject.swiftProtocolMethod().swiftClassObject
}
}
Unfortunately, you cannot do without wrapping a method that accepts a protocol instance:
@objc
public func swiftMethodWith(swiftProtocolObject: SwiftProtocolObjCWrapper) {
methodOwnerObject.swiftMethodWith(swiftProtocolObject: SwiftProtocolWrapper(swiftProtocolObject: swiftProtocolObject))
}
Not the easiest chain? Yes. Although, if the classes and protocols used have a tangible number of methods, the wrapper will no longer seem so disproportionately voluminous with respect to the source code.
Actually, using the protocol in the “Objective-C” code itself will look quite harmonious. Implementation of protocol methods:
@interface ObjectiveCClass: NSObject
@end
@implementation ObjectiveCClass
- (SwiftClass *)swiftProtocolMethod {
return [SwiftClass new];
}
@end
And using the method:
(ObjectiveCClass *)objectiveCClassObject = [ObjectiveCClass new];
[methodOwnerObject swiftMethodWithSwiftProtocolObject:objectiveCClassObject];
Enumerated types in “Swift” and “Objective-C”
When using the enumerated “Swift” types in “Objective-C” projects, there is only one caveat: they must have an integer Raw Type . Only after that we can annotate
enum
how @objc
. What if we cannot change the type
enum
, but want to use it inside “Objective-C”? We can, as usual, wrap a method that uses instances of this enumerated type and slip our own to it enum
. For instance:enum SwiftEnum {
case firstCase
case secondCase
}
class SwiftClass {
func swiftMethod() -> SwiftEnum {
// Implementation goes here.
}
}
@objc(SwiftEnum)
enum SwiftEnumObjCWrapper: Int {
case firstCase
case secondCase
}
@objc(SwiftClass)
public class SwiftClassObjCWrapper: NSObject {
let swiftClassObject = SwiftClass()
@objc
public func swiftMethod() -> SwiftEnumObjCWrapper {
switch swiftClassObject.swiftMethod() {
case .firstCase: return .firstCase
case .secondCase: return .secondCase
}
}
}
Conclusion
That, perhaps, is all that I wanted to report on this topic. Most likely, there are other aspects of the integration of the “Swift” code into the “Objective-C”, but I am sure that it is quite possible to deal with them using the logic described above.
This approach, of course, has its drawbacks. In addition to the most obvious (writing a significant amount of additional code), there is one more important one: the “Swift” code is transferred to the “Objective-C” runtime and will most likely not work as fast or, at least, differently. Although the difference in many cases with the naked eye will not be noticeable.