web-dev-qa-db-ja.com

「カテゴリはメソッドを実装していますが、このメソッドもプライマリクラスによって実装されます」という警告を表示しない

私は警告を抑制する方法を疑問に思っていました:

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

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

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

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

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

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

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

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

63
bneely

ひざまずいて言ったことはすべて正しいが、実際には警告を抑制する方法についてのあなたの質問には答えない。

何らかの理由でこのコードを持たなければならない場合(私の場合、プロジェクトに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

341
Ben Baron

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

#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:));
20
Sanjit Saluja

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

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

PDATE2:このマクロを追加

#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
10

メソッドのスウィズリングを使用して、このコンパイラ警告を抑制することができます。 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
5
Say2Manuj

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

Apple Class Extension(Anonymous Category)を使用したドキュメント)によると、パブリッククラスへのプライベートインターフェイスを作成して、プライベートインターフェイスが公開されているプロパティをオーバーライドできるようにします。読み取り専用から読み取り書き込みへ。

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

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

個人情報を隠すためにクラス拡張を使用する」を検索します。

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

2

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

1
gheese

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

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

その後、スウィズルの提案、それは私にとって大きなNO-NO-NOです。

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

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

バナナを見てオレンジ色のように振る舞わないでください。さらに悪いことに、バナナを、オレンジを支持して世界中のバナナを静かに妨害する秘密のエージェントに変えてはいけません。

うわぁ!

1
Leander