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-C
はnil
になったインスタンスに何かを行っても、無視されるという特徴がある言語です。ある意味ではインスタンスは全てnil
になることを前提にしているとも言えるでしょう。
これがSwift
と相性が悪いです。Swift
側からObjective-C
のメソッドを呼ぶときに、邪魔になる要素です。
そこで、Objective-C
が進歩しました。nil
になる可能性があるかどうかをObjective-C
側で書けるようになりました。Swift
から使うときは、これによって、OptionalなタイプとOptionalではないタイプとにマッピングされるようになりました。
nil
になる可能性がない場合はnonnull
、nil
になる可能性があるときは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
ではなく_Nonnull
、nullable
ではなく_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);
コレクションの要素のタイプを書く
NSArray
やNSDictionary
などのコレクションでは、格納されている要素のタイプを書くようにしましょう。書かないとSwift
ではAny
になってしまうので、扱いづらいです。
要素のタイプは次のようなコードで宣言できます。もちろん、Objective-C
なので、宣言していても他のものも入れられます。しかし、トラブルの元なのでやめましょう。
// NSString *の配列
NSArray<NSString *> *stringArray;
// キーがNSNumber *, 値がNSString *のディクショナリ
NSDictionary<NSNumber *, NSString *> *mappingTable;