Objective-CのコードをSwiftに対応してブラッシュアップさせる

macOSアプリは歴史的な経緯がある処理や既存のコードを活かすべきケースが多くあり、Swiftだけでは完結できないことが多くあります。そのため、Objective-CのコードやC/C++のコードを共存させています。

そのような、複数の言語が共存している中でコードを書いていると、Swiftから使いやすくするために、共存させるObjective-Cのコードをブラッシュアップさせて、Swiftから使われる準備や他のプロジェクトに組み込まれたときのための、予防措置を入れておくべきだなと強く感じます。

スポンサーリンク

ARC対応であることを宣言する

他のプロジェクトに組み込まれたときのための予防措置です。

macOS 10.6よりも前の時代からメンテナンスし続けているコードの場合、ARC (Automatic Reference Counting)に非対応のコードもあります。そんなことはないと自信を持って言えるところは、コストをかけてもすぐにキャッチアップしてきたか、中小規模のプログラムではないでしょうか?大規模なプログラムの場合、途中でARCを導入するのは困難です。

私の場合は大規模なプログラムであるためARCを導入していません。ただ、その中からほんの一部のコードを、Swiftで書かれているプログラムに移植するという機会が生まれました。移植先のプロジェクトはSwiftで書かれている部分がほとんどで、一部のObjective-CのコードもARC対応です。移植するソースファイルだけをARCの対象外にすることは設定できますが、将来を考えれば悪手でしょう。ブラッシュアップする良い機会を得たと考えて、ARC対応に修正してから組み込みました。

しかし、このコードが再びどこか別のプロジェクトに移植される可能性は高いと考えられるコードです。再び移植される先はARC非対応プロジェクトかもしれません。そのときに備えて、次のようなコードを書いておきます。

#if !__has_feature(objc_arc)
#error This source file must be compiled with ARC.
#endif

このコードにより、ARC非対応プロジェクトでビルドしようとするとビルドエラーになります。エラーになった先のプロジェクトで、このソースファイルはARCを有効化してビルドされることでしょう。もしくは、規模によってはプロジェクト全体をARC対応にすることも考えられます。

コメントなどに書いておいても恐らく見ません。気がつかれません。エラーになって初めて気がつくことでしょう。

また、逆にARC対応プロジェクトにARC非対応コードを組み込んだ場合、エラーになります。そのため、逆のケースはすぐに気がつけます。

SwiftのOptionalに備える

SwiftにはOptionalという機能があります。プロパティや引数、関数戻り値がnilになることがあるかどうかをコードで示すことができます。

Objective-CにはOptionalはありません。Objective-Cnilになったインスタンスに何かを行っても、無視されるという特徴がある言語です。ある意味ではインスタンスは全てnilになることを前提にしているとも言えるでしょう。

これがSwiftと相性が悪いです。Swift側からObjective-Cのメソッドを呼ぶときに、邪魔になる要素です。

そこで、Objective-Cが進歩しました。nilになる可能性があるかどうかをObjective-C側で書けるようになりました。Swiftから使うときは、これによって、OptionalなタイプとOptionalではないタイプとにマッピングされるようになりました。

nilになる可能性がない場合はnonnullnilになる可能性があるときはnullableを指定します。

次のように、メソッドの戻り値や引数、プロパティなどで指定します。もちろん、これを書くときは適当に書くのではなく、中身の実装もその指定通りの動作になるようにしましょう。nonnullを指定しておきながらnilが入ってきたら、Swift側で困ります。というより、たちが悪いです。

@interface MyObject : NSObject

// nilにならないプロパティ
@property (nonnull, nonatomic, strong) NSString *firstName;
@property (nonnull, nonatomic, strong) NSString *lastName;

// nilになるかもしれないプロパティ
@property (nullable, nonatomic, strong) NSString *middleName;

// メソッドの戻り値や引数にも指定する
- (nullable MyObject *)nextObject;
- (nonnull NSString *)fullNameWithPrefix:(nullable NSString *)prefix suffix:(nullable NSString *)suffix;

@end

ポインタやC++のクラスのインスタンスのとき

ポインタやC++のクラスのインスタンスなどのときは、nonullではなく_Nonnullnullableではなく_Nullableを使用します。また、記述する場所も次のようになります。

Type * _Nonnull variable
Type * _Nullable variable

例えば、ブロックの引数でポインタがあり、_Nonnull_Nullableを使用すると次のように記述します。

typedef void (^ExampleBlock)(const char * _Nonnull ptr, const int * _Nullable);

ハンドルなどポインタのポインタの場合は、2つ書く必要があります。

typedef void (^ExampleBlock)(const char * _Nonnull * _Nonnull handle, const int * _Nullable);
スポンサーリンク

コレクションの要素のタイプを書く

NSArrayNSDictionaryなどのコレクションでは、格納されている要素のタイプを書くようにしましょう。書かないとSwiftではAnyになってしまうので、扱いづらいです。

要素のタイプは次のようなコードで宣言できます。もちろん、Objective-Cなので、宣言していても他のものも入れられます。しかし、トラブルの元なのでやめましょう。

// NSString *の配列
NSArray<NSString *> *stringArray;

// キーがNSNumber *, 値がNSString *のディクショナリ
NSDictionary<NSNumber *, NSString *> *mappingTable;
スポンサーリンク
最新情報をチェックしよう!

Macアプリの作り方の最新記事8件