相互運用性(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つ以上の引数を取る場合は initinitWith: で始まります。Objective-C のイニシャライザが Swift によって import されると、init 接頭辞は init キーワードになり、メソッドが Swift のイニシャライザであることを示します。イニシャライザが引数を取る場合、With は削除され、残りのセレクタはそれに応じて名前付きパラメータに分割されます。


以下の Objective-C のイニシャライザ宣言を考えてみましょう:


    <<OBJECTIVE-C>>

  1. - (instancetype)init;
  2. - (instancetype)initWithFrame:(CGRect)frame
  3.                 style:(UITableViewStyle)style;


これに相当する Swift のイニシャライザの宣言は以下のとおりです。


    << SWIFT >>

  1. init() { /* ... */ }
  2. init(frame: CGRect, style: UITableViewStyle) { /* ... */ }


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 に推論させることができます。



これらの UITableViewUITextField オブジェクトは、Objective-C でインスタンス化したのと同じオブジェクトです。Objective-C と同じ方法でそれらを使用でき、プロパティにアクセスし、それぞれの型で定義された全てのメソッドを呼び出すことができます。


クラスファクトリメソッドとコンビニエンスイニシャライザ


一貫性と簡潔のために、Objective-C のクラスファクトリメソッドは、Swift 内にコンビニエンス・イニシャライザとして import されます。これで、それらはイニシャライザと同じ構文で使用することができます。


たとえば、Objective-C では、以下のようにこのファクトリメソッドを呼び出します:


Swift では、こうします:


失敗可能な初期化


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 の結合を使用して、失敗可能なイニシャライザの結果を開封できます。


    <<SWIFT>>

  1. if let image = UIImage(contentsOfFile: "MyImage.png") {
  2.         // loaded the image successfully
  3. } else {
  4.         // could not load the image
  5. }


プロパティへのアクセス


@property 構文を使用した Objective-C プロパティ宣言は、Swift プロパティとして以下のように import されます。


ドット構文を使用して、Swift 内の Objective-C のオブジェクトのプロパティにアクセスするには、括弧を使わずプロパティの名前を使って下さい。


たとえば、以下のコードを使用して、UITextField オブジェクトの textColor および text プロパティを設定できます。


    <<SWIFT>>

  1. myTextField.textColor = UIColor.darkGray
  2. myTextField.text = "Hello world"


値を返し、引数を取らない Objective-C メソッドは、ドット構文を使用して Objective-C のプロパティのように呼び出すことができます。しかし、Objective-C の @property 宣言だけがプロパティとして Swift に import されるため、これらはインスタンスメソッドとして Swift に import されます。メソッドは、メソッドでの作業 で説明したように import され、呼び出されます。


メソッドでの作業


Swift から Objective-C メソッドを呼び出す場合は、ドット構文を使用できます。


Objective-C メソッドが Swift に import されたとき、Objective-C のセレクタの最初の部分は、基本メソッド名となり、カッコの前に表示されます。最初の引数は名前なしで、括弧のすぐ内側に表示されます。セレクタ片の残りの部分は、引数名に対応し、括弧の中に表示されます。すべてのセレクタ片は呼び出し側で必要とされます。


たとえば、Objective-C では、こうなるでしょう:


Swift では、こうします。


引数なしでメソッドを呼び出している場合でも、括弧を含める必要があります。


id の互換性


Objective-C の id 型は Swift によって Any 型として import されます。コンパイル時および実行時に、Swift の値またはオブジェクトは Objective-C に id パラメータとして渡されると、コンパイラはユニバーサルブリッジ変換操作を導入します。id 値が Any として Swift に import されると、実行環境は自動的にクラス参照または Swift の値型のいずれかへのブリッジ作業を処理します。


    << SWIFT >>

  1. var x: Any = "hello" as String
  2. x as? String      // String with value "hello"
  3. x as? NSString // NSString with value "hello"
  4. x = "goodbye" as NSString
  5. x as? String      // String with value "goodbye"
  6. x as? NSString // NSString with value "goodbye"


Any のダウンキャスト


基になる型がわかっている、または合理的に決定できる Any 型のオブジェクトを扱う場合、これらのオブジェクトをより具体的な型にダウンキャストすると便利なことがよくあります。ただし、Any 型は全ての型を参照できるため、より具体的な型へのダウンキャストは成功するとは限りません。


条件付き型キャスト演算子(as?) を使用でき、これはダウンキャストしようとしている型の optional 値を返します。


    << SWIFT >>

  1. let userDefaults = UserDefaults.standard
  2. let lastRefreshDate = userDefaults.object(forKey: "LastRefreshDate") //
    lastRefreshDate is of type Any?
  3. if let date = lastRefreshDate as? Date {
  4.         print("\(date.timeIntervalSinceReferenceDate)")
  5. }


オブジェクトの型が確かな場合は、代わりに強制ダウンキャスト演算子(as!) を使用できます。


    << SWIFT >>

  1. let myDate = lastRefreshDate as! Date
  2. let timeInterval = myDate.timeIntervalSinceReferenceDate


ただし、強制ダウンキャストが失敗した場合は、実行時エラーが発生します。


    << SWIFT >>

    let myDate = lastRefreshDate as! String // Error



動的なメソッドの参照


Swift にはまた、ある種のオブジェクトを表す AnyObject 型も含まれており、全ての @objc メソッドを動的に参照できる特別な機能があります。これにより、id 値を返す Objective-C API への型のないアクセスの柔軟性を維持して書くことができます。


たとえば、全てのクラス型のオブジェクトを AnyObject 型の定数または変数に割り当て、変数を別の型のオブジェクトに再割り当てできます。また、全ての Objective-C メソッドを呼び出して、より具体的なクラス型にキャストすることなく AnyObject 値の任意のプロパティにアクセスすることもできます。


    << SWIFT >>

  1. var myObject: AnyObject = UITableViewCell()
  2. myObject = NSDate()
  3. let futureDate = myObject.addingTimeInterval(10)
  4. let timeSinceNow = myObject.timeIntervalSinceNow


認識されないセレクタと optional の連鎖


AnyObject 値の特定の型は実行時までわからないので、不注意に安全でないコードを書き込む可能性があります。Swift では Objective-C と同様に、存在しないメソッドを呼び出そうとすると、認識されないセレクタエラーが発生します。


たとえば、以下のコードはコンパイラの警告なしでコンパイルされますが、実行時にエラーが発生します。


    << SWIFT >>

  1. myObject.character(at: 5)
  2. // crash, myObject doesn't respond to that method


Swift はこのような危険な行動を防ぐために optionals を使用します。AnyObject 型の値でメソッドを呼び出すと、そのメソッド呼び出しは暗黙的に開封された optional のように動作します。プロトコル内の optional のメソッドに使用するのと同じ optional の連鎖の構文を使用して、任意に AnyObject のメソッドを呼び出せます。


たとえば、以下のコードリストでは、count プロパティと character(at:) メソッドが NSDate オブジェクトに存在しないため、1 行目と 2 行目は実行されません。myCount 定数は、optional の Int であると推定され、nil に設定されます。また、if-let 文を使用して、3 行目に示すように、オブジェクトが応答しないメソッドの結果を条件付きで開封することもできます。


    << SWIFT >>

  1. // myObject has AnyObject type and NSDate value
  2. let myCount = myObject.count
  3. // myCount has Int? type and nil value
  4. let myChar = myObject.character?(at: 5)
  5. // myChar has unichar? type and nil value
  6. if let fifthCharacter = myObject.character?(at: 5) {
  7.         print("Found \(fifthCharacter) at index 5")
  8. }
  9. // conditional branch not executed


注意: Swift は AnyObject 型の値に対してメソッドを呼び出すときに強制開封を必要としませんが、予期しない動作を防ぐ手段として推奨されます。


ヌル可能性 (Nullabity) と optionals


Objective-C では、NULL (Objective-C では nil として参照されます) の可能性がある生のポインタを使用してオブジェクトへの参照を扱います。Swift では、すべての値は、構造体やオブジェクト参照を含め、null ではないことが保証されています。代わりに、optional の型 で値の型を包み込むことで、不足している値を表します。値が不足していることを示す必要があるときには、nil の値を使用して下さい。optionals の詳細については、Swift プログラミング言語(Swift 4.0.3)Optionals を参照してください。


Objective-C は、ヌル可能性 (nullabity) の注釈を使用して、パラメータ型、プロパティ型、または戻り値の型が NULLnil 値を持てるかどうかを指定できます。_Nullable_Nonnull 注釈を使用して個々の型宣言を監査したり、nullable、nonnull および null_resettable プロパティ属性を使用して個々のプロパティ宣言を監査したり、NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END マクロを使用して領域全体を nullabity について監査できます。ある型に対して nullabity の情報が提供されていない場合、Swift は optional の参照と optional でない参照を区別できず、暗黙的に開封された optional として import します。


たとえば、以下の Objective-C 宣言を考えてみます:


    <<OBJECTIVE-C>>

  1. @property (nullable) id nullableProperty;
  2. @property (nonnull) id nonNullProperty;
  3. @property id unannotatedProperty;
  4. NS_ASSUME_NONNULL_BEGIN
  5. - (id)returnsNonNullValue;
  6. - (void)takesNonNullParameter:(id)value;
  7. NS_ASSUME_NONNULL_END
  8. - (nullable id)returnsNullableValue;
  9. - (void)takesNullableParameter:(nullable id)value;
  10. - (id)returnsUnannotatedValue;
  11. - (void)takesUnannotatedParameter:(id)value;


これが Swift に impoort されるとこうなります。


    << SWIFT >>

  1. var nullableProperty: Any?
  2. var nonNullProperty: Any
  3. var unannotatedProperty: Any!
  4. func returnsNonNullValue() -> Any
  5. func takesNonNullParameter(value: Any)
  6. func returnsNullableValue() -> Any?
  7. func takesNullableParameter(value: Any?)
  8. func returnsUnannotatedValue() -> Any!
  9. func takesUnannotatedParameter(value: Any!)


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 にブリッジする方法を示しています。


    <<OBJECTIVE-C>>

  1. @implementation OptionalBridging
  2. + (void)logSomeValue:(nonnull id)valueFromSwift {
  3.         if ([valueFromSwift isKindOfClass: [NSNull class]]) {
  4.                 os_log(OS_LOG_DEFAULT, "Received an NSNull value.");
  5.         } else {
  6.                 os_log(OS_LOG_DEFAULT, "%s", [valueFromSwift UTF8String]);
  7.         }
  8. }
  9. @end


valueFromSwift パラメータの型は id なので、これは Swift の Any 型として以下の Swift コードに import されます。しかし、Any が予想されるときに optional を渡すのは普通ではないので、logSomeValue(_:) クラスのメソッドに渡された optional は Any 型に明示的にキャストされ、コンパイラの警告は消えます。


    << SWIFT >>

  1. let someValue: String? = "Bridge me, please."
  2. let nilValue: String? = nil
  3. OptionalBridging.logSomeValue(someValue as Any) // Bridge me, please.
  4. OptionalBridging.logSomeValue(nilValue as Any)      // Received an NSNull value.


プロトコルで修飾されたクラス


1 つ以上のプロトコルで修飾された Objective-C クラスは、プロトコル構成型として Swift によって import されます。たとえば、View Controller を参照する以下の Objective-C プロパティがあるとします。


Swift は、これをこう import します。


Objective-C のプロトコルで修飾されたメタクラスは、Swift によってプロトコルメタタイプとして import されます。たとえば、指定されたクラスに対して操作を実行する以下の Objective-C メソッドがあるとします。


Swift は、これをこう import します。


軽量級の汎用


軽量級の汎用パラメータ化を使用する Objective-C の型宣言は Swift によって import され、その内容の型に関する情報が保存されます。たとえば、以下の Objective-C プロパティ宣言があるとします。


    <<OBJECTIVE-C>>

  1. @property NSArray<NSDate *> *dates;
  2. @property NSCache<NSObject *, id<NSDiscardableContent>> *cachedData;
  3. @property NSDictionary <NSString *, NSArray<NSLocale *>> *supportedLocales;


これは Swift に以下のように import されます。


    << SWIFT >>

  1. var dates: [Date]
  2. var cachedData: NSCache<AnyObject, NSDiscardableContent>
  3. var supportedLocales: [String: [Locale]]


Objective-C で記述された、パラメータ化されたクラスは、同じ数の型パラメータを持つ汎用クラスとして Swift に import されます。Swift によって import されたすべての Objective-C の汎用型パラメータには、その型がクラス(T:Any) であることが必須な型制約があります。Objective-C の汎用パラメータ化でクラス修飾が指定されている場合、import された Swift クラスには、その型が指定されたクラスのサブクラスであることが必須な制約があります。Objective-C の汎用パラメータ化でプロトコル修飾が指定されている場合、import された Swift クラスには、その型が指定されたプロトコルに準拠することが必須な制約があります。特殊化されていない Objective-C 型の場合、Swift は import されたクラス型制約の汎用パラメータ化を推論します。たとえば、以下の Objective-C クラスおよびカテゴリ宣言があるとします。


    <<OBJECTIVE-C>>

  1. @interface List<T: id<NSCopying>> : NSObject
  2. - (List<T> *)listByAppendingItemsInList:(List<T> *)otherList;
  3. @end
  4. @interface ListContainer : NSObject
  5. - (List<NSValue *> *)listOfValues;
  6. @end
  7. @interface ListContainer (ObjectList)
  8. - (List *)listOfObjects;
  9. @end


これは Swift に以下のように import されます。


    << SWIFT >>

  1. class List<T: NSCopying> : NSObject {
  2.         func listByAppendingItemsInList(otherList: List<T>) -> List<T>
  3. }
  4. class ListContainer : NSObject {
  5.         func listOfValues() -> List<NSValue>
  6. }
  7. extension ListContainer {
  8.         func listOfObjects() -> List<NSCopying>
  9. }


拡張機能


Swift の拡張機能は、Objective-C のカテゴリに似ています。拡張機能 は、Objective-C で定義されたものを含め、既存のクラス、構造体、および列挙型の動作を拡張します。システムフレームワークや、独自のカスタム型のいずれかから、型を拡張機能で定義することができます。単に適切なモジュールを import して、Objective-C で使用するのと同じ名前でクラス、構造体、または列挙型を参照してください。


例えば、提供側の length と開始する point に基づく、正三角形で単純なベジェパスを作成する UIBezierPath クラスを拡張できます。


    << SWIFT >>

  1. extension UIBezierPath {
  2.        convenience init(triangleSideLength: CGFloat, origin: CGPoint) {
  3.                self.init()
  4.                let squareRoot = CGFloat(sqrt(3.0))
  5.                let altitude = (squareRoot * triangleSideLength) / 2
  6.                move(to: origin)
  7.                addLine(to: CGPoint(x: origin.x + triangleSideLength, y: origin.x))
  8.                addLine(to: CGPoint(x: origin.x + triangleSideLength / 2, y:origin + altitude))
  9.                close()
  10.        }
  11. }


プロパティ(クラスや静的プロパティを含む) を追加する拡張機能を使用できます。しかし、これらのプロパティを計算しなければなりません。拡張機能は、クラス、構造体、または列挙型に格納されたプロパティを追加することはできません。


以下の例では、計算された area のプロパティを含む CGRect 構造体を拡張します。


    << SWIFT >>

  1. extension CGRect {
  2.        var area: CGFloat {
  3.                return width * height
  4.        }
  5. }
  6. let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
  7. let area = rect.area


また、それをサブクラス化することなく、クラスにプロトコルの適合性を追加するためにも拡張機能を使用できます。プロトコルが Swift で定義されている場合、Swift または Objective-C で定義されているか、構造体や列挙にそれへの適合性を追加できます。


Objective-C の型の既存のメソッドやプロパティをオーバーライドするためには拡張機能を使用できません。


クロージャ


Objective-C のブロックは、@convention(block) 属性で示される Objective-C ブロックの呼び出し規約を使用して Swift クロージャとして自動的に import されます。たとえば、Objective-C のブロック変数は以下のようになります。


    <<OBJECTIVE-C>>

  1. void (^completionBlock)(NSData *) = ^(NSData *data) {
  2.         // ...
  3. }


そして、これは Swift ではこうなります。


    << SWIFT >>

  1. let completionBlock: (Data) -> Void = {data in
  2.         // ...
  3. }


Swift のクロージャと Objective-C のブロックは、互換性がありますので、ブロックを期待する Objective-C のメソッドに Swift のクロージャを渡すことができます。Swift のクロージャと関数は、同じ型なので、Swift の関数の名前を渡すことさえもできます。


クロージャは、ブロックと同様のキャプチャの意味を持っていますが、1つの重要な方法が異なります。変数はコピーされるのではなく可変です。言い換えれば、Objective-C での __block の動作は Swift での変数のデフォルトの動作です。


self をキャプチャする時の強い循環参照を回避する


Objective-C では、ブロック内で self をキャプチャする必要がある場合は、メモリ管理の影響を考慮するのが重要です。


ブロックは、self を含む、キャプチャされたオブジェクトへの強い参照を保持します。self がプロパティをコピーするなどのブロックへの強い参照を保持している場合、これは強い循環参照を作ります。これを避けるには、代わりに、ブロックキャプチャを self への弱い参照にすることができます:


    <<OBJECTIVE-C>>

  1. __weak typeof(self) weakSelf = self;
  2. self.block = ^{
  3.     __strong typeof(self) strongSelf = weakSelf;
  4.       [strongSelf doSomething];
  5. };


Objective-C のブロックと同様に、Swift のクロージャも、self を含む全てのキャプチャされたオブジェクトへの強い参照を保持します。強い循環参照を防ぐために、クロージャのキャプチャリストに selfunowned と指定することができます:


    << SWIFT >>

  1. self.closure = { [unowned self] in
  2.         self.doSomething()
  3. }


詳細については、Swift プログラミング言語(Swift 4.0.3)クロージャの強い循環参照の解決 を参照してください。


オブジェクトの比較


Swift で2つのオブジェクトを比較する際に比較の二つの異なる種類があります。まず、等価 (==)は、オブジェクトの内容を比較します。第二は、アイデンティティ(識別) (===) で、定数または変数が同じオブジェクトインスタンスを参照しているかどうかを判断します。


Swift は、== および === 演算子のデフォルトの実装を提供し、NSObject クラスから派生したオブジェクトに対して Equatable プロトコルを採用しています。 == 演算子のデフォルト実装は isEqual: メソッドを呼び出し、=== 演算子のデフォルト実装はポインタの等価性を検査します。Objective-C から import された型の等価演算子や識別子演算子をオーバーライドしないでください。


NSObject クラスによって提供される isEqual: の基本実装は、ポインタの等価性による識別性チェックに相当します。サブクラスで isEqual: をオーバーライドして、Swift と Objective-C API が識別性よりむしろオブジェクトの内容に基づいて同等性を判断できるようにします。比較ロジックの実装の詳細については、Cocoa Core の能力オブジェクトの比較 を参照してください。


注意: Swift は、等価演算子と識別演算子(!=!==) の論理的補集合の実装を自動的に提供します。これらはオーバーライドするべきではありません。


Hash する


Swift は、KeyAnyHashable を持つ Dictionary としてキー型のクラス修飾を指定しない NSDictionary の Objective-C 宣言を import します。同様に、クラスで修飾されたオブジェクト型のない NSSet 宣言は Swift によって ElementAnyHashable を持つ Set として import されます。NSDictionary または NSSet 宣言はそれぞれそのキーまたはオブジェクト型をパラメータ化する場合は、代わりにその型が使用されます。たとえば、以下の Objective-C 宣言があるとします:


    <<OBJECTIVE-C>>

  1. @property NSDictionary *unqualifiedDictionary;
  2. @property NSDictionary<NSString *, NSDate *> *qualifiedDictionary;
  3. @property NSSet *unqualifiedSet;
  4. @property NSSet<NSString *> *qualifiedSet;


Swift はそれらを以下のように import します。


    << SWIFT >>

  1. var unqualifiedDictionary: [AnyHashable: Any]
  2. var qualifiedDictionary: [String: Date]
  3. var unqualifiedSet: Set<AnyHashable>
  4. var qualifiedSet: Set<String>


AnyHashable 型は、型が Hashable プロトコルに準拠する必要があるため、他の方法では Any として import できない、指定されていないまたは id 型の Objective-C 宣言を import するときに Swift によって使用されます。AnyHashable 型は、全ての Hashable 型から暗黙的に変換され、AnyHashable からより特定の型にキャストするには as? 演算子と as! 演算子を使用できます。


詳細については、AnyHashable を参照してください。


Swift の型の互換性


Objective-C クラスを祖先とした、Swift クラスを作成する時は、そのクラスとそのメンバ、すなわちプロパティ、メソッド、サブスクリプト、およびイニシャライザは、Objective-C と互換であり、自動的に Objective-C から利用可能になります。これには以下のような Swift 専用の機能は含まれません。


Objective-C API が Swift に変換されるのと同様に Swift API は、Objective-C に変換されますが、その逆に:


たとえば、以下の Swift 宣言を考えてみましょう:


    << SWIFT >>

  1. class Jukebox: NSObject {
  2.         var library: Set<String>
  3.         var nowPlaying: String?
  4.         var isCurrentlyPlaying: Bool {
  5.                 return nowPlaying != nil
  6.         }
  7.         class var favoritesPlaylist: [String] {
  8.         // return an array of song names
  9.         }
  10.         init(songs: String...) {
  11.                 self.library = Set<String>(songs)
  12.         }
  13.         func playSong(named name: String) throws {
  14.         // play song or throw an error if unavailable
  15.         }
  16. }


これは、Objective-C によって以下のように import されます。


    <<OBJECTIVE-C>>

  1. @interface Jukebox : NSObject
  2. @property (nonatomic, strong, nonnull) NSSet<NSString *> *library;
  3. @property (nonatomic, copy, nullable) NSString *nowPlaying;
  4. @property (nonatomic, readonly, getter=isCurrentlyPlaying) BOOL currentlyPlaying;
  5. @property (nonatomic, class, readonly, nonnull) NSArray<NSString *> *
    favoritesPlaylist;
  6. - (nonnull instancetype)initWithSongs:(NSArray<NSString *> * __nonnull)songs
    OBJC_DESIGNATED_INITIALIZER
    ;
  7. - (BOOL)playSong:(NSString * __nonnull)name error:(NSError * __nullable *
    __null_unspecified)error;
  8. @end


注意: Objective-C 内で Swift クラスをサブクラス化することはできません。


Objective-C で Swift インターフェースの構成


場合によっては、Swift API が Objective-C にどのように公開されるかを細かく制御する必要があります。@objc(name) 属性を使用すると、Objective-C コードに公開されているように、インターフェース内でクラス、プロパティ、メソッド、列挙型、または列挙型の case の宣言の名前を変更できます。


たとえば、Swift クラスの名前に Objective-C でサポートされていない文字が含まれている場合は、Objective-C 内で使用する代替名を指定できます。Swift 関数に Objective-C の名前を提供する場合は、Objective-C のセレクタ構文を使用します。パラメータがセレクタの部分に続く場合は、コロン(:) を追加するのを忘れないでください。


    << SWIFT >>

  1. @objc(Color)
  2. enum Цвет: Int {
  3.         @objc(Red)
  4.         case Красный
  5.         @objc(Black)
  6.         case Черный
  7. }
  8. @objc(Squirrel)
  9. class Белка: NSObject {
  10.         @objc(color)
  11.         var цвет: Цвет = .Красный
  12.         @objc(initWithName:)
  13.         init (имя: String) {
  14.         // ...
  15.         }
  16.         @objc(hideNuts:inTree:)
  17.         func прячьОрехи(количество: Int, вДереве дерево: Дерево) {
  18.         // ...
  19.         }
  20. }


Swift クラスで @objc(name) 属性を使用すると、クラスは、名前の空白なしで Objective-C で使用可能になります。結果として、Swift にアーカイブ可能な Objective-C クラスを移行するときにこの属性も役に立ちます。アーカイブされたオブジェクトはアーカイブ内にそれらのクラスの名前を保存しているので、古いアーカイブは、新しい Swift クラスでアーカイブされないようになり、あなたの Objective-C クラスと同じ名前を指定するには、@objc(name) 属性を使用する必要があります。


注意: 逆に、Swift はまた @nonobjc 属性も提供しており、これを使うと、Objective-C では Swift の宣言が使用できなくなります。これを使用すると、メソッドをブリッジする循環性を解決し、Objective-C によって import されたクラスメソッドをオーバーロードできるようになります。パラメータを変数に指定するなどのように、Objective-C メソッドが Objective-C で表現できない Swift メソッドによってオーバーライドされた場合、そのメソッドは @nonobjc とマークされなければなりません。


動的な送出を要求


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: ラベルを前に付けたプロパティ名を渡します。


    << SWIFT >>

  1. import UIKit
  2. class MyViewController: UIViewController {
  3.        let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
  4.        override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
    {
  5.                super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  6.                 let action = #selector(MyViewController.tappedButton)
  7.                myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
  8.        }
  9.        @objc func tappedButton(sender: UIButton?) {
  10.                print("tapped button")
  11.        }
  12.         required init?(coder: NSCoder) {
  13.                 super.init(coder: coder)
  14.         }
  15. }


注意: Objective-C のメソッド参照はカッコで囲むことができ、as 演算子を使用して #selector(((UIView.insert(subview:at:)) as (UIView) -> (UIView, Int) -> Void)) のように、オーバーロードされた関数の間の曖昧さを除くことができます。


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:) のような特定のスレッドまたは遅延の後にセレクタを実行するメソッドは、値を返しません 。詳細については、管理されないオブジェクト を参照してください。


    << SWIFT >>

  1. let string: NSString = "Hello, Cocoa!"
  2. let selector = #selector(NSString.lowercased(with:))
  3. let locale = Locale.current
  4. if let result = string.perform(selector, with: locale) {
  5.         print(result.takeUnretainedValue())
  6. }
  7. // Prints "hello, cocoa!"


認識できないセレクタを持つオブジェクト上のメソッドを呼び出そうとすると、受信者は doesNotRecognizeSelector(_:) を呼び出す事になり、これは、デフォルトで NSInvalidArgumentException の例外を発生させます。


    << SWIFT >>

  1. let array: NSArray = ["delta", "alpha", "zulu"]
  2. // Not a compile-time error because NSDictionary has this selector.
  3. let selector = #selector(NSDictionary.allKeysForObject)
  4. // Raises an exception because NSArray does not respond to this selector.
  5. array.perform(selector)


キーとキーパス


Objective-C では、キー はオブジェクトの特定のプロパティを識別する文字列です。キーパス(key path) は、オブジェクトプロパティのシーケンスを横切るのを指定する、ドットで区切られたキーの文字列です。キーとキーパスは、文字列識別子を使用してオブジェクトの属性や関係に間接的にアクセスするためのメカニズムである キー値コーディング(KVC) で頻繁に使用されます。キーとキーパスはまた、別のオブジェクトのプロパティが変更したときにオブジェクトに直接通知することを可能にするメカニズムである、キー値監視(KVO) にも使用されます。


Swift では、キーパス式を使用してプロパティにアクセスするためのキーパスを作成できます。たとえば、\Animal.name キーパス式を使用して、以下に示すように Animal クラスの name プロパティにアクセスできます。キーパス式を使用して作成されたキーパスには、それらが参照するプロパティに関する型情報が含まれています。キーパスをインスタンスに適用すると、そのインスタンスのプロパティに直接アクセスするのと同じ型の値になります。キーパス式は、\Animal.name.count などのプロパティ参照と連鎖プロパティ参照を受け入れます。


    << SWIFT >>

  1. class Animal: NSObject {
  2.         @objc var name: String
  3.         init(name: String) {
  4.                 self.name = name
  5.         }
  6. }
  7. let llama = Animal(name: "Llama")
  8. let nameAccessor = \Animal.name
  9. let nameCountAccessor = \Animal.name.count
  10. llama[keyPath: nameAccessor]
  11. // "Llama"
  12. llama[keyPath: nameCountAccessor]
  13. // "5"


Swift ではまた、#keyPath 文字列式を使用して、value(forKey:)value(forKeyPath:) などの KVC メソッドや addObserver(_:forKeyPath:options:context:) などの KVO メソッドで使用できるコンパイラチェックのキーとキーパスを作成することもできます。#keyPath 文字列式は、連鎖したメソッドまたはプロパティの参照を受け入れます。また、#keyPath(Person.bestFriend.name) など、連鎖内のオプションの値を介して連鎖することもサポートしています。キーパス式を使用して作成されたキーパスとは異なり、#keyPath 文字列式を使用して作成されたキーパスは、キーパスを受け入れる API を参照するプロパティまたはメソッドに関する型情報を渡しません。


注意: #keyPath 文字列式の構文は、セレクタ で説明したように、 #selector 式の構文に似ています。


    << SWIFT >>

  1. class Person: NSObject {
  2.         @objc var name: String
  3.         @objc var friends: [Person] = []
  4.         @objc var bestFriend: Person? = nil
  5.         init(name: String) {
  6.                 self.name = name
  7.         }
  8. }
  9. let gabrielle = Person(name: "Gabrielle")
  10. let jim = Person(name: "Jim")
  11. let yuanyuan = Person(name: "Yuanyuan")
  12. gabrielle.friends = [jim, yuanyuan]
  13. gabrielle.bestFriend = yuanyuan
  14. #keyPath(Person.name)
  15. // "name"
  16. gabrielle.value(forKey: #keyPath(Person.name))
  17. // "Gabrielle"
  18. #keyPath(Person.bestFriend.name)
  19. // "bestFriend.name"
  20. gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
  21. // "Yuanyuan"
  22. #keyPath(Person.friends.name)
  23. // "friends.name"
  24. gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
  25. // ["Yuanyuan", "Jim"]




前:基本設定 次:Swift のクラスとプロトコルの書き方及び Objective-C の動作
目次
Xcode の新機能

Swift:はじめに
Swift Programming Language
Swift Blog より

  • はじめに(Part I)
  • 基本設定
  • Swift 環境の設定
    Swift のインポートプロセスの理解
  • 相互運用性(Part II)
  • Objective-C API との相互作用
  • 初期化
  • クラスファクトリメソッドとコンビニエンスイニシャライザ
    失敗可能な初期化
    プロパティへのアクセス
    メソッドでの作業
  • id の互換性
  • Any のダウンキャスト
    動的なメソッドの参照
    認識されないセレクタと optional の連鎖
  • ヌル可能性 (Nullabity) と optionals
  • ヌル可能性のない (nonnullable) オブジェクトに Optional をブリッジする
    プロトコルで修飾されたクラス
    軽量級の汎用
    拡張機能
  • クロージャ
  • self をキャプチャする時の強い循環参照を回避する
  • オブジェクトの比較
  • Hash する
  • Swift の型の互換性
  • Objective-C で Swift インターフェースの構成
    動的な送出を要求
  • セレクタ
  • Objective-C メソッドの安全でない呼び出し
    キーとキーパス
  • Swift のクラスとプロトコルの書き方及び Objective-C の動作
  • Objective-C のクラスからの継承
  • NSCoding
    プロトコルの採用
    イニシャライザとデイニシャライザを書く
    Objective-C API で Swift クラス名を使用する
  • Interface Builder との統合
  • アウトレットとアクションを使った作業
    ライブレンダリング
  • プロパティ属性の指定
  • Strong と Weak
    読み/書き可能と読み取り専用
    コピーの意味
    コアデータ管理オブジェクトサブクラスの実装
    プロトコルの宣言
  • Cocoa フレームワークでの作業
  • Foundation
  • ブリッジ型
  • 名前の変更された型
  • 文字列

    配列
    セット
    Dictionary
  • Core Foundation
  • 再マッピングされた型
    メモリ管理オブジェクト
    管理されないオブジェクト
    統合ログオン
    Cocoa 構造体
  • Cocoa デザインパターンの採用
  • デリゲート化
    遅延した初期化
  • エラー処理
  • エラーの catch と処理
    エラーを Optional の値に変換する
    エラーを throw する
    カスタムエラーの Catch と処理
    キー値の監視
    ターゲット・アクション
    シングルトン
    Introspection(内省)
  • 連載
  • ローカライズ
    自動解放プール
    API の利用
    コマンドライン引数の処理
  • C の API との相互作用
  • 原始的な型
  • グローバル定数
  • import された定数の列挙体と構造体
  • 関数
  • 可変個引数の関数
  • 構造体
  • 型メンバとして関数を import
    列挙型
    Option のセット
    ユニオン
    ビットフィールド
    名前のない構造体と union のフィールド
  • ポインタ
  • 定数ポインタ
    可変ポインタ
    自動解放ポインタ
    関数ポインタ
    バッファポインタ
    ヌルポインタ
    ポインタの計算
    データ型サイズの計算
    1回限りの初期化
  • プリプロセッサの指令
  • 簡単なマクロ
    複雑なマクロ
    条件付きコンパイルブロック
  • うまく組み合わせる(part III)
  • 同じプロジェクトでの Swift と Objective-C
  • 「うまく組み合わせる」概観
  • 同じアプリターゲット内からコードを import
  • Objective-C から Swift への import
    Swift から Objective-C への import
  • 同じフレームワークターゲット内からコードを import
  • Objective-C から Swift への import
    Swift から Objective-C への import
    外部フレームワークの import
  • Objective-C からの Swift の使い方
  • Objective-C ヘッダでの Swift クラスまたはプロトコルの参照
    Objective-C クラスで採用可能な Swift プロトコルを宣言
    Objective-C の実装での Swift プロトコルの採用
    Objective-C から使用できる Swift のエラー型の宣言
  • Objective-C インターフェイス用に Swift 名をオーバーライド
  • クラスファクトリメソッド
    列挙型
    Objective-C 宣言を洗練する
    Swift で Objective-C インターフェイスを使用できないようにする
    Objective-C API へ利用可能性情報の追加
    プロダクトモジュールの命名
    トラブルシューティングのヒントと注意
  • 移行(Part IV)
  • Objective-C から Swift への移行
  • 移行のための Objective-C コードの準備
  • 移行プロセス
  • はじめる前に
    作業中に
    見終わったら
    トラブルシューティングのヒントと注意
  • 更新履歴
  • マニュアルの更新履歴












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)












    トップへ(Swift を Cocoa と Objective-C と使う)