ObjCとSwiftでの相互利用マクロ・属性まとめ

  • 68
    Like
  • 0
    Comment
More than 1 year has passed since last update.

1からフルSwiftでコードが書ければ良いですが、多くの人は大きなObjective-Cの遺産を抱えながらSwiftを書いていると思います。
今すぐにObjCをSwiftに書き換えることはできないけれど、ちょっとSwiftでだと使いづらい!とか、
SwiftでつくちゃったけどObjCでもうまいこと使いたい!という時に用意されたマクロや属性が用意されています。

Swift用ObjCマクロ

NS_SWIFT_NAME

ファクトリメソッドの名前変更

このマクロを用いることで、Swift上でのファクトリメソッドの名前を変更できます。

+ (instancetype)objectWithName: (NSString *)name NS_SWIFT_NAME(init(name:));

とすることでファクトリメソッドをイニシャライザとして扱えます。

let obj = MyObjCObject(name: "name")

しかし、ObjCの宣言がinstancetypeでなければinitにはできません。その場合は別名のファクトリメソッドにできます。

+ (id)objectWithName: (NSString *)name NS_SWIFT_NAME(object(name:));
let = obj = MyObjCObject.object(name: "name")

enumの名前変更

このマクロを利用するとenumの名前も変更できます。

typedef NS_ENUM(NSUInteger, MyEnum) {
    MyEnumYes NS_SWIFT_NAME(True),
    MyEnumNo NS_SWIFT_NAME(False)
};
MyEnum.True
MyEnum.False

NS_SWIFT_UNAVAILABLE

定義の後にこのマクロをつけることで、Swiftでは利用できないことを宣言できます。

- (void)iHateSwift NS_SWIFT_UNAVAILABLE("Don't Use In Swift");

クラスの前につければクラスごと利用不可にできます。

NS_SWIFT_UNAVAILABLE("Don't use it")
@interface MyObjCOnlyObject: NSObject

@end

NS_REFINED_FOR_SWIFT

Swiftで利用する際にメソッドの最初にアンダースコア2つ(__)付いたメソッドができます。
これはSwiftの利用を完全には制限しませんが、Swiftのextensionなどで、別名でSwiftの利用に即した
実装を行いたい場合に用います。

- (void)unrecommendMethodInSwift NS_REFINED_FOR_SWIFT;
MyObjCObject().__unrecommendMethodInSwift()

NS_SWIFT_NOTHROW

ObjCでerror pointerを用いるメソッドの場合Swift上ではthrowsに変換されますが、これをさせないようにします。

- (BOOL)doWithError:(NSError **)error NS_SWIFT_NOTHROW;
var error: NSError?
MyObjCObject().doWithError(&error)

余談ですが、NSURL#checkPromisedItemIsReachableAndReturnErrorはベータの途中までthrowsにしていて、
WWDCで例として どや顔で説明してたんですが、no throwになりましたね。

SwiftからObjC

@objc

@objc属性でObjCで利用できることを明示的に宣言できますが、その後に()でObjC上の名前を変更できます。

@objc(callMe)
func echo() {
    print(self.name)
}
[[MySwiftObject new] callMe];

@nonobjc

@nonobjcでObjCで使える条件を満たしていても使えないようにできます。

@nonobjc
func cannotUseObjC() {
    echo()
}

Swiftで実装しつつObjCでしか使えないようにする

Swiftで実装したものの、ObjCでも利用したいが、構造体などを使っていて利用できないといった場合があります。
その時ObjC用にバックポートを用意してあげたいですが、このバックポートをSwift上で使ってほしくない時にこの技が使えます。

@available(*, unavailable)
@objc func canUseOnlyObjC() {
    echo()
}

avaiableでSwiftでunavailableを宣言してSwift上で利用不可にしつつ、@objc属性でObjCでの利用を許可します。
これでSwiftで実装してるのにObjCでしか使えないものが定義できます。

あとがき

ObjCの遺産を活用しながらSwiftを使いたいけど、ObjCはSwiftのことなんか考えてないことが多いと思います。
これらの相互利用の機能を利用して、少しでも快適なSwift/ObjCライフが送れたらいいですね!