相互運用性(Part II)
Objective-C の API との相互作用
相互運用性 は、Swift と Objective-C との間のどちらの方向でもインタフェースできる可能性であり、ある言語で書かれたコードの部分を他の言語のファイルにアクセスして使用できます。Swift をアプリ開発ワークフローに統合し始めたら、相互運用性を梃入れし、Cocoa アプリの作成方法を再定義し、改善し、強化する方法を理解することをお勧めします。
相互運用性の1つの重要な側面は、Swift のコードを書くときに、Objective-C API で動作できるようにすることです。Objective-C フレームワークを import した後、それからクラスをインスタンス化し、それらを Swift に固有の構文を使用して対話することができます。
初期化
Swift で Objective-C クラスをインスタンス化するには、Swift のイニシャライザの構文で、そのイニシャライザのいずれか 1 つを呼び出します。
Objective-C のイニシャライザはイニシャライザが1つ以上の引数を取る場合は init か initWith: で始まります。Objective-C のイニシャライザが Swift によって import されると、init 接頭辞は init キーワードになり、メソッドが Swift のイニシャライザであることを示します。イニシャライザが引数を取る場合、With は削除され、残りのセレクタはそれに応じて名前付きパラメータに分割されます。
以下の Objective-C のイニシャライザ宣言を考えてみましょう:
- - (instancetype)init;
- - (instancetype)initWithFrame:(CGRect)frame
-                 style:(UITableViewStyle)style;
<<OBJECTIVE-C>>
これに相当する Swift のイニシャライザの宣言は以下のとおりです。
- init() { /* ... */ }
- init(frame: CGRect, style: UITableViewStyle) { /* ... */ }
<< SWIFT >>
Objective-C と Swift 構文の違いは、オブジェクトをインスタンス化するときにはっきりします。
Objective-C では、こうします:
<<OBJECTIVE-C>>
UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
Swift では、こうします:
<< SWIFT >>
let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)
alloc を呼び出す必要がない事に注意して下さい。Swift は正しくこれを処理します。Swift スタイルのイニシャライザを呼び出すときには、"init" がどこにも表れないことに注意してください。
定数や変数に代入するときに明示的な型を指定するか、型を省略して、イニシャライザから自動的に型を Swift に推論させることができます。
<<SWIFT>>
let myTextField = UITextField(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 40.0))
これらの UITableView と UITextField オブジェクトは、Objective-C でインスタンス化したのと同じオブジェクトです。Objective-C と同じ方法でそれらを使用でき、プロパティにアクセスし、それぞれの型で定義された全てのメソッドを呼び出すことができます。
クラスファクトリメソッドとコンビニエンスイニシャライザ
一貫性と簡潔のために、Objective-C のクラスファクトリメソッドは、Swift 内にコンビニエンス・イニシャライザとして import されます。これで、それらはイニシャライザと同じ構文で使用することができます。
たとえば、Objective-C では、以下のようにこのファクトリメソッドを呼び出します:
<<OBJECTIVE-C>>
UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];
Swift では、こうします:
<<SWIFT>>
let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)
失敗可能な初期化
Objective-C では、イニシャライザは、それらが初期化するオブジェクトを直接返します。初期化が失敗したことを呼び出し者に通知するには、Objective-C のイニシャライザは nil を返す事ができます。Swift では、このパターンは 失敗可能な初期化 と呼ばれる言語機能に組み込まれています。
システムフレームワーク内の多くの Objective-C のイニシャライザは、初期化が失敗できるかどうかを示すために、監査を受けました。ヌル可能性 (nullabity) 注釈 を使用して、自分の Objective-C クラスのイニシャライザが失敗できるかどうかを ヌル可能性 (nullabity) と Optionals で説明したように、指定できます。Object-C のイニシャライザは、それらが失敗できるかどうかは、初期化が失敗できない場合 init(...)、または初期化が失敗できる場合 init?(...) のいずれかとして import される事を示します。それ以外の場合、Objective-C のイニシャライザは init!(...) として import されます。
たとえば、イメージファイルが提供されたパスに存在しない場合、UIImage(contentsOfFile:) イニシャライザは UIImage オブジェクトを初期化するのを失敗できます。初期化が成功した場合、Optional の結合を使用して、失敗可能なイニシャライザの結果を開封できます。
- if let image = UIImage(contentsOfFile: "MyImage.png") {
-         // loaded the image successfully
- } else {
-         // could not load the image
- }
<<SWIFT>>
プロパティへのアクセス
@property 構文を使用した Objective-C プロパティ宣言は、Swift プロパティとして以下のように import されます。
- ヌル可能性 (nullabity) プロパティ属性(nonnull、nullable、 および null_resettable) を持つプロパティは、ヌル可能性と Optionals で説明したように、optional または optional でない Swift プロパティとして import されます。
- readonly プロパティ属性を持つプロパティは、getter({get}) を使用して Swift の計算されたプロパティとして import されます。
- weak プロパティ属性を持つプロパティは、weak キーワード(weak var) でマークされた Swift プロパティとして import されます。
- weak 以外の所有権プロパティー属性(つまり、assign、copy、strong、 または unsafe_unretained) を持つプロパティーは、適切な保管場所を持つ Swift プロパティーとして import されます。
- class プロパティ属性を持つプロパティは、Swift 型のプロパティとして import されます。
- Atomic プロパティの属性(atomic および nonatomic) は、対応する Swift プロパティ宣言に反映されませんが、Swift から import されたプロパティにアクセスするとき、Objective-C 実装の atomic の保証が保持されます。
- アクセサのプロパティ属性(getter= および setter=) は Swift によって無視されます。
ドット構文を使用して、Swift 内の Objective-C のオブジェクトのプロパティにアクセスするには、括弧を使わずプロパティの名前を使って下さい。
たとえば、以下のコードを使用して、UITextField オブジェクトの textColor および text プロパティを設定できます。
- myTextField.textColor = UIColor.darkGray
- myTextField.text = "Hello world"
<<SWIFT>>
値を返し、引数を取らない Objective-C メソッドは、ドット構文を使用して Objective-C のプロパティのように呼び出すことができます。しかし、Objective-C の @property 宣言だけがプロパティとして Swift に import されるため、これらはインスタンスメソッドとして Swift に import されます。メソッドは、メソッドでの作業 で説明したように import され、呼び出されます。
メソッドでの作業
Swift から Objective-C メソッドを呼び出す場合は、ドット構文を使用できます。
Objective-C メソッドが Swift に import されたとき、Objective-C のセレクタの最初の部分は、基本メソッド名となり、カッコの前に表示されます。最初の引数は名前なしで、括弧のすぐ内側に表示されます。セレクタ片の残りの部分は、引数名に対応し、括弧の中に表示されます。すべてのセレクタ片は呼び出し側で必要とされます。
たとえば、Objective-C では、こうなるでしょう:
<<OBJECTIVE-C>>
[myTableView insertSubview:mySubview atIndex:2];
Swift では、こうします。
<<SWIFT>>
myTableView.insertSubview(mySubview, at: 2)
引数なしでメソッドを呼び出している場合でも、括弧を含める必要があります。
<<SWIFT>>
myTableView.layoutIfNeeded()
id の互換性
Objective-C の id 型は Swift によって Any 型として import されます。コンパイル時および実行時に、Swift の値またはオブジェクトは Objective-C に id パラメータとして渡されると、コンパイラはユニバーサルブリッジ変換操作を導入します。id 値が Any として Swift に import されると、実行環境は自動的にクラス参照または Swift の値型のいずれかへのブリッジ作業を処理します。
- var x: Any = "hello" as String
- x as? String      // String with value "hello"
- x as? NSString // NSString with value "hello"
- x = "goodbye" as NSString
- x as? String      // String with value "goodbye"
- x as? NSString // NSString with value "goodbye"
<< SWIFT >>
Any のダウンキャスト
基になる型がわかっている、または合理的に決定できる Any 型のオブジェクトを扱う場合、これらのオブジェクトをより具体的な型にダウンキャストすると便利なことがよくあります。ただし、Any 型は全ての型を参照できるため、より具体的な型へのダウンキャストは成功するとは限りません。
条件付き型キャスト演算子(as?) を使用でき、これはダウンキャストしようとしている型の optional 値を返します。
- let userDefaults = UserDefaults.standard
- let lastRefreshDate = userDefaults.object(forKey: "LastRefreshDate") //
lastRefreshDate is of type Any? - if let date = lastRefreshDate as? Date {
-         print("\(date.timeIntervalSinceReferenceDate)")
- }
<< SWIFT >>
オブジェクトの型が確かな場合は、代わりに強制ダウンキャスト演算子(as!) を使用できます。
- let myDate = lastRefreshDate as! Date
- let timeInterval = myDate.timeIntervalSinceReferenceDate
<< SWIFT >>
ただし、強制ダウンキャストが失敗した場合は、実行時エラーが発生します。
<< SWIFT >>
let myDate = lastRefreshDate as! String // Error
動的なメソッドの参照
Swift にはまた、ある種のオブジェクトを表す AnyObject 型も含まれており、全ての @objc メソッドを動的に参照できる特別な機能があります。これにより、id 値を返す Objective-C API への型のないアクセスの柔軟性を維持して書くことができます。
たとえば、全てのクラス型のオブジェクトを AnyObject 型の定数または変数に割り当て、変数を別の型のオブジェクトに再割り当てできます。また、全ての Objective-C メソッドを呼び出して、より具体的なクラス型にキャストすることなく AnyObject 値の任意のプロパティにアクセスすることもできます。
- var myObject: AnyObject = UITableViewCell()
- myObject = NSDate()
- let futureDate = myObject.addingTimeInterval(10)
- let timeSinceNow = myObject.timeIntervalSinceNow
<< SWIFT >>
認識されないセレクタと optional の連鎖
AnyObject 値の特定の型は実行時までわからないので、不注意に安全でないコードを書き込む可能性があります。Swift では Objective-C と同様に、存在しないメソッドを呼び出そうとすると、認識されないセレクタエラーが発生します。
たとえば、以下のコードはコンパイラの警告なしでコンパイルされますが、実行時にエラーが発生します。
- myObject.character(at: 5)
- // crash, myObject doesn't respond to that method
<< SWIFT >>
Swift はこのような危険な行動を防ぐために optionals を使用します。AnyObject 型の値でメソッドを呼び出すと、そのメソッド呼び出しは暗黙的に開封された optional のように動作します。プロトコル内の optional のメソッドに使用するのと同じ optional の連鎖の構文を使用して、任意に AnyObject のメソッドを呼び出せます。
たとえば、以下のコードリストでは、count プロパティと character(at:) メソッドが NSDate オブジェクトに存在しないため、1 行目と 2 行目は実行されません。myCount 定数は、optional の Int であると推定され、nil に設定されます。また、if-let 文を使用して、3 行目に示すように、オブジェクトが応答しないメソッドの結果を条件付きで開封することもできます。
- // myObject has AnyObject type and NSDate value
- let myCount = myObject.count
- // myCount has Int? type and nil value
- let myChar = myObject.character?(at: 5)
- // myChar has unichar? type and nil value
- if let fifthCharacter = myObject.character?(at: 5) {
-         print("Found \(fifthCharacter) at index 5")
- }
- // conditional branch not executed
<< SWIFT >>
ヌル可能性 (Nullabity) と optionals
Objective-C では、NULL (Objective-C では nil として参照されます) の可能性がある生のポインタを使用してオブジェクトへの参照を扱います。Swift では、すべての値は、構造体やオブジェクト参照を含め、null ではないことが保証されています。代わりに、optional の型 で値の型を包み込むことで、不足している値を表します。値が不足していることを示す必要があるときには、nil の値を使用して下さい。optionals の詳細については、Swift プログラミング言語(Swift 4.0.3) の Optionals を参照してください。
Objective-C は、ヌル可能性 (nullabity) の注釈を使用して、パラメータ型、プロパティ型、または戻り値の型が NULL や nil 値を持てるかどうかを指定できます。_Nullable と _Nonnull 注釈を使用して個々の型宣言を監査したり、nullable、nonnull および null_resettable プロパティ属性を使用して個々のプロパティ宣言を監査したり、NS_ASSUME_NONNULL_BEGIN と NS_ASSUME_NONNULL_END マクロを使用して領域全体を nullabity について監査できます。ある型に対して nullabity の情報が提供されていない場合、Swift は optional の参照と optional でない参照を区別できず、暗黙的に開封された optional として import します。
- _Nonnull 注釈または監査された領域のいずれかで、nonnullable と宣言された型は、Swift によって nonoptional として import されます。
- _Nullable 注釈で nullable と宣言された型は、Swift によって optional として import されます。
- ヌル可能性注釈なしで宣言された型は、Swift によって 暗黙的に開封された optional として import されます。
たとえば、以下の Objective-C 宣言を考えてみます:
- @property (nullable) id nullableProperty;
- @property (nonnull) id nonNullProperty;
- @property id unannotatedProperty;
- NS_ASSUME_NONNULL_BEGIN
- - (id)returnsNonNullValue;
- - (void)takesNonNullParameter:(id)value;
- NS_ASSUME_NONNULL_END
- - (nullable id)returnsNullableValue;
- - (void)takesNullableParameter:(nullable id)value;
- - (id)returnsUnannotatedValue;
- - (void)takesUnannotatedParameter:(id)value;
<<OBJECTIVE-C>>
これが Swift に impoort されるとこうなります。
- var nullableProperty: Any?
- var nonNullProperty: Any
- var unannotatedProperty: Any!
- func returnsNonNullValue() -> Any
- func takesNonNullParameter(value: Any)
- func returnsNullableValue() -> Any?
- func takesNullableParameter(value: Any?)
- func returnsUnannotatedValue() -> Any!
- func takesUnannotatedParameter(value: Any!)
<< SWIFT >>
Foundation を含む Objective-C システムフレームワークのほとんどは、すでにヌル可能性注釈を提供しており、慣用的で型安全な方法で値を扱うことができます。
ヌル可能性のない (nonnullable) オブジェクトに Optional をブリッジする
Swift は、optional が基本となる包みこまれた値を含むかどうかによって、ヌル可能性のない (nonnullable) Objective-C オブジェクトに optional の値をブリッジします。optional が nil の場合、Swift は nil 値を NSNull インスタンスとしてブリッジします。それ以外の場合、Swift は optional を、その開封された値としてブリッジします。たとえば、optional 項目の配列([T?]) が NSArray にブリッジされているときと、ヌルでない id を取る Objective-C API に optional が渡されたときのこの動作を参照してください。
以下のコードは String? インスタンスがその値に応じて Objective-C にブリッジする方法を示しています。
- @implementation OptionalBridging
- + (void)logSomeValue:(nonnull id)valueFromSwift {
-         if ([valueFromSwift isKindOfClass: [NSNull class]]) {
-                 os_log(OS_LOG_DEFAULT, "Received an NSNull value.");
-         } else {
-                 os_log(OS_LOG_DEFAULT, "%s", [valueFromSwift UTF8String]);
-         }
- }
- @end
<<OBJECTIVE-C>>
valueFromSwift パラメータの型は id なので、これは Swift の Any 型として以下の Swift コードに import されます。しかし、Any が予想されるときに optional を渡すのは普通ではないので、logSomeValue(_:) クラスのメソッドに渡された optional は Any 型に明示的にキャストされ、コンパイラの警告は消えます。
- let someValue: String? = "Bridge me, please."
- let nilValue: String? = nil
- OptionalBridging.logSomeValue(someValue as Any) // Bridge me, please.
- OptionalBridging.logSomeValue(nilValue as Any)      // Received an NSNull value.
<< SWIFT >>
プロトコルで修飾されたクラス
1 つ以上のプロトコルで修飾された Objective-C クラスは、プロトコル構成型として Swift によって import されます。たとえば、View Controller を参照する以下の Objective-C プロパティがあるとします。
<<OBJECTIVE-C>>
@property UIViewController<UITableViewDataSource, UITableViewDelegate> *
myController;
Swift は、これをこう import します。
<< SWIFT >>
var myController: UIViewController & UITableViewDataSource & UITableViewDelegate
Objective-C のプロトコルで修飾されたメタクラスは、Swift によってプロトコルメタタイプとして import されます。たとえば、指定されたクラスに対して操作を実行する以下の Objective-C メソッドがあるとします。
<<OBJECTIVE-C>>
- (void)doSomethingForClass:(Class<NSCoding>)codingClass;
Swift は、これをこう import します。
<< SWIFT >>
func doSomething(for codingClass: NSCoding.Type)
軽量級の汎用
軽量級の汎用パラメータ化を使用する Objective-C の型宣言は Swift によって import され、その内容の型に関する情報が保存されます。たとえば、以下の Objective-C プロパティ宣言があるとします。
- @property NSArray<NSDate *> *dates;
- @property NSCache<NSObject *, id<NSDiscardableContent>> *cachedData;
- @property NSDictionary <NSString *, NSArray<NSLocale *>> *supportedLocales;
<<OBJECTIVE-C>>
これは Swift に以下のように import されます。
- var dates: [Date]
- var cachedData: NSCache<AnyObject, NSDiscardableContent>
- var supportedLocales: [String: [Locale]]
<< SWIFT >>
Objective-C で記述された、パラメータ化されたクラスは、同じ数の型パラメータを持つ汎用クラスとして Swift に import されます。Swift によって import されたすべての Objective-C の汎用型パラメータには、その型がクラス(T:Any) であることが必須な型制約があります。Objective-C の汎用パラメータ化でクラス修飾が指定されている場合、import された Swift クラスには、その型が指定されたクラスのサブクラスであることが必須な制約があります。Objective-C の汎用パラメータ化でプロトコル修飾が指定されている場合、import された Swift クラスには、その型が指定されたプロトコルに準拠することが必須な制約があります。特殊化されていない Objective-C 型の場合、Swift は import されたクラス型制約の汎用パラメータ化を推論します。たとえば、以下の Objective-C クラスおよびカテゴリ宣言があるとします。
- @interface List<T: id<NSCopying>> : NSObject
- - (List<T> *)listByAppendingItemsInList:(List<T> *)otherList;
- @end
- @interface ListContainer : NSObject
- - (List<NSValue *> *)listOfValues;
- @end
- @interface ListContainer (ObjectList)
- - (List *)listOfObjects;
- @end
<<OBJECTIVE-C>>
これは Swift に以下のように import されます。
- class List<T: NSCopying> : NSObject {
-         func listByAppendingItemsInList(otherList: List<T>) -> List<T>
- }
- class ListContainer : NSObject {
-         func listOfValues() -> List<NSValue>
- }
- extension ListContainer {
-         func listOfObjects() -> List<NSCopying>
- }
<< SWIFT >>
拡張機能
Swift の拡張機能は、Objective-C のカテゴリに似ています。拡張機能 は、Objective-C で定義されたものを含め、既存のクラス、構造体、および列挙型の動作を拡張します。システムフレームワークや、独自のカスタム型のいずれかから、型を拡張機能で定義することができます。単に適切なモジュールを import して、Objective-C で使用するのと同じ名前でクラス、構造体、または列挙型を参照してください。
例えば、提供側の length と開始する point に基づく、正三角形で単純なベジェパスを作成する UIBezierPath クラスを拡張できます。
- extension UIBezierPath {
-        convenience init(triangleSideLength: CGFloat, origin: CGPoint) {
-                self.init()
-                let squareRoot = CGFloat(sqrt(3.0))
-                let altitude = (squareRoot * triangleSideLength) / 2
-                move(to: origin)
-                addLine(to: CGPoint(x: origin.x + triangleSideLength, y: origin.x))
-                addLine(to: CGPoint(x: origin.x + triangleSideLength / 2, y:origin + altitude))
-                close()
-        }
- }
<< SWIFT >>
プロパティ(クラスや静的プロパティを含む) を追加する拡張機能を使用できます。しかし、これらのプロパティを計算しなければなりません。拡張機能は、クラス、構造体、または列挙型に格納されたプロパティを追加することはできません。
以下の例では、計算された area のプロパティを含む CGRect 構造体を拡張します。
- extension CGRect {
-        var area: CGFloat {
-                return width * height
-        }
- }
- let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
- let area = rect.area
<< SWIFT >>
また、それをサブクラス化することなく、クラスにプロトコルの適合性を追加するためにも拡張機能を使用できます。プロトコルが Swift で定義されている場合、Swift または Objective-C で定義されているか、構造体や列挙にそれへの適合性を追加できます。
Objective-C の型の既存のメソッドやプロパティをオーバーライドするためには拡張機能を使用できません。
クロージャ
Objective-C のブロックは、@convention(block) 属性で示される Objective-C ブロックの呼び出し規約を使用して Swift クロージャとして自動的に import されます。たとえば、Objective-C のブロック変数は以下のようになります。
- void (^completionBlock)(NSData *) = ^(NSData *data) {
-         // ...
- }
<<OBJECTIVE-C>>
そして、これは Swift ではこうなります。
- let completionBlock: (Data) -> Void = {data in
-         // ...
- }
<< SWIFT >>
Swift のクロージャと Objective-C のブロックは、互換性がありますので、ブロックを期待する Objective-C のメソッドに Swift のクロージャを渡すことができます。Swift のクロージャと関数は、同じ型なので、Swift の関数の名前を渡すことさえもできます。
クロージャは、ブロックと同様のキャプチャの意味を持っていますが、1つの重要な方法が異なります。変数はコピーされるのではなく可変です。言い換えれば、Objective-C での __block の動作は Swift での変数のデフォルトの動作です。
self をキャプチャする時の強い循環参照を回避する
Objective-C では、ブロック内で self をキャプチャする必要がある場合は、メモリ管理の影響を考慮するのが重要です。
ブロックは、self を含む、キャプチャされたオブジェクトへの強い参照を保持します。self がプロパティをコピーするなどのブロックへの強い参照を保持している場合、これは強い循環参照を作ります。これを避けるには、代わりに、ブロックキャプチャを self への弱い参照にすることができます:
- __weak typeof(self) weakSelf = self;
- self.block = ^{
-     __strong typeof(self) strongSelf = weakSelf;
-       [strongSelf doSomething];
- };
<<OBJECTIVE-C>>
Objective-C のブロックと同様に、Swift のクロージャも、self を含む全てのキャプチャされたオブジェクトへの強い参照を保持します。強い循環参照を防ぐために、クロージャのキャプチャリストに self を unowned と指定することができます:
- self.closure = { [unowned self] in
-         self.doSomething()
- }
<< SWIFT >>
詳細については、Swift プログラミング言語(Swift 4.0.3) の クロージャの強い循環参照の解決 を参照してください。
オブジェクトの比較
Swift で2つのオブジェクトを比較する際に比較の二つの異なる種類があります。まず、等価 (==)は、オブジェクトの内容を比較します。第二は、アイデンティティ(識別) (===) で、定数または変数が同じオブジェクトインスタンスを参照しているかどうかを判断します。
Swift は、== および === 演算子のデフォルトの実装を提供し、NSObject クラスから派生したオブジェクトに対して Equatable プロトコルを採用しています。 == 演算子のデフォルト実装は isEqual: メソッドを呼び出し、=== 演算子のデフォルト実装はポインタの等価性を検査します。Objective-C から import された型の等価演算子や識別子演算子をオーバーライドしないでください。
NSObject クラスによって提供される isEqual: の基本実装は、ポインタの等価性による識別性チェックに相当します。サブクラスで isEqual: をオーバーライドして、Swift と Objective-C API が識別性よりむしろオブジェクトの内容に基づいて同等性を判断できるようにします。比較ロジックの実装の詳細については、Cocoa Core の能力 の オブジェクトの比較 を参照してください。
Hash する
Swift は、Key 型 AnyHashable を持つ Dictionary としてキー型のクラス修飾を指定しない NSDictionary の Objective-C 宣言を import します。同様に、クラスで修飾されたオブジェクト型のない NSSet 宣言は Swift によって Element 型 AnyHashable を持つ Set として import されます。NSDictionary または NSSet 宣言はそれぞれそのキーまたはオブジェクト型をパラメータ化する場合は、代わりにその型が使用されます。たとえば、以下の Objective-C 宣言があるとします:
- @property NSDictionary *unqualifiedDictionary;
- @property NSDictionary<NSString *, NSDate *> *qualifiedDictionary;
- @property NSSet *unqualifiedSet;
- @property NSSet<NSString *> *qualifiedSet;
<<OBJECTIVE-C>>
Swift はそれらを以下のように import します。
- var unqualifiedDictionary: [AnyHashable: Any]
- var qualifiedDictionary: [String: Date]
- var unqualifiedSet: Set<AnyHashable>
- var qualifiedSet: Set<String>
<< SWIFT >>
AnyHashable 型は、型が Hashable プロトコルに準拠する必要があるため、他の方法では Any として import できない、指定されていないまたは id 型の Objective-C 宣言を import するときに Swift によって使用されます。AnyHashable 型は、全ての Hashable 型から暗黙的に変換され、AnyHashable からより特定の型にキャストするには as? 演算子と as! 演算子を使用できます。
詳細については、AnyHashable を参照してください。
Swift の型の互換性
Objective-C クラスを祖先とした、Swift クラスを作成する時は、そのクラスとそのメンバ、すなわちプロパティ、メソッド、サブスクリプト、およびイニシャライザは、Objective-C と互換であり、自動的に Objective-C から利用可能になります。これには以下のような Swift 専用の機能は含まれません。
- ジェネリックス(汎用)
- タプル
- Int の生の値型 なしで Swift で定義された列挙型
- Swift で定義された構造体
- Swift で定義されたトップレベルの関数
- Swift で定義されたグローバル変数
- Swift で定義された型エイリアス
- Swift スタイルの可変個引数
- ネストされた型
- カレー化された関数
Objective-C API が Swift に変換されるのと同様に Swift API は、Objective-C に変換されますが、その逆に:
- Swift の optional 型は、__nullable として注釈が付けられます。
- Swift の optional でない型は、__nonnull として注釈が付けられます。
- Swift の定数に格納されたプロパティと計算されたプロパティは、読み取り専用の Objective-C プロパティになります。
- Swift の変数に格納されたプロパティは、読み書き可能な Objective-C プロパティになります。
- Swift の型プロパティは、class プロパティ属性の Objective-C プロパティになります。
- Swift の型メソッドは Objective-C のクラスメソッドになります。
- Swift のイニシャライザとインスタンスメソッドは、Objective-C のインスタンスメソッドになります。
- エラーを throw する Swift メソッドは、NSError ** パラメータを持つ Objective-C メソッドになります。Swift のメソッドにパラメータがない場合、Objective-C のメソッド名に AndReturnError: が追加されます。そうでない場合は error: が追加されます。Swift メソッドが戻り値型を指定していない場合、対応する Objective-C メソッドは BOOL の戻り値の型を持ちます。Swift のメソッドが optional でない型を返す場合、対応する Objective-C メソッドは optional の戻り値型を持ちます。
たとえば、以下の Swift 宣言を考えてみましょう:
- class Jukebox: NSObject {
-         var library: Set<String>
-         var nowPlaying: String?
-         var isCurrentlyPlaying: Bool {
-                 return nowPlaying != nil
-         }
-         class var favoritesPlaylist: [String] {
-         // return an array of song names
-         }
-         init(songs: String...) {
-                 self.library = Set<String>(songs)
-         }
-         func playSong(named name: String) throws {
-         // play song or throw an error if unavailable
-         }
- }
<< SWIFT >>
これは、Objective-C によって以下のように import されます。
- @interface Jukebox : NSObject
- @property (nonatomic, strong, nonnull) NSSet<NSString *> *library;
- @property (nonatomic, copy, nullable) NSString *nowPlaying;
- @property (nonatomic, readonly, getter=isCurrentlyPlaying) BOOL currentlyPlaying;
- @property (nonatomic, class, readonly, nonnull) NSArray<NSString *> *
favoritesPlaylist; - - (nonnull instancetype)initWithSongs:(NSArray<NSString *> * __nonnull)songs
OBJC_DESIGNATED_INITIALIZER; - - (BOOL)playSong:(NSString * __nonnull)name error:(NSError * __nullable *
__null_unspecified)error; - @end
<<OBJECTIVE-C>>
Objective-C で Swift インターフェースの構成
場合によっては、Swift API が Objective-C にどのように公開されるかを細かく制御する必要があります。@objc(name) 属性を使用すると、Objective-C コードに公開されているように、インターフェース内でクラス、プロパティ、メソッド、列挙型、または列挙型の case の宣言の名前を変更できます。
たとえば、Swift クラスの名前に Objective-C でサポートされていない文字が含まれている場合は、Objective-C 内で使用する代替名を指定できます。Swift 関数に Objective-C の名前を提供する場合は、Objective-C のセレクタ構文を使用します。パラメータがセレクタの部分に続く場合は、コロン(:) を追加するのを忘れないでください。
- @objc(Color)
- enum Цвет: Int {
-         @objc(Red)
-         case Красный
-         @objc(Black)
-         case Черный
- }
- @objc(Squirrel)
- class Белка: NSObject {
-         @objc(color)
-         var цвет: Цвет = .Красный
-         @objc(initWithName:)
-         init (имя: String) {
-         // ...
-         }
-         @objc(hideNuts:inTree:)
-         func прячьОрехи(количество: Int, вДереве дерево: Дерево) {
-         // ...
-         }
- }
<< SWIFT >>
Swift クラスで @objc(name) 属性を使用すると、クラスは、名前の空白なしで Objective-C で使用可能になります。結果として、Swift にアーカイブ可能な Objective-C クラスを移行するときにこの属性も役に立ちます。アーカイブされたオブジェクトはアーカイブ内にそれらのクラスの名前を保存しているので、古いアーカイブは、新しい Swift クラスでアーカイブされないようになり、あなたの Objective-C クラスと同じ名前を指定するには、@objc(name) 属性を使用する必要があります。
動的な送出を要求
Objective-C から呼び出し可能な Swift API は、動的送出によって使用可能になっていなければなりません。しかし、動的送出が利用可能であっても、Swift コンパイラは Swift コードからこれらの API を呼び出すときに、より効率的な送出手法を選択できません。
dynamic 修飾子とともに @objc 属性を使用すると、Objective-C 実行環境を介してメンバーへのアクセスを動的に送出する必要があります。この種の動的送出を必要とすることはほとんどありません。 ただし、Objective-C 実行環境でキー値監視や method_exchangeImplementations 関数などの API を使用する場合は、実行時にメソッドの実装を動的に置き換える必要があります。
@objc 属性が宣言の文脈によって暗黙的に追加されない限り、dynamic 修飾子でマークされた宣言は、@objc 属性でも明示的にマークしなければなりません。@objc 属性が暗黙的に追加される時については、プログラミング言語Swift (Swift 4.0.3) の 宣言の属性 を参照してください。
セレクタ
Objective-C では、セレクタは Objective-C のメソッドの名前を参照する型です。Swift では、Objective-C のセレクタは、Selector 構造体で表され、#selector 式を使って構築できます。Objective-C から呼び出すことができるメソッドのセレクタを作成するには、#selector(MyViewController.tappedButton(sender:)) のようなメソッドの名前を渡します。プロパティの Objective-C の getter または setter メソッドのセレクタを構築するには、#selector(getter:MyViewController.myButton) のように getter: または setter: ラベルを前に付けたプロパティ名を渡します。
- import UIKit
- class MyViewController: UIViewController {
-        let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
-        override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
{ -                super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
-                 let action = #selector(MyViewController.tappedButton)
-                myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
-        }
-        @objc func tappedButton(sender: UIButton?) {
-                print("tapped button")
-        }
-         required init?(coder: NSCoder) {
-                 super.init(coder: coder)
-         }
- }
<< SWIFT >>
Objective-C メソッドの安全でない呼び出し
perform(_:) メソッドまたはそのバリアントの一つでセレクタを使用して、Objective-C 互換のオブジェクト上で Objective-C メソッドを呼び出すことができます。コンパイラが結果を保証できないか、オブジェクトがセレクタに応答するかどうかさえ保証できないため、セレクタを使用するメソッドの呼び出しは本質的に安全ではありません。したがって、これらの API の使用は、Objective-C 実行環境によって提供される動的メソッド解決にコードが特別に依存している場合を除いて、全くお勧めしません。たとえば、NSResponder のように、そのインターフェイスでターゲットアクションのデザインパターンを使用するクラスを実装する必要がある場合、これらの API を使用するのは適切です。ほとんどの場合、オブジェクトを AnyObject にキャストし、id の互換性 で説明されているように、optional の連鎖をメソッド呼び出しで使用する方が安全で便利です。
perform(_:) のようなセレクタを同期的に実行するメソッドは、セレクタを実行することによって返される値の型と所有権は、コンパイル時に決定されないため、AnyObject インスタンス (Unmanaged<AnyObject>!) への暗黙に開封された optional の管理されないポインタを返します。これに対して、perform(_:on:with:waitUntilDone:modes:) や perform(_:with:afterDelay:) のような特定のスレッドまたは遅延の後にセレクタを実行するメソッドは、値を返しません 。詳細については、管理されないオブジェクト を参照してください。
- let string: NSString = "Hello, Cocoa!"
- let selector = #selector(NSString.lowercased(with:))
- let locale = Locale.current
- if let result = string.perform(selector, with: locale) {
-         print(result.takeUnretainedValue())
- }
- // Prints "hello, cocoa!"
<< SWIFT >>
認識できないセレクタを持つオブジェクト上のメソッドを呼び出そうとすると、受信者は doesNotRecognizeSelector(_:) を呼び出す事になり、これは、デフォルトで NSInvalidArgumentException の例外を発生させます。
- let array: NSArray = ["delta", "alpha", "zulu"]
- // Not a compile-time error because NSDictionary has this selector.
- let selector = #selector(NSDictionary.allKeysForObject)
- // Raises an exception because NSArray does not respond to this selector.
- array.perform(selector)
<< SWIFT >>
キーとキーパス
Objective-C では、キー はオブジェクトの特定のプロパティを識別する文字列です。キーパス(key path) は、オブジェクトプロパティのシーケンスを横切るのを指定する、ドットで区切られたキーの文字列です。キーとキーパスは、文字列識別子を使用してオブジェクトの属性や関係に間接的にアクセスするためのメカニズムである キー値コーディング(KVC) で頻繁に使用されます。キーとキーパスはまた、別のオブジェクトのプロパティが変更したときにオブジェクトに直接通知することを可能にするメカニズムである、キー値監視(KVO) にも使用されます。
Swift では、キーパス式を使用してプロパティにアクセスするためのキーパスを作成できます。たとえば、\Animal.name キーパス式を使用して、以下に示すように Animal クラスの name プロパティにアクセスできます。キーパス式を使用して作成されたキーパスには、それらが参照するプロパティに関する型情報が含まれています。キーパスをインスタンスに適用すると、そのインスタンスのプロパティに直接アクセスするのと同じ型の値になります。キーパス式は、\Animal.name.count などのプロパティ参照と連鎖プロパティ参照を受け入れます。
- class Animal: NSObject {
-         @objc var name: String
-         init(name: String) {
-                 self.name = name
-         }
- }
- let llama = Animal(name: "Llama")
- let nameAccessor = \Animal.name
- let nameCountAccessor = \Animal.name.count
- llama[keyPath: nameAccessor]
- // "Llama"
- llama[keyPath: nameCountAccessor]
- // "5"
<< SWIFT >>
Swift ではまた、#keyPath 文字列式を使用して、value(forKey:) や value(forKeyPath:) などの KVC メソッドや addObserver(_:forKeyPath:options:context:) などの KVO メソッドで使用できるコンパイラチェックのキーとキーパスを作成することもできます。#keyPath 文字列式は、連鎖したメソッドまたはプロパティの参照を受け入れます。また、#keyPath(Person.bestFriend.name) など、連鎖内のオプションの値を介して連鎖することもサポートしています。キーパス式を使用して作成されたキーパスとは異なり、#keyPath 文字列式を使用して作成されたキーパスは、キーパスを受け入れる API を参照するプロパティまたはメソッドに関する型情報を渡しません。
- class Person: NSObject {
-         @objc var name: String
-         @objc var friends: [Person] = []
-         @objc var bestFriend: Person? = nil
-         init(name: String) {
-                 self.name = name
-         }
- }
- let gabrielle = Person(name: "Gabrielle")
- let jim = Person(name: "Jim")
- let yuanyuan = Person(name: "Yuanyuan")
- gabrielle.friends = [jim, yuanyuan]
- gabrielle.bestFriend = yuanyuan
- #keyPath(Person.name)
- // "name"
- gabrielle.value(forKey: #keyPath(Person.name))
- // "Gabrielle"
- #keyPath(Person.bestFriend.name)
- // "bestFriend.name"
- gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
- // "Yuanyuan"
- #keyPath(Person.friends.name)
- // "friends.name"
- gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
- // ["Yuanyuan", "Jim"]
<< SWIFT >>
前:基本設定 次:Swift のクラスとプロトコルの書き方及び Objective-C の動作