Skip to content

TIPS Swiftから利用しやすいようにObjective Cを書く

stv-ekushida edited this page Apr 18, 2018 · 1 revision

Swiftから利用しやすいようにObjective-Cを書く

1.Lightweight Generics

コレクション要素の型指定ができる。
クラス名<型 *> *
Obj-C側で要素の型が未指定(id型)の場合、SwiftではAny型に変換される。
→要素アクセスのたびにダウンキャストが必要になり冗長

型指定するとSwiftにも指定が引き継がれ、対応する型の要素を持ったコレクションとなる。
→キャスト不要

型指定なし

Objective-C
@property NSArray *array;
Swift
// XcodeのGenerated Interfaceを利用して確認した値(以下同様)
// (↑jump bar 左端のボタンから)
open var array: [Any]!

型指定あり

Objective-C
@property NSArray<NSString *> *stringArray;
@property NSArray<NSNumber *> *numberArray;
Swift
open var stringArray: [String]!
open var numberArray: [NSNumber]!

2.__kindofキーワード

特定の型のサブクラスであることを表現するためのキーワード。
Lightweight Genericsでの型指定は指定した型しか許容しない。
あるクラスのサブクラスのコレクションを定義する際に__kindofを使用する。

@interface Fruits : NSObject
@end

@interface Apple : Fruits
@end

@interface Orange : Fruits
@end

@interface Dessert : NSObject
// Fruits型のサブクラス(Apple型やOrange型)の値を代入するとコンパイラに警告される
@property NSArray<Fruits *> *fruits;
@end
@interface Fruits : NSObject
@end

@interface Apple : Fruits
@end

@interface Orange : Fruits
@end

@interface Dessert : NSObject
// Fruits型のサブクラス(Apple型やOrange型)代入を許容
@property NSArray<__kindof Fruits *> *fruits;
@end

3.null許容性アノテーション

メソッドの引数やプロパティがObjective-CのnilやCポインタのNULLを取るかどうかを指定する。

名称 説明 Swiftインポート時の型
nonnull プロパティがnilやNULLにならない 非オプショナル型
nullable プロパティがnilやNULLになる可能性がある Optional<Wrapped>
null_unspecified プロパティがnilやNULLかどうかわからない
デフォルト値
ImplicitlyUnwrappedOptional<Wrapped>
null_resettable プロパティは常に値を持っているが、nilの代入によってリセット可能 ImplicitlyUnwrappedOptional<Wrapped>
Objective-C
@property (nonnull) id nonnullValue;
@property (nullable) id nullableValue;
@property (null_unspecified) id nullUnspecifiedValue;
@property (null_resettable) id nullResettableValue;

Swift

open var nonnullValue: Any
open var nullableValue: Any?
open var nullUnspecifiedValue: Any!
open var nullResettableValue: Any!

特定の範囲への適用

同一ファイル内でひとつでもnull許容性の指定をすると、ファイル内の全てのメソッドの引数・戻り値、プロパティにnull強制を指定する必要がある。
(指定が漏れるとコンパイラに警告される) 全てに設定していく手間を省くにはNS_ASSUME_NONNULL_BEGINマクロとNS_ASSUME_NONNULL_ENDマクロを利用する。
これらに囲まれた領域の値は、自動的にnonnullとなる。
nonull以外の属性は明示的に指定する。

Objective-C
NS_ASSUME_NONNULL_BEGIN
@property id nonnullValue;
@property id hoge;
@property id fuga;
@property (nullable) id nullableValue;
@property (null_unspecified) id nullUnspecifiedValue;
@property (null_resettable) id nullResettableValue;
NS_ASSUME_NONNULL_END

Swift

open var nonnullValue: Any
open var hoge: Any
open var fuga: Any
open var nullableValue: Any?
open var nullUnspecifiedValue: Any!
open var nullResettableValue: Any!

4.id型をできるだけ利用しないようにする

instancetypeの利用

自身と同じ型のインスタンスを返却する場合はid型ではなくinstancetypeを利用する。 戻り値をid型とした場合、戻り値はAny型の値となり、型の情報が失われる。
戻り値をinstancetypeとした場合、戻り値はそのメソッドが属する型の値となり、Swiftから利用する際のダウンキャストが不要になる。

Lightweight Genericsの利用

NSArrayNSSetNSDictionaryなどのObjective-Cのコレクションは、そのままでは要素がAny型としてSwiftにインポートされる。
1.参照)
Lightweight Genericsを使うことにより要素の型もSwiftにインポートでき、Swiftから利用する際のダウンキャストが不要になる。
様々な型が代入される可能性がある型でも、特定の型のサブクラスに限定できる場合は__kindofを利用する。

プロトコルに準拠したid型の利用

Objective-Cで特定のプロトコルに準拠した任意の型を表現する場合はid< プロトコル名 > と記述する。
これにより予期せぬ型の値の代入を予防できる。

5.Objective-Cはあくまで動的な言語であることに注意する

Lightweight Genericsやnull許容性アノテーションは、Objective-Cの実行コードに一切影響を与えない。
コンパイラは型情報を持っているが、実行コード生成時にそれらの状況は消去される。
つまり、コードに不整合がある場合にコンパイラが警告を出すが、ビルドは通り実行可能。

// Fruits.h
#import <Foundation/Foundation.h>

@interface Fruits : NSObject
- (nonnull instancetype)initWithName:(nonnull NSString *)name;
@end

// Fruits.m
#import "Fruits.h"

@implementation Fruits

// nonullを指定しているが、nilを返す
// 警告は出るが、実行可能
- (nonnull instancetype)initWithName:(nonnull NSString *)name {
  return nil;
}
@end

Bridging-Header.h
#import "Fruits.h"

main.swift
let apple = Fruits(name: "Apple")
print(apple) // 実行時エラー
Clone this wiki locally