警告を抑制します「カテゴリは、そのプライマリクラスによっても実装されるメソッドを実装しています」


98

警告を抑制する方法を考えていました:

カテゴリは、そのプライマリクラスによって実装されるメソッドを実装しています。

私は特定のコードカテゴリに対してこれを持っています:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}

メソッドによるスウィズリング。私はそうしませんが、同じメソッドをオーバーライドするUIFontサブクラスを作成し、superそれ以外の場合は呼び出すことができます。
アランZeino、2012

4
あなたの問題は警告ではありません。あなたの問題はあなたが同じメソッド名を持っていることです、それは問題につながるでしょう。
gnasher729 2014


アプリケーション全体のフォントを設定するためのより洗練された解決策を知っている人がいたら、是非聞いてください!
To1ne

回答:


64

カテゴリを使用すると、新しいメソッドを既存のクラスに追加できます。クラスにすでに存在するメソッドを再実装する場合は、通常、カテゴリではなくサブクラスを作成します。

Appleドキュメント:既存のクラスのカスタマイズ

カテゴリで宣言されたメソッドの名前が元のクラスのメソッド、または同じクラス(またはスーパークラス)の別のカテゴリのメソッドと同じである場合、どのメソッドの実装が使用されるかについての動作は定義されていません。ランタイム。

同じクラスでまったく同じシグネチャを持つ2つのメソッドは、各呼び出し元が必要な実装を指定できないため、予期しない動作を引き起こします。

したがって、カテゴリを使用して、クラスに対して新しい一意のメソッド名を指定するか、クラスの既存のメソッドの動作を変更する場合はサブクラスを指定する必要があります。


1
私は上記で説明したアイデアに完全に同意し、開発中にそれらに従うことを試みます。しかし、それでもなお、カテゴリのオーバーライドメソッドが適切である場合があります。たとえば、多重継承(c ++など)またはインターフェース(c#など)を使用できる場合などです。私のプロジェクトでちょうどそれに直面し、カテゴリのメソッドをオーバーライドすることが最善の選択であることを認識しました。
peetonn

4
シングルトンが含まれているコードを単体テストするときに役立ちます。理想的には、シングルトンをプロトコルとしてコードに挿入し、実装を切り替えることができるようにする必要があります。ただし、コード内に既に1つが埋め込まれている場合は、単体テストにシングルトンのカテゴリを追加し、sharedInstanceとそれらをダミーオブジェクトに変換するために制御するメソッドをオーバーライドできます。
bandejapaisa

@PsychoDadに感謝します。リンクを更新し、この投稿に関連するドキュメントからの引用を追加しました。
bneely

いいね。Appleは、既存のメソッド名でカテゴリを使用する動作に関するドキュメントを提供していますか?
jjxtra 2013

1
すごい、カテゴリとサブクラスのどちらを使うべきか
わかり

343

ひどいことはすべて正しいですが、実際には警告を抑制する方法についてのあなたの質問に答えるものではありません。

何らかの理由でこのコードを含める必要がある場合(私の場合、プロジェクトにHockeyKitがあり、UIImageカテゴリのメソッドをオーバーライドします[編集:これはもう当てはまりません])、プロジェクトをコンパイルする必要があります、次のように#pragmaステートメントを使用して警告をブロックできます。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

// do your override

#pragma clang diagnostic pop

私はここに情報を見つけました:http : //www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html


どうもありがとう!したがって、プラグマは警告も抑制できると思います。:-p
コンスタンティーノツァロウハ

そう、これらはLLVM固有のステートメントですが、GCCにも同様のステートメントがあります。
Ben Baron

1
テストプロジェクトの警告は、リンカーの警告であり、llvmコンパイラの警告ではないため、llvmプラグマは何も実行していません。ただし、リンカーの警告であるため、テストプロジェクトは「警告をエラーとして扱う」がオンのままでビルドされていることに気付くでしょう。
Ben Baron

12
これは実際に質問に答えるので、これは実際に受け入れられる答えでなければなりません。
Rob Jones

1
この答えは正しいはずです。とにかく、それは答えとして選ばれたものより多くの票を持っています。
ファンカタラン

20

より良い代替案(この警告が災害からあなたを救う理由についてのbneelyの回答を参照)は、メソッドのスウィズリングを使用することです。メソッドのスウィズリングを使用することにより、誰が「勝つ」のかがわからず、古いメソッドを呼び出す機能を維持しながら、カテゴリの既存のメソッドを置き換えることができます。秘密は、オーバーライドに別のメソッド名を付け、ランタイム関数を使用してそれらを交換することです。

#import <objc/runtime.h> 
#import <objc/message.h>

void MethodSwizzle(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
    method_exchangeImplementations(origMethod, newMethod);
}

次に、カスタム実装を定義します。

+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}

デフォルトの実装を自分のものでオーバーライドします。

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));

10

あなたのコードでこれを試してください:

+(void)load{
    EXCHANGE_METHOD(Method1, Method1Impl);
}

UPDATE2:このマクロを追加

#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]

@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end

#import <objc/runtime.h>

@implementation NSObject (MethodExchange)

+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
    Class class = [self class];

    Method origMethod = class_getInstanceMethod(class, origSel);
    if (!origMethod){
        origMethod = class_getClassMethod(class, origSel);
    }
    if (!origMethod)
        @throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!newMethod){
        newMethod = class_getClassMethod(class, newSel);
    }
    if (!newMethod)
        @throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
    if (origMethod==newMethod)
        @throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
    method_exchangeImplementations(origMethod, newMethod);
}

@end

1
これは完全な例ではありません。EXCHANGE_METHODという名前のマクロは、実際にはObjective-Cランタイムによって定義されていません。
リチャードJ.ロスIII

@Vitaly stil -1。そのメソッドは、クラス型には実装されていません。どのフレームワークを使用していますか?
リチャードJ.ロスIII

申し訳ありませんが、これを試してみてください。作成したファイルNSObject + MethodExchange
Vitaliy Gervazuk '27

NSObjectのカテゴリを使用して、なぜマクロに悩まされるのでしょうか。「exchangeMethod」でスウィズルしないのはなぜですか?
hvanbrug 2013年

5

メソッドのスウィズリングを使用して、このコンパイラの警告を抑制できます。UITextBorderStyleNoneでカスタム背景を使用するときに、UITextFieldでマージンを描画するためのメソッドスウィズリングを実装する方法を次に示します。

#import <UIKit/UIKit.h>

@interface UITextField (UITextFieldCatagory)

+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end

#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>

@implementation UITextField (UITextFieldCatagory)

+(void)load
{
    Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
    Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));

    Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
    Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));


    method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
    method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);

}


- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
    CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
    return inset;
}

@end

2

プロパティのオーバーライドは、クラス拡張(匿名カテゴリ)では有効ですが、通常のカテゴリでは無効です。

クラス拡張機能(匿名カテゴリ)を使用するApple Docsによると、パブリックインターフェイスへのプライベートインターフェイスを作成して、プライベートインターフェイスがパブリックに公開されたプロパティをオーバーライドできるようにすることができます。つまり、プロパティをreadonlyからreadwriteに変更できます。

この使用例は、パブリックプロパティへのアクセスを制限するライブラリを作成するときに、同じプロパティがライブラリ内で完全な読み取り/書き込みアクセスを必要とする場合です。

Apple Docsリンク:https : //developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

クラス拡張を使用して個人情報を非表示にする検索します。

したがって、この手法はクラス拡張には有効ですが、カテゴリには有効ではありません。


1

カテゴリは良いことですが、乱用される可能性があります。カテゴリを作成するときは、原則として既存のメソッドを再実装しないでください。別のクラスが依存するコードを書き直しているため、これを行うと奇妙な副作用が発生する可能性があります。既知のクラスを壊して、デバッガを裏返しにしてしまう可能性があります。それは単に悪いプログラミングです。

それを行う必要がある場合は、実際にサブクラス化する必要があります。

それから、スウィズリングの提案、それは私にとって大きなNO-NO-NOです。

実行時にそれを入れ替えることは完全なNO-NO-NOです。

バナナをオレンジのように見せたいのですが、実行時のみですか?オレンジが欲しいなら、オレンジを書いてください。

バナナのように見せたり、オレンジのように振ったりしないでください。そしてさらに悪いことに、あなたのバナナを秘密のエージェントに変えないでください。秘密のエージェントは、オレンジをサポートするために世界中で静かにバナナを妨害します。

うわぁ!


3
ただし、実行時にスウィズすることは、テスト環境で動作をモックするのに役立ちます。
ベンG

2
滑稽ではありますが、あなたの答えは実際にはすべての可能な方法が悪いと言う以上のものではありません。野獣の性質は、実際にはサブクラス化できない場合があるため、カテゴリが残され、特に、分類するクラスのコードを所有していない場合は、スウィズルを実行する必要がある場合があります。望ましくない方法であることは関係ありません。
hvanbrug 2013年

1

メインクラスではなくカテゴリにデリゲートメソッドを実装すると、この問題が発生しました(メインクラスの実装はありませんでした)。私のための解決策は、メインクラスのヘッダーファイルからカテゴリーのヘッダーファイルに移動することでしたこれはうまくいきます

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