Objective-Cで-initメソッドをプライベートにすることは可能ですか?


147

-initObjective-Cでクラスのメソッドを非表示にする(プライベートにする)必要があります。

どうやってやるの?


3
以下のこの回答に示すように、これを実現するための特定のクリーンで説明的な機能があります。具体的には:NS_UNAVAILABLE。一般的には、このアプローチを使用することをお勧めします。OPは承認された回答を改訂することを検討しますか?ここでの他の回答は多くの有用な詳細を提供しますが、これを達成するための好ましい方法ではありません。
Benjohn

他の人が以下で述べているように、NS_UNAVAILABLE発信者はをinit介して間接的に呼び出すことができますnew。単にオーバーライドinitして返すだけでnil、両方のケースが処理されます。
グレッグブラウン

回答:


88

SmalltalkのようなObjective-Cには、「プライベート」メソッドと「パブリック」メソッドの概念はありません。任意のメッセージをいつでも任意のオブジェクトに送信できます。

あなたができることはNSInternalInconsistencyExceptionあなたの-initメソッドが呼び出された場合にスローすることです:

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}

もう1つの代替案-おそらく実際にははるかに優れている-は、-init可能であれば、クラスに対して賢明なことを行うことです。

シングルトンオブジェクトが使用されていることを「確認」しようとしているためにこれを実行しようとしている場合は、気にしないでください。具体的には、「オーバーライドと気にしないでください+allocWithZone:-init-retain-releaseシングルトンを作成する」方法。これは事実上常に不要であり、複雑さを増すだけで実際には大きな利点はありません。

代わりに、+sharedWhateverメソッドがシングルトンにアクセスする方法となるようにコードを記述し、それをヘッダー内のシングルトンインスタンスを取得する方法として文書化します。ほとんどの場合、それで十分です。


2
ここで実際に返品が必要ですか?
philsquared 2009

5
はい、コンパイラを満足させるためです。そうしないと、コンパイラーはvoid以外の戻り値を持つメソッドからの戻りがないと不平を言うことがあります。
Chris Hanson、

おかしい、私には向かない。おそらく、異なるコンパイラーのバージョンまたはスイッチですか?(私はXCode 3.1でデフォルトのgccスイッチを使用しています)
2009

3
開発者がパターンに従うことを当てにすることは良い考えではありません。例外をスローする方が良いので、別のチームの開発者はそうしない方がいいでしょう。個人的なコンセプトの方がいいです。
Nick Turner

4
「本当の意味での大きな利点はない」。まったく真実ではありません。重要な利点は、シングルトンパターンを適用することです。新しいインスタンスの作成を許可すると、APIに精通していない開発者が使用allocinit、適切なクラスを持っているが間違ったインスタンスを持っているためにコードが正しく機能しない可能性があります。これは、OO におけるカプセル化の原則の本質です。APIで、他のクラスが必要としない、またはアクセスする必要がないものを非表示にします。すべてを公開し続けるだけでなく、人間がすべてを追跡することを期待します。
2014年

345

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

これは、利用できない属性の短いバージョンです。それはmacOS 10.7iOS 5で最初に登場しました。NSObjCRuntime.hでとして定義されてい#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTEます。

ObjCコードではなく、Swiftクライアントに対してのみメソッド無効にするバージョンがあります。

- (instancetype)init NS_SWIFT_UNAVAILABLE;

unavailable

unavailableヘッダーに属性を追加して、initの呼び出し時にコンパイラエラーを生成します。

-(instancetype) init __attribute__((unavailable("init not available")));  

コンパイル時エラー

理由がない場合は、と入力__attribute__((unavailable))するか、さらには__unavailable

-(instancetype) __unavailable init;  

doesNotRecognizeSelector:

doesNotRecognizeSelector:NSInvalidArgumentException を発生させるために使用します。「オブジェクトが応答または転送できないaSelectorメッセージを受信すると、ランタイムシステムはこのメソッドを呼び出します。」

- (instancetype) init {
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

NSAssert

NSAssertNSInternalInconsistencyExceptionをスローしてメッセージを表示するために使用します。

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}

raise:format:

raise:format:独自の例外をスローするために使用します。

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}

[self release]オブジェクトはすでにalloc食べられているので必要です。ARCを使用すると、コンパイラーがそれを呼び出します。いずれにしても、意図的に実行を停止しようとしているときに心配する必要はありません。

objc_designated_initializer

init指定したイニシャライザの使用を強制的に無効にする場合は、そのための属性があります。

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

これにより、他の初期化メソッドがmyOwnInit内部で呼び出されない限り、警告が生成されます。詳細は、次のXcodeリリース後に、Adopting Modern Objective-Cで公開されると思います(多分)。


これは以外の方法にも適していますinit。このメソッドが無効な場合、なぜオブジェクトを初期化するのですか?さらに、例外をスローする場合init*、開発者に正しいメソッドを伝えるカスタムメッセージを指定できますが、の場合はそのようなオプションはありませんdoesNotRecognizeSelector
Aleks N. 2012

Aleks、それはそこにあるべきではありません。:)私は答えを編集しました。
Jano、2012

それはあなたのシステムをクラッシュさせてしまったので、これは奇妙です。何かを起こさない方がいいと思いますが、もっと良い方法があるかと思っています。私が欲しいのは、他の開発者がそれを呼び出せず、「実行中」または「ビルド中」のときにコンパイラーにキャッチされないようにすることです
okysabeni

1
私はこれを試しましたが、うまくいきません:-(id)init __attribute __((unavailable( "init not available"))){NSAssert(false、@ "Use initWithType"); nilを返します。}
okysabeni 2013

1
@Miraajコンパイラではサポートされていないようです。これはXcode 6でサポートされています。イニシャライザが指定されたイニシャライザを呼び出さない場合は、「便利なイニシャライザが別のイニシャライザへの「自己」呼び出しを欠いている」と表示されます。
Jano、2014年

101

Appleは、ヘッダーファイルで以下を使用して、initコンストラクタを無効にし始めました。

- (instancetype)init NS_UNAVAILABLE;

これは、Xcodeでコンパイラエラーとして正しく表示されます。具体的には、これはいくつかのHealthKitヘッダーファイルで設定されています(HKUnitはその1つです)。


3
ただし、[MyObject new]を使用してオブジェクトをインスタンス化することもできます。
ホセ・

11
+(instancetype)new NS_UNAVAILABLEを実行することもできます。
sonicfly

@sonicfly試してみましたが、プロジェクトはまだコンパイルされています
Cyber​​Mew

3

デフォルトの-initメソッドについて話している場合はできません。これはNSObjectから継承され、すべてのクラスが警告なしでそれに応答します。

-initMyClassなどの新しいメソッドを作成し、Mattが提案するようにプライベートカテゴリに配置することができます。次に、デフォルトの-initメソッドを定義して、呼び出された場合に例外を発生させるか、デフォルト値を指定してプライベート-initMyClassを呼び出します。

initを非表示にしたいと思われる主な理由の1つは、シングルトンオブジェクトです。その場合は、-initを非表示にする必要はなく、代わりにシングルトンオブジェクトを返すだけです(まだ存在しない場合は作成します)。


これは、シングルトンの「init」をそのままにして、「sharedWhatever」を介してアクセスすることになっていることをユーザーに伝えるためにドキュメントに依存するよりも優れたアプローチのようです。人々は通常、問題を解明しようとして何分も無駄にするまで、ドキュメントを読みません。
Greg Maletic、2011年


3

を使用して、使用できないメソッドを宣言できますNS_UNAVAILABLE

したがって、これらの行を@interfaceの下に置くことができます

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

接頭辞ヘッダーでマクロをさらに定義する

#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;

そして

@interface YourClass : NSObject
NO_INIT

// Your properties and messages

@end

2

それは、「プライベートにする」という意味によって異なります。Objective-Cでは、オブジェクトのメソッドを呼び出すことは、そのオブジェクトにメッセージを送信することとして説明する方がよいでしょう。クライアントがオブジェクトの特定のメソッドを呼び出すことを禁止する言語には何もありません。最善の方法は、ヘッダーファイルでメソッドを宣言しないことです。それにもかかわらず、クライアントが適切な署名を使用して「プライベート」メソッドを呼び出した場合でも、実行時に実行されます。

つまり、Objective-Cでプライベートメソッドを作成する最も一般的な方法は、実装ファイルでカテゴリを作成し、その中ですべての「非表示」メソッドを宣言することです。これは本当に呼び出しを妨げることはありませんinit実行が、誰かがこれを行おうとするとコンパイラは警告を吐き出します。

MyClass.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

このトピックについてMacRumors.comにまともなスレッドがあります。


3
残念ながら、この場合、カテゴリアプローチは実際には役立ちません。通常、メソッドがクラスで定義されていない可能性があるというコンパイル時の警告が表示されます。ただし、MyClassはルートクラスの1つから継承する必要があり、それらはinitを定義しているため、警告はありません。
Barry Wark、

2

「プライベート/非表示」にすることができない理由は、initメソッドがYourClassにではなく、(allocがIDを返すので)IDに送信するためです。

コンパイラー(チェッカー)の観点から、IDはこれまでに入力されたものに潜在的に応答する可能性がある(実行時に実際にIDに入る内容をチェックできない)ことに注意してください。 header)initメソッドを使用します。コンパイルでわかるように、idがinitに応答する方法はありません。これは、initがどこにもないためです(ソース、すべてのライブラリなど)。

したがって、ユーザーがinitを渡してコンパイラーによって破壊されることを禁止することはできません...しかし、あなたができることは、ユーザーがinitを呼び出して実際のインスタンスを取得できないようにすることです

nilを返し、他の誰かが取得できない(プライベート/非表示)イニシャライザを持っているinitを実装するだけで(initOnce、initWithSpecialなど)

static SomeClass * SInstance = nil;

- (id)init
{
    // possibly throw smth. here
    return nil;
}

- (id)initOnce
{
    self = [super init];
    if (self) {
        return self;
    }
    return nil;
}

+ (SomeClass *) shared 
{
    if (nil == SInstance) {
        SInstance = [[SomeClass alloc] initOnce];
    }
    return SInstance;
}

注:誰かがこれを行うことができた

SomeClass * c = [[SomeClass alloc] initOnce];

実際には新しいインスタンスを返しますが、initOnceがプロジェクトのどこにも(ヘッダーで)公に宣言されていない場合、警告が生成され(IDが応答しない場合があります...)、とにかくこれを使用しているユーザーは、実際の初期化子がinitOnceであることを正確に知る

これをさらに防ぐことができますが、必要はありません


0

サブクラスでメソッドを非表示にするためにアサーションを配置し、例外を発生させることは、意図したとおりの厄介なトラップであることを言及しなければなりません。

私が使用することをお勧めします__unavailableようJanoが彼の最初の例で説明しました

メソッドはサブクラスでオーバーライドできます。これは、スーパークラスのメソッドがサブクラスで例外を発生させるだけのメソッドを使用する場合、おそらく意図したとおりに機能しないことを意味します。言い換えれば、これまで機能していたものを壊してしまいました。これは、初期化メソッドにも当てはまります。このようなかなり一般的な実装の例を次に示します。

- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    ...bla bla...
    return self;
}

- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
    self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
    return self;
}

サブクラスでこれを行うと、-initWithLessParametersがどうなるか想像してみてください。

- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

これは、メソッドをオーバーライドする予定がない限り、特に初期化メソッドではプライベート(非表示)メソッドを使用する傾向があることを意味します。ただし、スーパークラスの実装を常に完全に制御できるわけではないため、これは別のトピックです。(これにより、__ attribute((objc_designated_initializer))の使用は悪い習慣として疑問視されます。

また、サブクラスでオーバーライドする必要があるメソッドでアサーションと例外を使用できることも意味します。(Objective-Cでの抽象クラスの作成と同様の「抽象」メソッド)

+新しいクラスメソッドを忘れないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.