うまく組み合わせる(part III)
同じプロジェクトでの Swift と Objective-C
Objective-C との Swift の互換性により、どちらかの言語で書かれたファイルを含むプロジェクトを作成できるようになります。言語が混在したコードベースを持っているアプリを書くためには、この機能、mix and match (うまく組み合わせる) と呼ばれる特徴を使用できます。うまく組み合わせると、最新の Swift の機能を使用して、アプリの機能の一部を実装して、シームレスに既存の Objective-C のコードベースに戻すことができます。
「うまく組み合わせる」概観
Objective-C と Swift のファイルは、プロジェクトが元々 Objective-C または Swift プロジェクトであっても、単一のプロジェクトで共存することができます。既存のプロジェクトに直接他の言語のファイルを単に追加することができます。この自然なワークフローは、単一の言語で書かれたアプリやフレームワークのターゲットを作成することと同じく簡単で、言語が混在したアプリやフレームワークのターゲットを作成できます。
言語が混在したターゲットで作業するプロセスは、あなたがアプリを書いているかフレームワークを書いているかによってわずかに異なります。同じターゲット内で両方の言語で作業するための一般的な import モデルは下の図に示されており、以下の節で詳しく説明しています。
同じアプリターゲット内からコードを import
言語の混じったアプリを書いている場合は、Swift から Objective-C のコードにアクセスし、Objective-C から Swift のコードにアクセスする必要があります。この節で説明するプロセスは、フレームワークでないターゲットに適用されます。
Objective-C から Swift への import
Swift コードと同じアプリターゲットにある Objective-C のファイルのセットを import するには、Swft にそれらのファイルを公開する Objective-C ブリッジヘッダ に依頼しましょう。Xcode は既存の Objective-C アプリに Swift ファイル、または既存の Swift アプリに Objective-C ファイルを追加するときに、このヘッダファイルを作成するために提案します。
同意する場合、Xcode は作成していたファイルとともにヘッダーファイルを作成し、製品モジュール名の後に "-Bridging-Header.h" を追加します。(製品モジュール名の詳細については、後で プロダクトモジュールの命名 で学びます。)
代わりに、[File]> [New]> [File]> [(iOS,watchOS,tvOS または mac OS)]>[Source]>[Header File] を選択することで、ご自分でブリッジヘッダを作成することもできます。
Swift コードに Objective-C コードを公開するためのブリッジヘッダファイルを編集する必要があります。
同じターゲットから Swift に Objective-C コードを import するには
- Objective-C のブリッジヘッダファイルでは、Swift に公開したいすべての Objective-C ヘッダを import して下さい。たとえば、以下のように:
- #import "XYZCustomCell.h"
- #import "XYZCustomView.h"
- #import "XYZCustomViewController.h"
- [Build Settings] で、[Swift Compiler - Code Generation] で [Objective-C Bridging Header] ビルド設定がブリッジヘッダへのパスであるのを確認して下さい。
パスは、Info.plist のパスが [Build Setting(ビルド設定)] で指定されている方法と同様に、プロジェクトへの相対パスである必要があります。ほとんどの場合、この設定を変更する必要はありません。
<<OBJECTIVE-C>>
このブリッジヘッダファイルにリストされているすべての public の Objective-C ヘッダは、Swift に表示されます。Objective-C の機能は、import 文なしで、自動的にそのターゲット内の任意の Swift ファイルで利用できるようになります。システムクラスで使用するものと同じ Swift 構文でカスタム Objective-C のコードを使用して下さい。
- let myCell = XYZCustomCell()
- myCell.subtitle = "A custom cell"
<< SWIFT >>
Swift から Objective-C への import
Swift コードから Objective-C に import するときには、Objective-C にそれらのファイルを公開するように Xcode で生成されたヘッダ ファイルに依頼します。この自動的に生成されたファイルは、ターゲット内の Swift インタフェースを宣言する Objective-C ヘッダです。それは Swift コードのアンブレラヘッダと考えることができます。このヘッダの名前はプロダクトモジュールの名前の後に "-Swift.h" が続く名前です。(プロダクトモジュールの命名については、後で プロダクトモジュールの命名 で詳細を学びます。)
デフォルトでは、生成されたヘッダは、public または open 修飾子でマークされた Swift 宣言用のインタフェースを含んでいます。また、アプリのターゲットに Objective-C のブリッジヘッダがある場合は、internal 修飾子でマークされたものも含みます。private または fileprivate 修飾子でマークされた宣言は生成されたヘッダには現れません。これらは明示的に @IBAction、@IBOutlet、または @objc でマークされない限り、private 宣言は、Objective-C には公開されません。あなたのアプリターゲットがテストが有効になるようにコンパイルされている場合、ユニットテストターゲットは、public 修飾子で宣言されていて、@testable をプロダクトモジュールの import 文に用意することによって宣言されているかのように、internal 修飾子で全ての宣言にアクセスできます。
アクセスレベルの修飾子の詳細については、Swift プログラミング言語(Swift 4.0.3) の アクセス制御 を参照してください。
生成されたヘッダファイル、つまりちょうど Objective-C コードでその内容を使用するように import したものを作成するためには特別なものを何も作成する必要はありません。生成されたヘッダ内の Swift インターフェイスがそれらに使用される Objective-C 型へのすべての参照を含んでいることに注意してください。Swift コードで Objective-C の独自の型を使用する場合は、それから Swift コードにアクセスしたい Objective-C .m ファイルに、Swift が作成したヘッダを import する前に、それらの型のための Objective-C ヘッダを確実に import して下さい。
同じターゲットから Objective-C に Swift コードを import するには
- この構文を使用して適切な名前に置き換えてそのターゲット内の任意の Objective-C .m ファイルにそのターゲットから Swift コードを import します。
<<OBJECTIVE-C>>
#import "ProductModuleName-Swift.h"
ターゲット内の Swift ファイルは、この import 文を含む Objective-C の .m ファイルに表示されます。Objective-C コードから Swift を使用する方法については、Objective-C からの Swift の使い方 を参照してください。
Swift へのインポート | Objective-C へのインポート | |
---|---|---|
Swift コード | import 文なし | #import "ProductModuleName-Swift.h" |
Objective-C コード | import 文なし; Objective-C ブリッジヘッダ必要 | #import "Header.h" |
同じフレームワークターゲット内からコードを import
言語の混じったフレームワークを書いている場合は、Swift から Objective-C のコードにアクセスし、Objective-C から Swift コードにアクセスする必要があります。
Objective-C から Swift への import
Swift コードと同じフレームワークターゲットに Objective-C ファイルのセットを import するには、フレームワークの Objective-C のアンブレラヘッダにそれらのファイルを import する必要があります。
同じフレームワークから Swift に Objective-C コードを import するには
- [Build Setting(ビルド設定)]で、パッケージに、そのフレームワークターゲットの定義モジュールの設定が "Yes" に設定されていることを確認してください。
- あなたのアンブレラヘッダファイルで、Swift に公開したいすべての Objective-C のヘッダを import します。たとえば、次のように:
- #import <XYZ/XYZCustomCell.h>
- #import <XYZ/XYZCustomView.h>
- #import <XYZ/XYZCustomViewController.h>
<<OBJECTIVE-C>>
Swift では、アンブレラヘッダで public に公開する全てのヘッダが現れます。そのフレームワークの中の Objective-C ファイルの内容は、全く import 文なしで、そのフレームワークターゲット内の任意の Swift ファイルで自動的に利用できるようになります。システムクラスで使用するものと同じ Swift の構文でカスタムの Objective-C のコードを使用して下さい。
- let myOtherCell = XYZCustomCell()
- myOtherCell.subtitle = "Another custom cell"
<< SWIFT >>
Swift から Objective-C への import
Objective-C コードと同じフレームワークターゲットに Swift ファイルのセットを import するには、フレームワークのアンブレラヘッダには何も import する必要はありません。その代わりに、それから Swift コードを使用したい全ての Objective-C の .m のファイルに Swift コード用に Xcode が生成したヘッダファイルを import します。
フレームワークターゲットのために生成されたヘッダは、フレームワークの public のインターフェイスの一部であるため、public または open 修飾子でマークされた宣言だけが、フレームワークターゲットのために生成されたヘッダに表示されます。
internal 修飾子でマークされ、Objective-C クラスから継承したクラス内で宣言された Swift メソッドおよびプロパティは、Objective-C 実行環境にアクセスできます。ただし、コンパイル時にはアクセスできず、フレームワークターゲット用に生成されたヘッダには表示されません。
アクセスレベルの修飾子の詳細については、Swift プログラミング言語(Swift 4.0.3) の アクセス制御 を参照してください。
同じフレームワークから Objective-C の中に Swift コードを import するには
- [Build Setting]で、[Packaging]に、そのフレームワークターゲットの [Defines Module] 設定が、[YES] に設定してあるのを確認して下さい。
- そのフレームワークターゲットから、Swift コードを任意の Objective-C の .m ファイルに次の構文を使用し、適切な名前に置き換えて import します。
<<OBJECTIVE-C>>
"#import <ProductName/ProductModuleName-Swift.h>
フレームワーク・ターゲット内の Swift ファイルは、この import 文を含む Objective-C の .m ファイルに現れます。Objective-C コードから Swift を使用する方法については、Objective-C からの Swift の使い方 を参照してください。
Swift への import | Object-C への import | |
---|---|---|
Swift コード | import 文なし | #import <ProductName/ProductModuleName-Swift.h> |
Objective-C コード | import 文なし;Objective-C アンブレラヘッダ必要 | #import "Header.h" |
外部フレームワークの import
純粋な Objective-C のコードベースを持つ、または純粋な Swift のコードベースを持つ、または言語の混じったコードベースを持っている外部のフレームワークを import することができます。外部のフレームワークを import するためのプロセスは、フレームワークが、単一の言語で書かれたか、または両方の言語のファイルを含んでいるかどうかと同じです。外部のフレームワークを import する時は、import しているフレームワークの定義モジュールのビルド設定が "Yes" に設定されていることを確認してください。
以下の構文を使用して、異なるターゲット内の Swift ファイルどれでもにフレームワークを import できます。
<< SWIFT >>
import FrameworkName
以下の構文を使用して、異なるターゲット内の Objective-C の .m ファイルどれでもにフレームワークを import できます。
<<OBJECTIVE-C>>
@import FrameworkName;
Swift への import | Objective-C への import | |
---|---|---|
任意の言語のフレームワーク | import FrameworkName | @import FrameworkName; |
Objective-C からの Swift の使い方
Objective-C に Swift コードを一度 import すると、Swift のクラスを操作するために通常の Objective-C の構文を使用できます。
- MySwiftClass *swiftObject = [[MySwiftClass alloc] init];
- [swiftObject swiftMethod];
<<OBJECTIVE-C>>
Swift のクラスは Objective-C でアクセス可能で使用できるように、Objective-C クラスの子孫でなければなりません。Objective-C からアクセスできる情報と Swift インターフェイスの import 方法の詳細については、Swift の型の互換性 を参照してください。
Objective-C ヘッダでの Swift クラスまたはプロトコルの参照
あなたのコードが別のモジュールに由来する Swift クラスまたはプロトコルを参照する場合、Swift モジュールを @import を使用して Objective-C ヘッダにインポートして下さい。ただし、循環参照を避けるために、Swift コードを 同じ モジュール内から Objective-C ヘッダ (.h) ファイルに import しないでください。代わりに、Swift クラスまたはプロトコルを Objective-C インターフェイス内で参照するように前方宣言することができます。
- // MyObjcClass.h
- @class MySwiftClass;
- @protocol MySwiftProtocol;
- @interface MyObjcClass : NSObject
- - (MySwiftClass *)returnSwiftClassInstance;
- - (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
- // ...
- @end
<<OBJECTIVE-C>>
Swift クラスとプロトコルの前方宣言は、メソッド宣言とプロパティ宣言の型としてのみ使用できます。
Objective-C クラスで採用可能な Swift プロトコルを宣言
Objective-C クラスが採用できる Swift プロトコルを作成するには、protocol 宣言を @objc 属性でマークします。
- @objc public protocol MySwiftProtocol {
-         func requiredMethod()
-         @objc optional func optionalMethod()
- }
<< SWIFT >>
プロトコルは、Objective-C クラスがプロトコルに準拠するために実装しなければならないすべてのイニシャライザ、プロパティ、サブスクリプト、およびメソッドを宣言します。optional のプロトコル要件は、@ objc 属性でマークし、optional の修飾子を付けなければなりません。
Objective-C の実装での Swift プロトコルの採用
Objective-C クラスは、Swift コード用の Xcode が生成したヘッダを import し、クラス拡張を使用して、実装(.m) ファイルで Swift プロトコルを採用できます。
- // MyObjcClass.m
- #import "ProductModuleName-Swift.h"
- @interface MyObjcClass () <MySwiftProtocol>
- // ...
- @end
- @implementation MyObjcClass
- // ...
- @end
<<OBJECTIVE-C>>
Objective-C から使用できる Swift のエラー型の宣言
Error プロトコルに準拠し、@objc 属性で宣言された Swift の列挙型は、NS_ENUM 宣言と、生成されたヘッダ内の対応するエラードメインの NSString 定数を生成します。たとえば、以下の Swift 列挙型宣言があるとします。
- @objc public enum CustomError: Int, Error {
-         case a, b, c
- }
<< SWIFT >>
生成されたヘッダに対応する Objective-C の宣言は以下のとおりです。
- // Project-Swift.h
- typedef SWIFT_ENUM(NSInteger, CustomError) {
-     CustomErrorA = 0,
-     CustomErrorB = 1,
-     CustomErrorC = 2,
- };
- static NSString * const CustomErrorDomain = @"Project.CustomError";
<<OBJECTIVE-C>>
Objective-C インターフェイス用に Swift 名をオーバーライド
Swift コンパイラは Objective-C コードを従来の Swift コードとして自動的に import します。Objective-C のクラスファクトリメソッドを Swift のイニシャライザとして import し、Objective-C 列挙型の case は名前を切り捨てます。
自動的に処理されない境界のケースがコード内にある可能性があります。Objective-C のメソッド、列挙型の case、またはオプション設定値の Swift によって import された名前を変更する必要がある場合は、 NS_SWIFT_NAME マクロを使用して宣言の import 方法をカスタマイズできます。
クラスファクトリメソッド
Swift コンパイラがクラスファクトリメソッドを識別するのを失敗した場合は、NS_SWIFT_NAME マクロを使用して、イニシャライザの Swift 署名を渡して、正しく import されるようにできます。 例えば:
<<OBJECTIVE-C>>
+ (instancetype)recordWithRPM:(NSUInteger)RPM NS_SWIFT_NAME(init(RPM:));
Swift コンパイラがメソッドを誤ってクラスファクトリメソッドと識別した場合は、メソッドの Swift 署名を渡して正しく import するように NS_SWIFT_NAME マクロを使用できます。例えば:
<<OBJECTIVE-C>>
+ (id)recordWithQuality:(double)quality NS_SWIFT_NAME(record(quality:));
列挙型
デフォルトでは、Swift は列挙型の値の名前接頭辞を切り捨てて列挙型を import します。列挙型の case の名前をカスタマイズするには、NS_SWIFT_NAME マクロを使用して Swift 列挙型の case 名を渡します。例えば:
- typedef NS_ENUM(NSInteger, ABCRecordSide) {
-     ABCRecordSideA,
-     ABCRecordSideB NS_SWIFT_NAME(FlipSide),
- };
<<OBJECTIVE-C>>
Objective-C 宣言を洗練する
Objective-C メソッド宣言で NS_REFINED_FOR_SWIFT マクロを使用すると、拡張機能で洗練された Swift のインタフェースを提供し、元の実装を洗練されたインタフェースから呼び出す事ができます。たとえば、1つ以上のポインタ引数をとる Objective-C メソッドを、Swift で洗練して、値のタプルを返すことができます。
- イニシャライザメソッドは、最初の引数ラベルの前に2つのアンダースコア(__) を付けて Swift に import されます。
- ゲッタメソッドまたはセッタメソッドのいずれかが NS_REFINED_FOR_SWIFT でマークされている場合、オブジェクトサブスクリプトメソッドは Swift によって Swift のサブスクリプトではなく、ベース名の前に2つのアンダースコア(__) が付いたメソッドとして import されます。
- 他のメソッドは、ベース名の前に2つののアンダースコア(__) をつけて import されます。
以下の Objective-C 宣言があるとします。
- @interface Color : NSObject
- - (void)getRed:(nullable CGFloat *)red
-                 green:(nullable CGFloat *)green
-                   blue:(nullable CGFloat *)blue
-                 alpha:(nullable CGFloat *)alpha NS_REFINED_FOR_SWIFT;
- @end
<<OBJECTIVE-C>>
以下のように拡張機能内で、洗練された Swift インターフェースを提供することができます:
- extension Color {
-         var RGBA: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
-                 var r: CGFloat = 0.0
-                 var g: CGFloat = 0.0
-                 var b: CGFloat = 0.0
-                 var a: CGFloat = 0.0
-                 __getRed(red: &r, green: &g, blue: &b, alpha: &a)
-                 return (red: r, green: g, blue: b, alpha: a)
-         }
- }
<< SWIFT >>
Swift で Objective-C インターフェイスを使用できないようにする
Objective-C のインタフェースのいくつかは、Swift インタフェースとして公開するのに適していないものや、必要でないものもあります。Objective-C の宣言が Swift によって import されないようにするには、NS_SWIFT_UNAVAILABLE マクロを使用して API の使用者に、存在する代替案を指示するメッセージを渡します。
たとえば、キー値ペアの可変個数引数を取るコンビニエンスイニシャライザを提供する Objective-C クラスは、Swift の使用者に、代わりに辞書リテラルを使用するように助言するかもしれません:
<<OBJECTIVE-C>>
+ (instancetype)collectionWithValues:(NSArray *)values forKeys:(NSArray<NSCopying>
*)keys NS_SWIFT_UNAVAILABLE("Use a dictionary literal instead");
Swift コードから +collectionWithValues:forKeys: メソッドを呼び出そうとすると、コンパイラエラーが発生します。
Swift と Objective-C の両方でコンパイル時に Objective-C の宣言を使用できないようにするには、NS_UNAVAILABLE マクロを使用します。マクロは、カスタマイズ可能なエラーメッセージを省略し、Objective-C コードの宣言へのコンパイル時のアクセスを制限する点を除いて、NS_SWIFT_UNAVAILABLE マクロと同様に動作します。
Objective-C API へ利用可能性情報の追加
Swift では、@available 属性を使用して、特定のターゲットプラットフォーム用にアプリをビルドする際に宣言を使用できるかどうかを制御します。同様に、利用可能性条件 #available を使用して、必要なプラットフォームおよびバージョン条件に基づいて条件付きでコードを実行します。
両方の種類の利用可能性指定子は、以下の例に示す、対応する構文を使用して Objective-C で使用できます。
以下の例は、Swift の宣言で使用される利用可能性情報を示しています。
- @available(iOS 11, macOS 10.13, *)
- func newMethod() {
-         // Use iOS 11 APIs.
- }
<< SWIFT >>
Objective-C に同じ利用可能性情報を追加する方法は以下のとおりです。
- @interface MyViewController : UIViewController
- - (void) newMethod API_AVAILABLE(ios(11), macosx(10.13));
- @end
<<OBJECTIVE-C>>
以下の例は、Swift の条件文で使用される利用可能性情報を示しています。
- if #available(iOS 11, *) {
-         // Use iOS 11 APIs.
- } else {
-         // Alternative code for earlier versions of iOS.
- }
<< SWIFT >>
Objective-C で同じ利用可能性情報を使用する方法は以下のとおりです。
- if (@available(iOS 11, *)) {
-         // Use iOS 11 APIs.
- } else {
-         // Alternative code for earlier versions of iOS.
- }
<<OBJECTIVE-C>>
プラットフォームの利用可能性の特定に関する詳細については、Swiftプログラミング言語(Swift 4.0.3) の 宣言の属性 を参照してください。
プロダクトモジュールの命名
Swift コード用の、Xcode で生成されたヘッダの名前と Xcode が作成した Objective-C のブリッジヘッダの名前はあなたのプロダクトモジュールの名前から生成されます。デフォルトでは、プロダクトモジュールの名前は、プロダクトの名前と同じです。しかし、プロダクトの名前が、ピリオド(.) のような英数字以外の文字を持つ場合は、それらはプロダクトモジュールの名前ではアンダースコア(_) に置き換えられます。名前が数字で始まる場合、最初の数字はアンダースコアに置き換えられます。
また、プロダクトモジュールの名前にカスタムの名前も提供でき、ブリッジヘッダおよび生成されたヘッダに名前を付けるときに Xcode はこれを使用します。これを行うには、プロダクトモジュール名のビルド設定を変更して下さい。
トラブルシューティングのヒントと注意
- コードと同じコレクションとして Swift と Objective-C のファイルを扱い、名前の重複に気を付けて下さい。
- フレームワークで作業している場合は、[Packaging] の [Defines Module(モジュール定義)(DEFINES_MODULE)] の[build setting(ビルド設定)]を"YES" に設定していることを確認してください。
- Objective-C のブリッジヘッダで作業している場合は、[Swift コンパイラ] の [Objective-C のブリッジヘッダ(SWIFT_OBJC_BRIDGING_HEADER)] のビルド設定ー[Code Generation(コード生成)] は、プロジェクトへの相対パスでありヘッダへのパスに設定した事を確認してください。(例えば、“MyApp/MyApp-Bridging-Header.h”)
- Xcode はターゲット名 (TARGET_NAME) ではなくプロダクトモジュール名(PRODUCT_MODULE_NAME) を使用し、その際に Objective-C のブリッジヘッダと Swift コード用に生成されたヘッダに名前を付けます。プロダクトモジュールの命名については、プロダクトモジュールの命名 を参照してください。
- Objective-C でアクセス可能で使用可能にするには、Swift クラスは Objective-C クラスの子孫でなければならないか、@objc でマークしなければなりません。
- Objective-C に Swift のコードを持って来る時には、その Objective-C は、Swift に特有の機能を翻訳することはできないと覚えていて下さい。リストについては、Objective-C からの Swift の使い方 を参照してください。
- Swift のコードで Objective-C の独自の型を使用する場合は、そこから Swift のコードを使用したい Objective-C の .m ファイルに Swift が生成したヘッダを import する前に、それらの型のための Objective-C のヘッダを import してください。
- private または fileprivate 修飾子でマークされた Swift の宣言は、生成されたヘッダに現れません。これらは明示的に @IBAction、@IBOutlet または @objc でマークされない限り、private 宣言は、Objective-C には公開されません。
- アプリのターゲットの場合、internal 修飾子でマークされた宣言は、アプリのターゲットに Objective-C のブリッジヘッダがある場合、生成されたヘッダに表示されます。
- フレームワークターゲットの場合、public または open 修飾子の付いた宣言だけが、生成されたヘッダに表示されます。それらが Objective-C クラスから継承するクラス内で宣言されている限り、あなたのフレームワークの Objective-C の部分からの internal 修飾子でマークされた Swift メソッドやプロパティを使用することがまだできます。アクセスレベルの修飾子の詳細については、Swift プログラミング言語(Swift 4.0.3) の アクセス制御 を参照してください。
前:C の API との相互作用 次:Objective-C から Swift へのコードの移行