Objective-C互換のSwiftコード

Appleは、 「Objective-C」アプリケーション内で「Swift」コードを使用する方法(およびその逆)について一見詳細なドキュメントを作成しましたが、何らかの理由でこれでは不十分です。 「Swift」で完全に記述されたフレームワークが「Objective-C」アプリケーションと互換性があることを確認する必要性を最初に感じたとき、何らかの理由でアップルのドキュメントが答えよりも多くの質問を提起しました(まあ、少なくとも多くのスペースを残しました) 。 検索エンジンの集中的な使用により、このトピックはWebでかなり不十分にしかカバーされていないことがわかりました。StackOverflowに関するいくつかの質問、(もちろん英語のリソースに関する )いくつかの入門記事 -発見されたすべてです。



この記事は、得られた情報と得られた経験を一般化したものです。 彼らが言うように、それは良い習慣と呼ばれるふりをするのではなく、記述された状況で可能な行動を示唆する、または一種の学術実験であると強調します。







最終更新日: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」ランタイムに転送され、ほとんどの場合、少なくとも高速では動作しません。 肉眼による多くの場合の違いは顕著ではありませんが。



All Articles