この記事は、得られた情報と得られた経験を一般化したものです。 彼らが言うように、それは良い習慣と呼ばれるふりをするのではなく、記述された状況で可能な行動を示唆する、または一種の学術実験であると強調します。
最終更新日:2019年2月
TL; DR。 「Objective-C」内で「Swift」コードを使用するには、「Swift」の「機能」をいくつか放棄し、「Objective-C」メソッドと互換性のないコード( 「structures」 、 「generics」 、 「列挙値」 、 「プロトコル拡張」など)、NSObject
後継NSObject
基づきます。
開始する
そのため、「Objective-C」プロジェクトと、このプロジェクトで使用するいくつかのSwiftコードがあります。 たとえば、 「CocoaPods」を使用して、プロジェクトに追加するサードパーティの「Swift」フレームワークとします。 通常どおり、 「Podfile」に必要な依存関係を追加し、
pod install
実行し、 「xcworkspace」ファイルを開きます 。
フレームワークを「Objective-C」ファイルに
import
するために、「Swift」で行ったようにフレームワーク全体の
import
を登録する必要はありません。「Objective-C」で行ったように、パブリックフレームワークAPIの個々のファイルをインポートしようとします。 フレームワーク機能へのアクセスが必要なファイルでは、
<>-Swift.h
というファイルをインポートします。これは、「Objective-C」ファイルのコンダクターである「Objective-C」ファイルのコンダクターである自動生成ヘッダーファイルです。インポートされたSwiftファイル。 次のようになります。
#import "YourProjectName-Swift.h"
Objective-CファイルでのSwiftクラスの使用
「Swift」ヘッダーをインポートした後、「Objective-C」プロジェクトでクラスまたはメソッドを簡単に使用できた場合、非常に幸運です-誰かがあなたの前に互換性の面倒を見るということです。 実際には、「Objective-C」は
NSObject
の下位クラスのみを「ダイジェスト」し、 パブリックな 「API」のみを表示します。 また、クラス内では、必要なパブリックプロパティ 、 初期化子、およびメソッドに
@objc
で注釈を付ける必要があります。
独自の「Swift」コードをインポートする場合、もちろん、そこから何かを「継承」して
@objc
アノテーション(または属性)を追加する機会があります 。 しかし、この場合、おそらく、「Objective-C」で記述する機会と必要なコードがあります。 したがって、他の誰かの「Swift」コードをプロジェクトにインポートする場合に焦点を当てる方が理にかなっています。 この場合、ほとんどの場合、必要なクラスまたはその他に継承を追加する機会がありません。 この場合の対処方法 ラッパーを書くことは残っています!
インポートされたフレームワークに必要な次のクラスが含まれていると仮定します。
public class SwiftClass { public func swiftMethod() { // Implementation goes here. } }
独自の「Swift」ファイルを作成し、外部フレームワークをそのファイルにインポートし、
NSObject
から継承した独自のクラスを作成し、その中で外部クラス型のプライベートメンバーを宣言します。 外部クラスのメソッドを呼び出すことができるように、クラスのプライベートメンバーを介して外部クラスの対応するメソッドを内部で呼び出すメソッドをクラスに定義します(混乱するように聞こえますが、コードからはすべてが明確だと思います)。
import Foundation import SwiftFramework public class SwiftClassObjCWrapper: NSObject { private let swiftClass = SwiftClass() public func swiftMethod() { swiftClass.swiftMethod() } }
(
NSObject
クラスと
NSObject
アノテーションへのアクセスは、 Foundationのインポート後に表示されます。)
明らかな理由により、宣言で同じクラス名とメソッド名を使用することはできません。 そして、ここで
@objc
アノテーションが役立ちます。
@objc(SwiftClass) public class SwiftClassObjCWrapper: NSObject { private let swiftClass = SwiftClass() @objc public func swiftMethod() { swiftClass.swiftMethod() } }
これで、「Objective-C」コードから呼び出す場合、クラスとメソッドの名前は、外部クラスから対応する名前を記述しているかのように、見たいとおりになります。
SwiftClass *swiftClass = [SwiftClass new]; [swiftClass swiftMethod];
「Objective-C」ファイルで「Swift」メソッドを使用する機能
残念ながら、すべての(パブリック)「Swift」メソッドが単に
@objc
で
@objc
され、「Objective-C」内で使用できるわけではありません。 「Swift」と「Objective-C」は異なる機能と異なるロジックを持つ異なる言語であり、「Swift」コードを記述するとき、「Objective-C」にはない、または根本的に異なる実装の機能を使用することがよくあります。
たとえば、デフォルト設定は破棄する必要があります。 この方法:
@objc public func anotherSwiftMethod(parameter: Int = 1) { // Implementation goes here. }
...「Objective-C」コード内では、次のようになります。
[swiftClassObject anotherSwiftMethodWithParameter:1];
(
1
は渡した値で、引数にはデフォルト値はありません。)
メソッド名
Objective-Cには、Objective-C環境でSwiftメソッドに名前を付ける独自のシステムがあります。 ほとんどの単純なケースでは、十分に満足できますが、多くの場合、読みやすくするには介入が必要です。 たとえば、
do(thing:)
“ Objective-C”の精神にあるメソッドの名前は
doWithThing:
に
doWithThing:
これはおそらく意図と一致しません。 この場合も、
@objc
アノテーションが
@objc
ます:
@objc(doThing:) public func do(thing: Type) { // Implementation goes here. }
例外をスローするメソッド
「Swift」メソッドが
throws
とマークされている場合、「Objective-C」はそのシグネチャに別のパラメーターを追加します-メソッドがスローするエラー。 例:
@objc(doThing:error:) public func do(thing: Type) throws { // Implementation goes here. }
この方法の使用は、「Objective-C」(いわば)の精神で行われます。
NSError *error = nil; [swiftClassObject doThing:thingValue error:&error]; if (error != nil) { // Handle error. }
パラメーターと戻り値でのSwift型の使用
「Swift」 関数のパラメータ値または戻り値が、「Objective-C」環境に自動的に転送されない標準の「Swift」タイプを使用しない場合、このメソッドは「Objective-C」環境で再び機能しません...彼を「思い起こさせる」な。
この「Swift」タイプが
NSObject
の後継である場合、前述のように問題はありません。 しかし、ほとんどの場合、これはそうではないことが判明しています。 この場合、ラッパーは再び役立ちます。 たとえば、元の「Swift」コード:
class SwiftClass { func swiftMethod() { // } } class AnotherSwiftClass { func anotherSwiftMethod() -> SwiftClass { return SwiftClass() } }
彼のためにラップ:
@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()) } }
「Objective-C」内で使用:
AnotherSwiftClass *anotherSwiftClassObject = [AnotherSwiftClass new]; SwiftClass *swiftClassObject = [anotherSwiftClassObject anotherSwiftMethod]; [swiftClassObject swiftMethod];
「Objective-C」クラスによる「Swift」プロトコルの実装
例として、もちろん、パラメータまたはメソッドの戻り値が「Objective-C:では使用できない」「Swift」型を使用するプロトコルを取り上げましょう。
public class SwiftClass { } public protocol SwiftProtocol { func swiftProtocolMethod() -> SwiftClass } public func swiftMethod(swiftProtocolObject: SwiftProtocol) { // Implementation goes here. }
もう一度ラップする必要があります。 はじめに
SwiftClass
:
@objc(SwiftClass) public class SwiftClassObjCWrapper: NSObject { let swiftClassObject = SwiftClass() }
次に、
SwiftProtocol
に似た独自のプロトコルを作成しますが、クラスのラップバージョンを使用します。
@objc(SwiftProtocol) public protocol SwiftProtocolObjCWrapper { func swiftProtocolMethod() -> SwiftClassObjCWrapper }
次に、最も興味深いのは、必要な「Swift」プロトコルを適応させる「Swift」クラスを宣言することです。 これは、「Objective-C」プロジェクトで適応するために作成したプロトコルと、元の「Swift」プロトコルのオブジェクトを受け入れる「Swift」メソッドとの間の一種のブリッジになります。 クラスのメンバーには、説明したプロトコルのインスタンスが含まれます。 また、プロトコルメソッドのクラスメソッドは、作成したプロトコルのメソッドを呼び出します。
class SwiftProtocolWrapper: SwiftProtocol { private let swiftProtocolObject: SwiftProtocolObjCWrapper init(swiftProtocolObject: SwiftProtocolObjCWrapper) { self.swiftProtocolObject = swiftProtocolObject } func swiftProtocolMethod() -> SwiftClass { return swiftProtocolObject.swiftProtocolMethod().swiftClassObject } }
残念ながら、プロトコルインスタンスを受け入れるメソッドをラップせずにはできません。
@objc public func swiftMethodWith(swiftProtocolObject: SwiftProtocolObjCWrapper) { methodOwnerObject.swiftMethodWith(swiftProtocolObject: SwiftProtocolWrapper(swiftProtocolObject: swiftProtocolObject)) }
最も簡単なチェーンではありませんか? はい ただし、使用するクラスとプロトコルに具体的な数のメソッドがある場合、ラッパーはソースコードに対してそれほど不均衡に大きくなることはありません。
実際、「Objective-C」コード自体でプロトコルを使用することは、すでに非常に調和しているように見えます。 プロトコルメソッドの実装:
@interface ObjectiveCClass: NSObject<SwiftProtocol> @end @implementation ObjectiveCClass - (SwiftClass *)swiftProtocolMethod { return [SwiftClass new]; } @end
メソッドを使用して:
(ObjectiveCClass *)objectiveCClassObject = [ObjectiveCClass new]; [methodOwnerObject swiftMethodWithSwiftProtocolObject:objectiveCClassObject];
「Swift」および「Objective-C」の列挙型
「Objective-C」プロジェクトで列挙された「Swift」型を使用する場合、警告が1つだけあります。整数型のRaw Typeが必要です。 その場合のみ、
enum
に
@objc
として注釈を付けることができます。
enum
型を変更できないが、「Objective-C」内で使用したい場合はどうなりますか? 通常どおり、この列挙型のインスタンスを使用してメソッドをラップし、独自の
enum
型をスリップ
enum
ます。 例:
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 } } }
おわりに
おそらく、このトピックについて報告したかったのはそれだけです。 ほとんどの場合、「Swift」コードを「Objective-C」に統合する方法は他にもありますが、上記のロジックを使用してそれらに対処することは非常に可能です。
もちろん、このアプローチには欠点があります。 最も明白な(有形の追加コードの記述)に加えて、もう1つ重要なものがあります。「Swift」コードは「Objective-C」ランタイムに転送され、ほとんどの場合、少なくとも高速では動作しません。 肉眼による多くの場合の違いは顕著ではありませんが。