web-dev-qa-db-ja.com

Objective-Cの継承とカテゴリの違いは何ですか

Objective Cのカテゴリと継承の違いを説明できる人はいますか? Wikipediaのエントリ を読みましたが、そこにあるカテゴリに関する議論は継承のものと何ら変わりはありません。私はまた、「Open iPhone Development」という本のトピックに関する議論を見ましたが、まだ理解できません。

57
hhafez

場合によっては、継承は価値があるよりも多くの問題のように思えます。既存のクラスに、そのクラスの動作の変更である何かを追加する場合に、正しく使用されます。

カテゴリを使用すると、既存のオブジェクトにもう少し処理が必要になります。既に説明したように、圧縮を処理する文字列クラスだけが必要な場合、文字列クラスをサブクラス化する必要はなく、圧縮を処理するカテゴリを作成するだけです。そうすれば、既に使用している文字列クラスのタイプを変更する必要はありません。

手がかりは、カテゴリがメソッドを追加するだけであり、カテゴリを使用してクラスに変数を追加できないという制限にあります。クラスにさらにプロパティが必要な場合は、サブクラス化する必要があります(編集:連想記憶域を使用できます)。

カテゴリは、機能を追加すると同時に、継承よりも合成を優先するオブジェクト指向の原則に準拠する良い方法です。

2012年1月編集

状況は今変わりました。現在のLLVMコンパイラと最新の64ビットランタイムを使用すると、iVarとプロパティをクラスextensions(カテゴリではなく)に追加できます。これにより、プライベートiVarsをパブリックインターフェースから除外できます。ただし、iVarsのプロパティを宣言する場合、Objective-Cにはプライベートメソッドなどはまだないため、KVCを介してプロパティにアクセス/変更できます。

96
Abizern

カテゴリを使用すると、既存のクラスにメソッドを追加できます。したがって、NSDataをサブクラス化してファンキーな新しい暗号化メソッドを追加するのではなく、NSDataクラスに直接追加できます。アプリ内のすべてのNSDataオブジェクトは、これらのメソッドにアクセスできるようになりました。

これがどれほど役立つかを確認するには、 CocoaDev を見てください。

17
Terry Wilcox

動作中のObjective-cカテゴリのお気に入りの図の1つはNSStringです。 NSStringは、ビューまたはウィンドウの概念を持たないFoundationフレームワークで定義されています。ただし、CocoaアプリケーションでNSStringを使用すると、– drawInRect:withAttributes:などのメッセージに応答することに気付くでしょう。

AppKitは、追加の描画メソッドを提供するNSStringのカテゴリを定義します。このカテゴリを使用すると、既存のクラスに新しいメソッドを追加できるため、NSStringsを扱っているだけです。 AppKitがサブクラス化による描画を代わりに実装する場合、「AppKitStrings」または「NSSDrawableStrings」またはそのようなものを処理する必要があります。

カテゴリを使用すると、アプリケーションまたはドメイン固有のメソッドを既存のクラスに追加できます。非常に強力で便利な場合があります。

11
amrox

プログラマーとしてコードライブラリまたはアプリケーションのソースコードの完全なセットが提供されている場合は、そのコードでプログラミングの目標を達成するために必要なものを変更できます。

残念ながら、これは常に当てはまるわけではなく、望ましいことでもありません。多くの場合、バイナリライブラリ/オブジェクトキットとそれに対応する一連のヘッダーが提供されます。

次に、クラスに新しい機能が必要になるため、いくつかのことができます。

  1. ストッククラスの代わりに新しいクラス全体を作成します。すべての関数とメンバーを複製し、すべてのコードを書き換えて新しいクラスを使用します。

  2. ストッククラスをメンバーとして含む新しいラッパークラスを作成し(合成)、新しいクラスを利用するようにコードベースを書き換えます。

  3. コードを変更するライブラリのバイナリパッチ(幸運)

  4. コンパイラに新しいクラスを古いクラスとして認識させ、特定のサイズやメモリ内の場所や特定のエントリポイントに依存しないことを期待します。

  5. サブクラスの特化-機能を追加するサブクラスを作成し、代わりにサブクラスを使用するようにドライバーコードを変更します-理論的には問題はほとんどないはずです。データメンバーを追加する必要がある場合は必要ですが、メモリフットプリントは異なります。サブクラスで新しいコードと古いコードの両方を使用できるという利点があり、どちらを使用するか、基本クラスメソッドまたはオーバーライドメソッドを選択できます。

  6. 必要なことを行うためのメソッドを含むカテゴリ定義で必要なobjcクラスを変更するか、ストッククラスの古いメソッドをオーバーライドします。

    また、ライブラリのエラーを修正したり、新しいハードウェアデバイスなどに合わせてメソッドをカスタマイズしたりすることもできます。これは万能薬ではありませんが、変更されていないクラス/ライブラリを再コンパイルせずにクラスメソッドを追加できます。元のクラスはコード、メモリサイズ、エントリポイントが同じであるため、レガシーアプリは壊れません。コンパイラは、新しいメソッドをそのクラスに属するものとしてランタイムに単に配置し、元のコードと同じシグネチャを持つメソッドをオーバーライドします。

    例:

    端末に出力するが、シリアルポートには出力しないクラスBingがあります。これが必要なものです。 (何らかの理由で)。キットにはBing.hとlibBing.soがありますが、Bing.mはありません。

    Bingクラスはあらゆる種類の処理を内部で実行します。すべてを知ることすらできず、ヘッダーにパブリックAPIが含まれているだけです。

    あなたは賢いので、Bingクラスの(SerialOutput)カテゴリを作成します。

    [Bing_SerialOutput.m]
    @interface Bing (SerialOutput)   // a category
    - (void)ToSerial: (SerialPort*) port ;
    @end
    
    @implementation Bing (SerialOutput)
    - (void)ToSerial: (SerialPort*) port 
    {
    ... /// serial output code ///
    }
    @end
    

    コンパイラーはアプリにリンクできるオブジェクトを作成する必要があり、ランタイムはBingが@selector(ToSerial :)に応答することを認識し、Bingクラスがそのメソッドで構築されたかのように使用できます。データメンバーのみのメソッドを追加することはできません。これは、基本クラスにアタッチされたコードの巨大な腫瘍を作成することを意図していませんでしたが、厳密に型指定された言語よりも優れています。

4
Chris Reid

これらの回答のいくつかは、少なくとも継承は既存のクラスに機能を追加するより重い方法であり、カテゴリはより軽量であるという考えを指し示していると思います。

継承は、新しいクラス階層(すべての機能)を作成するときに使用され、既存のクラスに機能を追加する方法として選択した場合、おそらく多くの作業をもたらします。

NSStringに新しいメソッドを追加するために継承を使用している場合、この新しいメソッドを使用する他のコードで使用している型を変更する必要があります。ただし、カテゴリを使用する場合、サブクラス化せずに、既存のNSString型でメソッドを呼び出すだけで済みます。

どちらでも同じ目的を達成できますが、カテゴリによって、よりシンプルでメンテナンスの必要性が低い(おそらく)オプションが提供されるようです。

カテゴリが絶対に必要な状況がある場合、誰でも知っていますか?

3
user3881658

カテゴリは、ミックスインのようなものです。Rubyのモジュール、またはJavaのインターフェイスのようなものです。 「裸のメソッド」と考えることができます。カテゴリを追加すると、クラスにメソッドが追加されます。ウィキペディアの記事には good stuff があります。

2
Charlie Martin

この違いを確認する最善の方法は、次のとおりです。例:遅延読み込みを実装するAsyncImageView。これは、UIViewを継承することによって行われます。 2. category:単に追加のフレーバーを追加したいだけです。例:テキストフィールドのテキストからすべてのスペースを置き換えたい

   @interface UITextField(setText)
      - (NSString *)replaceEscape;
   @end

   @implementation UITextField(setText)
      - (NSString *)replaceEscape
      {
         self.text=[self.text stringByTrimmingCharactersInSet:
                           [NSCharacterSet whitespaceCharacterSet]];
         return self.text;
      }
   @end

---すべての空白をエスケープするために、新しいプロパティがテキストフィールドに追加されます。方法を完全に変更せずに新しい次元を追加するのと同じです。

0
Avik Roy