最新の Objective-C の採用
長年にわたり、Objective-C 言語は成長し、進化してきました。コアの概念と実践は同じままですが、言語の部分が大幅な変更と改善をしてきました。これらの近代化は、あなたが簡単に正しいコードを書くために、型の安全性、メモリ管理、パフォーマンス、および Objective-C の他の側面を改善しています。それはより多くの一貫性があり、読みやすく、そして弾力性のあるように既存および将来のコードでこれらの変更を適用することが重要です。
Xcode はこれらの構造変化のいくつかを作る手助けになるツールを提供します。しかし、このツールを使用する前に、それがコードを作るために提供する変更内容を理解し、そしてそれがなぜかを考えて下さい。この文書では、コードベースの採用を、最も重要かつ有用な現代化のいくつかである事を強調しています。
instancetype
それらが呼び出されるクラスのインスタンス (またはそのクラスのサブクラス) を返すメソッドの戻り値の型として instancetype キーワードを使用して下さい。これらのメソッドは、alloc,init そして、クラスファクトリメソッドを含んでいます。
適切な場所で id の代わりに instancetype を使用すると、Objective-C コードの型の安全性を向上させます。たとえば、以下のコードを考えてみます。
@interface MyObject : NSObject + (instancetype)factoryMethodA; + (id)factoryMethodB; @end @implementation MyObject + (instancetype)factoryMethodA { return [[[self class] alloc] init]; } + (id)factoryMethodB { return [[[self class] alloc] init]; } @end void doSomething() { NSUInteger x, y; x = [[MyObject factoryMethodA] count]; // Return type of +factoryMethodA is taken // to be "MyObject *" y = [[MyObject factoryMethodB] count]; // Return type of +factoryMethodB is "id" }
+ factoryMethodA の instancetype の戻り値の型は、そのメッセージ式の型が MyObject * であり、MyObject には -count メソッドがないため、コンパイラは、x 行についての警告を与えます:
main.m: ’MyObject’ may not respond to ‘count’
しかし、+factoryMethodB の id 戻り値の型のため、コンパイラは、y 行についての警告を与えられきません。 id 型のオブジェクトは、どのクラスにもすることができるため、またいずれかのクラスのどこかに存在する -count と呼ばれるメソッドのため、コンパイラには、+factoryMethodB の戻り値はメソッドを実装している可能性があります。
必ず instancetype ファクトリメソッドが、正しいサブクラス化挙動を有するようにするには、クラス名を直接参照するのではなく、クラスを割り当てる時には、[self class] を必ず使用してください。この規則に従うと、コンパイラは正しくサブクラスの型を推論することが保証されます。たとえば、前の例からの MyObject のサブクラスでこれをしようと考えてみましょう。
@interface MyObjectSubclass : MyObject @end void doSomethingElse() { NSString *aString = [MyObjectSubclass factoryMethodA]; }
コンパイラは、このコードに関して以下の警告を与えます:
main.m: Incompatible pointer types initializing ’NSString *’ with an expression of type ’MyObjectSubclass *’
この例では、+factoryMethodA のメッセージは、受信者の型である MyObjectSubclass、型のオブジェクトを返します。コンパイラが適切に、+factoryMethodA の戻り値の型が、ファクトリメソッドが宣言されたスーパークラスのものではなく、サブクラス MyObjectSubclass のものであるべきと判断します。
どのように採用するか
あなたのコードで、戻り値としての id の発生を、適切な場所で instancetype と置き換えて下さい。これは、一般的には init メソッドとクラスファクトリメソッドの場合に当てはまります。コンパイラが "alloc","init",または "new" で始まるメソッド、そして id の戻り値の型を持つメソッドを自動的に instancetype を返すように変換したとしても、他のメソッドは変換しません。Objective-C の規則では、すべてのメソッドに対して明示的に instancetype を書くことになっています。
ただ戻り値だけで、コード内の他の場所ではなく、 instancetype で id を置き換える必要があることに注意してください。id とは異なり、 instancetype キーワードは、メソッド宣言で戻り値型としてのみ使用できます。
例えば:
@interface MyObject - (id)myFactoryMethod; @end
は以下のようになります:
@interface MyObject - (instancetype)myFactoryMethod; @end
代わりに、コードで自動的にこの変更を行うために Xcode で現代的な Objective-C へのコンバータを使用することができます。詳細については、Xcode を使用してコードをリファクタリング を参照してください。
プロパティ
Objective-C の プロパティ は @property 構文で宣言した public または private メソッドです。
@property (readonly, getter=isBlue) BOOL blue;
プロパティは、オブジェクトの状態をキャプチャします。それは、オブジェクトの本質的な属性や他のオブジェクトとの関係を反映します。プロパティは、カスタムアクセサメソッドの設定を記述する必要なく、これらの属性と対話するための安全な、便利な方法を提供します。(プロパティはカスタムのゲッタとセッタを、必要に応じて許可しますが)
できるだけ多くの場所でインスタンス変数の代わりに、プロパティを使用するのは、多くの利点があります。
プロパティメソッドは、単純な命名規則に従います。ゲッタ は、プロパティの名前 (例えば、date)、セッタ はキャメルケース (例えば、setDate) で記述された set の接頭辞を持つプロパティの名前です。ブール型のプロパティの命名規則は、ゲッタは単語 "is" で始まる名前でそれらを宣言することです:
@property (readonly, getter=isBlue) BOOL blue;
その結果、以下に挙げたすべてはきちんと動きます:
if (color.blue) { } if (color.isBlue) { } if ([color isBlue]) { }
プロパティにできるものを決定する際に、以下の物はプロパティではないことに注意してください。
- init メソッド
- copyメソッド、mutableCopy メソッド
- クラスファクトリメソッド
- アクションを開始し、BOOL の結果を返すメソッド
- ゲッタの副作用として、内部状態を明示的に変更するメソッド
加えて、あなたのコード内の潜在的なプロパティを識別するときには、以下の一連のルールを考慮してください。
- 読み/書きプロパティには、2つのアクセサメソッドがあります。セッタは、1つの引数を取り、何も返さず、ゲッタは引数を取らず、1つの値を返します。プロパティにメソッドのこれらのセットを変換した場合、readwrite キーワードでタグを付けて下さい。
- 読み取り専用のプロパティには、一つのアクセサメソッドがあり、すなわちゲッタは引数を取らず、1つの値を返します。プロパティにこのメソッドを変換する場合は、readonly キーワードでタグを付けて下さい。
- ゲッタは 冪等 である必要があります。(ゲッタが二度呼び出された場合、第2の呼び出しの結果は最初のと同じ結果です) しかし、ゲッタは呼び出されるたびに結果を計算するため、それは許容されます。
どのように採用するか
プロパティに変換する資格を持つメソッドの一揃いを識別します。これらのような:
- (NSColor *)backgroundColor; - (void)setBackgroundColor:(NSColor *)color;
そして適切なキーワード(複数可)で @property 構文を使用してそれらを宣言します。
@property (copy) NSColor *backgroundColor;
プロパティのキーワードやその他の考慮事項については、 カプセル化データ を参照してください。
代わりに、自動的にあなたのコードにこの変更を行うために Xcode で現代的な Objective-C のコンバータを使用することができます。詳細については、Xcode を使用してコードをリファクタリング を参照してください。
列挙型マクロ
NS_ENUM と NS_OPTIONS マクロは、C ベースの言語で列挙型とオプションを定義する簡潔な、簡単な方法を提供します。これらのマクロは、Xcode でのコード補完を改善し、明示的に列挙型とオプションの型とサイズを指定します。さらに、この構文は古いコンパイラによって正しく評価されている方法で列挙型 (enums) を宣言し、基礎となる型情報を解釈できる新しいもので宣言します。
相互に排他的である一揃いの値、列挙型 を定義するために NS_ENUM マクロを使用して下さい。
typedef NS_ENUM(NSInteger, UITableViewCellStyle) { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle };
NS_ENUM マクロは NSInteger 型の UITableViewCellStyle という名前のこの場合は、名前と列挙型の両方を定義するのに役立ちます。列挙型の型は、NSInteger でなければなりません。
一緒に組み合わせることができるビットマスク値の一揃いの オプション を定義するために NS_OPTIONS マクロを使用して下さい。
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5 };
列挙型と同様に、NS_OPTIONS マクロは名前と型の両方を定義します。しかし、オプションの型は通常 NSUInteger でなければなりません。
どのように採用するか
以下のように、enum 宣言を、置き換えます:
enum { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle }; typedef NSInteger UITableViewCellStyle;
NS_ENUM 構文を使うと:
typedef NS_ENUM(NSInteger, UITableViewCellStyle) { UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle };
しかし、ビットマスクを定義する enum を使用すると、以下のようになります:
enum { UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5 }; typedef NSUInteger UIViewAutoresizing;
NS_OPTIONS マクロを使用すると:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { UIViewAutoresizingNone = 0, UIViewAutoresizingFlexibleLeftMargin = 1 << 0, UIViewAutoresizingFlexibleWidth = 1 << 1, UIViewAutoresizingFlexibleRightMargin = 1 << 2, UIViewAutoresizingFlexibleTopMargin = 1 << 3, UIViewAutoresizingFlexibleHeight = 1 << 4, UIViewAutoresizingFlexibleBottomMargin = 1 << 5 };
代わりに、自動的にあなたのコードにこの変更を行うための Xcode の現代的な Objective-C のコンバータを使用できます。詳細については、Xcode を使用してコードをリファクタリング を参照してください。
オブジェクトの初期化
Objective-C では、オブジェクトの初期化は 指定イニシャライザ と、そのスーパークラスのイニシャライザの1つを呼び出し、その後、独自のインスタンス変数を初期化する責任がある初期化メソッドの概念に基づいています。指定イニシャライザでないイニシャライザは、コンビニエンスイニシャライザ として知られています。コンビニエンスイニシャライザは、一般的に別のイニシャライザ - 初期化をそれ自身で実行するのではなく、最終的には指定イニシャライザで連鎖を終了してデリゲートします。
指定イニシャライザパターンは、継承されたイニシャライザが適切にすべてのインスタンス変数を初期化することを保証します。重大な初期化を実行する必要があるサブクラスは、そのスーパークラスの指定イニシャライザのすべてをオーバーライドする必要がありますが、コンビニエンスイニシャライザをオーバーライドする必要はありません。イニシャライザの詳細については、オブジェクトの初期化 を参照してください。
指定されたものと、指定イニシャライザの区別を明確にするために、それが指定イニシャライザである事を表すため、init ファミリー内の全てのメソッドに NS_DESIGNATED_INITIALIZER マクロを追加できます。このマクロを使用すると、いくつかの点が制限されます。
- 指定イニシャライザの実装は、必ずスーパークラスの init メソッドに連鎖し ([super init...] で) スーパークラスの指定イニシャライザであることを示さなければなりません。
- コンビニエンスイニシャライザの実装 (クラスの中で指定イニシャライザとしてマークされないイニシャライザで、少なくとも1つの指定イニシャライザとしてマークされたイニシャライザを持つクラス) は別のイニシャライザにデリゲートしなければなりません ([self init...] で)。
- クラスが1つ以上の指定イニシャライザを提供する場合、そのスーパークラスの全ての指定イニシャライザを実装しなければなりません。
これらの制限のいずれかに違反している場合は、コンパイラから警告を受けます。
クラス内で NS_DESIGNATED_INITIALIZER マクロを使用する場合は、このマクロを使用して全ての指定イニシャライザをマークする必要があります。他のすべてのイニシャライザは、コンビニエンスイニシャライザとみなされます。
どのように採用するか
クラス内の指定イニシャライザを特定し、NS_DESIGNATED_INITIALIZER マクロでそれらをタグ付けします。例えば:
- (instancetype)init NS_DESIGNATED_INITIALIZER;
自動参照カウント (ARC)
自動参照カウント (ARC) は、Objective-C のオブジェクトの自動メモリ管理を提供するコンパイラの機能です。retain,release そして autorelease を使う時期を覚える必要がある代わりに、ARC は、オブジェクトの有効期間の要件を評価し、自動的にコンパイル時にあなたのために適切なメモリ管理の呼び出しを挿入します。コンパイラはまた、適切な dealloc メソッドも生成します。
どのように採用するか
Xcode は ARC 変換の機械的な部分を自動化するツールを提供し、(retain や release 呼び出しを削除するなど) 移行機が自動的に処理できない問題を修正するのを補助します。ARC 移行ツールを使用するには、 Edit > Refactor > Convert to Objective-C ARC を選択します。移行ツールは、ARC を使用する、プロジェクト内のすべてのファイルを変換します。
詳細については、ARC への移行の公開ノート を参照してください。
Xcode を使用してコードをリファクタリング
Xcode は現代化プロセスで支援することができるように現代の Objective-C コンバータを提供しています。コンバータは、識別し、潜在的な近代化を適用するメカニズムに役立ちますが、それはコードの意味を解釈しません。例えば、それはあなたの -toggle メソッドが、オブジェクトの状態に影響を与える行動であることを検出しませんし、それは誤ってこのアクションを現代化してプロパティにすることを提案するかもしれません。手動で確認し、コンバータが、コードに提供するすべての変更を確認してください。
前述の現代化の中で、コンバータは以下のことを提供します:
- 適切な場所で id を instancetype に変更します
- enum を NS_ENUM または NS_OPTIONS 変更します
- @property 構文に更新します
これらの現代化に加えて、このコンバータは、以下に挙げたコードへの追加の変更をお勧めします。
- リテラルへの変換、それで [NSNumber numberWithInt:3] のような文は @3 になります。
- サブスクリプトを使用し、それで [dictionary setObject:@3 forKey:key] のような文は、dictionary[key] = @3 になります。
現代の Objective-C のコンバータを使用するには、Edit > Refactor > Convert to Modern Objective-C Syntax を選択します。
次