web-dev-qa-db-ja.com

Objective-Cがプライベートメソッドをサポートしないのはなぜですか?

Objective-Cでセミプライベートメソッドを宣言するための多くの戦略を見てきましたが、真にプライベートなメソッドを作成する方法はないようです。私はそれを受け入れます。しかし、なぜそうなのでしょうか?私が本質的に言ったすべての説明は、「あなたはそれをすることはできませんが、これは厳密な近似です。」

スコープを制御するivars(メンバー)に適用されるいくつかのキーワードがあります。 @private@public@protected。メソッドでもこれができないのはなぜですか?ランタイムがサポートできるもののようです。欠けている根本的な哲学はありますか?これは意図的ですか?

122
Rob Jones

答えは...まあ...簡単です。実際、シンプルさと一貫性。

Objective-Cは、メソッドのディスパッチの時点で純粋に動的です。特に、すべてのメソッドディスパッチは、他のすべてのメソッドディスパッチとまったく同じ動的メソッド解決ポイントを通過します。実行時、すべてのメソッド実装はまったく同じ露出を持ち、Objective-Cランタイムが提供するすべてのAPIは、メソッドとセレクターで機能し、すべてのメソッドで同等に機能します。

多くの人が答えているように(ここと他の質問の両方で)、コンパイル時のプライベートメソッドがサポートされています。クラスが公開されているインターフェイスでメソッドを宣言していない場合、コードに関する限り、そのメソッドは存在しない可能性があります。つまり、プロジェクトを適切に編成することにより、コンパイル時に必要な可視性のさまざまな組み合わせをすべて実現できます。

同じ機能をランタイムに複製してもほとんど利点はありません。それは途方もない量の複雑さとオーバーヘッドを追加します。そして、そのような複雑さをすべて備えていても、最もカジュアルな開発者以外のすべてが「プライベート」メソッドを実行することを妨げることはありません。

編集:私が気づいた仮定の1つは、プライベートメッセージがランタイムを通過しなければならず、潜在的に大きなオーバーヘッドが発生することです。これは絶対に本当ですか?

はい、そうです。クラスの実装者が実装でObjective-Cの機能セットのすべてを使用したくないと考える理由はありません。これは、動的ディスパッチが発生する必要があることを意味します。 ただし、プライベートメソッドがobjc_msgSend()の特別なバリアントによってディスパッチされない特別な理由はありません。つまり、これはClass構造にプライベート専用のメソッドテーブルを追加することで実現できます。

プライベートメソッドがこのチェックを短絡したり、ランタイムをスキップしたりする方法はありませんか?

ランタイムをスキップできませんでしたが、ランタイムwould n'tは必ずプライベートメソッドのチェックを行う必要があります。

とは言っても、サードパーティが故意にオブジェクトのobjc_msgSendPrivate()をそのオブジェクトの実装の外で呼び出すことができなかった理由はなく、いくつかのこと(たとえば、KVO)がそれをしなければならないでしょう。実際には、これは単なる慣習であり、プライベートメソッドのセレクターにプレフィックスを付けるか、インターフェイスヘッダーでそれらに言及しないよりも実際上は少しだけ良いでしょう。

ただし、そうすると、言語の純粋な動的な性質が損なわれます。すべてのメソッドディスパッチが同一のディスパッチメカニズムを通過することはなくなりました。代わりに、ほとんどのメソッドが一方向に動作し、ほんの一握りが異なるだけの状況に置かれます。

CocoaにはObjective-Cの一貫したダイナミズムに基づいて構築された多くのメカニズムがあるため、これはランタイムを超えて拡張されます。たとえば、プライベートメソッドをサポートするには、キー値コーディングとキー値監視の両方を大幅に変更する必要があります(ほとんどの場合、悪用可能な抜け穴を作成することによって)か、プライベートメソッドに互換性がありません。

102
bbum

ランタイムはそれをサポートできますが、コストは莫大になります。送信されるすべてのセレクターは、そのクラスに対してプライベートかパブリックかをチェックする必要があります。または、各クラスが2つの個別のディスパッチテーブルを管理する必要があります。この保護レベルはコンパイル時に行われるため、インスタンス変数の場合は同じではありません。

また、ランタイムは、プライベートメッセージの送信者が受信者と同じクラスであることを確認する必要があります。プライベートメソッドをバイパスすることもできます。クラスがinstanceMethodForSelector:を使用した場合、返されたIMPを他のクラスに渡して、プライベートメソッドを直接呼び出すことができます。

プライベートメソッドは、メッセージのディスパッチをバイパスできませんでした。次のシナリオを検討してください。

  1. クラスAllPublicには、パブリックインスタンスメソッドdoSomethingがあります

  2. 別のクラスHasPrivateには、doSomethingとも呼ばれるプライベートインスタンスメソッドがあります

  3. AllPublicHasPrivateの両方のインスタンスをいくつでも含む配列を作成します

  4. 次のループがあります。

    for (id anObject in myArray)
        [anObject doSomething];
    

    AllPublic内からループを実行した場合、ランタイムはdoSomethingインスタンスでHasPrivateの送信を停止する必要がありますが、このループはHasPrivateクラス内にあれば使用できます。

18
dreamlax

これまでに投稿された回答は、哲学的な観点から質問に答えるのに良い仕事をしているので、より実用的な理由を推測します:言語のセマンティクスを変更することで何が得られるでしょうか?プライベートメソッドを効果的に「隠す」のに十分なほど簡単です。例として、次のように、ヘッダーファイルで宣言されたクラスがあるとします。

@interface MyObject : NSObject {}
- (void) doSomething;
@end

「プライベート」メソッドが必要な場合は、これを実装ファイルに含めることもできます。

@interface MyObject (Private)
- (void) doSomeHelperThing;
@end

@implementation MyObject

- (void) doSomething
{
    // Do some stuff
    [self doSomeHelperThing];
    // Do some other stuff;
}

- (void) doSomeHelperThing
{
    // Do some helper stuff
}

@end

確かに、それはquite C++/Javaプライベートメソッドと同じではありませんが、実質的に十分に近いので、言語のセマンティクス、コンパイラ、ランタイムなどを変更して、既に許容可能な方法でエミュレートされている機能他の回答で述べたように、メッセージ受け渡しのセマンティクスとランタイムリフレクションへの依存により、「プライベート」メッセージの処理が重要になります。

13
mipadi

最も簡単な解決策は、Objective-Cクラスでいくつかの静的C関数を宣言することです。これらは、静的キーワードのCルールに従ってファイルスコープのみを持ち、そのため、そのクラスのメソッドでのみ使用できます。

大騒ぎはありません。

7
Cromulent

はい、C++を処理するためにコンパイラで既に採用されている手法を使用することで、ランタイムに影響を与えることなく実行できます:名前のマングリング。

他の手法(プレフィックスやアンダースコアなど)が十分に回避できるコーディング問題空間のかなりの困難を解決することが確立されていないため、これは行われていません。 IOW、あなたは根深い習慣を克服するためにより多くの痛みが必要です。

Clangまたはgccにパッチを提供して、構文にプライベートメソッドを追加し、コンパイル中にそれだけが認識した(そしてすぐに忘れてしまった)マングルされた名前を生成できます。その後、Objective-Cコミュニティの他のユーザーは、実際に価値があるかどうかを判断できます。開発者を説得しようとするよりも、その方が速いでしょう。

6
Huperniketes

基本的に、Objective-Cのメソッド呼び出しのメッセージ受け渡し形式に関係しています。任意のオブジェクトに任意のメッセージを送信でき、オブジェクトはメッセージへの応答方法を選択します。通常、メッセージにちなんで名付けられたメソッドを実行することで応答しますが、他の多くの方法でも応答できます。これはプライベートメソッドを完全に不可能にするわけではありません— Rubyは同様のメッセージパッシングシステムでそれを行います—しかしそれはそれらをやや厄介にします。

Rubyのプライベートメソッドの実装でさえ、奇妙さのために人々を少し混乱させます(好きなメッセージをオブジェクトに送信できますこのリストにあるものを除く!)。基本的に、Rubyは、明示的なレシーバーで呼び出されるプライベートメソッドを禁止することで機能します。Objective-Cにはそのオプションがないため、Objective-Cではさらに多くの作業が必要になります。

4
Chuck

これは、Objective-Cのランタイム環境の問題です。 C/C++ は読み取り不可能なマシンコードにコンパイルされますが、 Objective-Cは文字列としてのメソッド名のような人間が読み取り可能な属性を保持します 。これにより、Objective-Cで 反射 機能を実行できます。

EDIT:厳密なプライベートメソッドのないリフレクティブ言語であるため、Objective-Cは、コードを制限するのではなくコードを使用する他の人を信頼するという点で、より「Python的」です。彼らが呼び出すことができるメソッド。ダブルアンダースコアなどの命名規則を使用すると、カジュアルなクライアントコーダーからコードを隠すことができますが、より深刻な作業を行う必要のあるコーダーを止めることはできません。

1
pokstad

質問の解釈に応じて2つの答えがあります。

1つ目は、メソッドの実装をインターフェイスから隠すことです。これは通常、名前のないカテゴリで使用されます(例:@interface Foo())。これにより、オブジェクトはそれらのメッセージを送信できますが、他のメッセージは送信できません。ただし、誤って(または別の方法で)オーバーライドする可能性があります。

2番目の答えは、これがパフォーマンスとインライン化に関するものであるという前提で、可能になりましたが、代わりにローカルC関数としてです。 「プライベートfoo(_NSString *arg_)」メソッドが必要な場合は、void MyClass_foo(MyClass *self, NSString *arg)を実行し、MyClass_foo(self,arg)のようなC関数として呼び出します。構文は異なりますが、C++のプライベートメソッドの健全なパフォーマンス特性で動作します。

これは質問に答えますが、no-nameカテゴリは、これを行うためのより一般的なObjective-Cの方法であることを指摘する必要があります。

1
AlBlue

Objective-Cは、プライベートメソッドを必要としないため、プライベートメソッドをサポートしません。

C++では、クラスの宣言ですべてのメソッドが表示される必要があります。ヘッダーファイルを含む誰かが見ることができないメソッドを持つことはできません。したがって、実装外のコードで使用しないメソッドが必要な場合、選択の余地はありません。コンパイラは、メソッドを使用してはならないこと、つまり「プライベート」キーワードであることを伝えるためのツールを提供する必要があります。

Objective-Cでは、ヘッダーファイルにないメソッドを使用できます。そのため、メソッドをヘッダーファイルに追加しないことで、同じ目的を非常に簡単に達成できます。プライベートメソッドは必要ありません。 Objective-Cには、プライベートメソッドを変更したため、クラスのすべてのユーザーを再コンパイルする必要がないという利点もあります。

ヘッダーファイルで宣言する必要があったインスタンス変数(これ以上ではない)、@ private、@ public、および@protectedは使用可能です。

0
gnasher729

ここで欠けている答えは、プライベートメソッドは進化可能性の観点からは悪い考えだからです。メソッドを記述するときにメソッドをプライベートにすることは良い考えのように思えるかもしれませんが、それは事前バインディングの形式です。コンテキストが変更される可能性があり、後のユーザーが別の実装を使用する場合があります。少し挑発的:「アジャイル開発者はプライベートメソッドを使用しません」

ある意味では、Smalltalkと同様に、Objective-Cは大​​人のプログラマ向けです。元の開発者がインターフェイスをどのように想定しているかを知っていることを大切にし、実装を変更する必要がある場合は結果に対処する責任を負います。はい、それは哲学であり、実装ではありません。

0