抑制警告“类别正在实现一个方法,该方法也将由其主类实现”

Posted

技术标签:

【中文标题】抑制警告“类别正在实现一个方法,该方法也将由其主类实现”【英文标题】:Suppress warning "Category is implementing a method which will also be implemented by its primary class" 【发布时间】:2012-03-14 12:07:08 【问题描述】:

我想知道如何抑制警告:

Category 正在实现一个方法,该方法也将由 它的主要类。

我有一个特定的代码类别:

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

【问题讨论】:

按方法调配。虽然我不会那样做——也许你可以创建一个覆盖相同方法的 UIFont 子类,否则调用 super 您的问题不是警告。你的问题是你有相同的方法名,这会导致问题。 请参阅Overriding methods using categories in Objective-C,了解您不应使用类别覆盖方法的原因以及替代解决方案。 如果你们知道设置应用程序范围字体的更优雅的解决方案,我真的很想听听! 【参考方案1】:

类别允许您向现有类添加新方法。如果要重新实现类中已存在的方法,通常创建子类而不是类别。

Apple 文档:Customizing existing classes

如果在 a 中声明的方法的名称 category 与原始类中的方法相同,或者与原始类中的方法相同 同一类(甚至是超类)的另一个类别, 行为未定义关于使用哪种方法实现 运行时。

同一类中具有完全相同签名的两个方法会导致不可预知的行为,因为每个调用者都无法指定他们想要的实现。

因此,如果要更改类中现有方法的行为,您应该使用类别并提供新的和唯一的类方法名称,或者提供子类名称。

【讨论】:

我完全同意上面解释的那些想法,并在开发过程中尝试遵循它们。但仍然存在某些情况,类别中的覆盖方法可能是合适的。例如,可以使用多重继承(如在 c++ 中)或接口(如在 c# 中)的情况。刚刚在我的项目中遇到了这个问题,并意识到在类别中覆盖方法是最好的选择。 在对某些包含单例的代码进行单元测试时很有用。理想情况下,单例应该作为协议注入代码中,允许您切换实现。但是,如果您的代码中已经嵌入了一个,您可以在单元测试中添加一个单例类别,并覆盖 sharedInstance 和您控制的方法以将它们变成虚拟对象。 谢谢@PsychoDad。我更新了链接并添加了与这篇文章相关的文档中的引用。 看起来不错。 Apple 是否提供有关使用具有现有方法名称的类别的行为的文档? 太棒了,不确定我应该使用类别还是子类:-)【参考方案2】:

尽管 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

【讨论】:

非常感谢!所以,我看到编译指示也可以抑制警告。 :-p 是的,虽然这些是 LLVM 特定的语句,但 GCC 也有类似的语句。 测试项目中的警告是链接器警告,而不是 llvm 编译器警告,因此 llvm pragma 没有做任何事情。但是,您会注意到您的测试项目仍然在打开“将警告视为错误”的情况下构建,因为它是一个链接器警告。 这确实应该是公认的答案,因为它确实回答了问题。 这个答案应该是正确的。无论如何,它的票数比被选为答案的票数要多。【参考方案3】:

在你的代码中试试这个:

+(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

【讨论】:

这不是一个完整的例子。 Objective-c 运行时实际上没有定义名为 EXCHANGE_METHOD 的宏。 @Vitaly 仍然是 -1。该方法未针对类类型实现。你用的是什么框架? 对不起,试试这个我创建的文件 NSObject+MethodExchange 有了 NSObject 上的类别,为什么还要麻烦宏?为什么不直接使用“exchangeMethod”呢?【参考方案4】:

更好的选择(请参阅 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:));

【讨论】:

【参考方案5】:

类别是一件好事,但它们可能会被滥用。 在编写类别时,您应该作为原则而不是重新实现现有方法。 这样做可能会导致奇怪的副作用,因为您现在正在重写另一个类依赖的代码。你可以打破一个已知的类,并最终把你的调试器从里到外。 这简直是​​糟糕的编程。

如果你需要这样做,你真的应该继承它。

然后是 swizzling 的建议,这对我来说是一个很大的 NO-NO-NO。

在运行时调整它是完全不可以的。

您想让香蕉看起来像橘子,但只是在运行时? 如果你想要一个橙子,就写一个橙子。

不要让香蕉看起来和表现得像橘子。 更糟糕的是:不要把你的香蕉变成一个秘密特工,他们会悄悄地破坏全世界的香蕉来支持橙子。

哎呀!

【讨论】:

不过,在运行时调整可能有助于模拟测试环境中的行为。 虽然很幽默,但您的回答实际上并没有比说所有可能的方法都不好。野兽的本质是,有时你真的不能子类化,所以你只剩下一个类别,特别是如果你不拥有你正在分类的类的代码,有时你需要做 swizzle,它作为一种不受欢迎的方法是无关紧要的。【参考方案6】:

当我在一个类别而不是主类中实现委托方法时遇到了这个问题(即使没有主类实现)。我的解决方案是将主类头文件移动到类别头文件 这很好用

【讨论】:

【参考方案7】:

您可以使用方法调配来抑制此编译器警告。当我们使用带有 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

【讨论】:

【参考方案8】:

覆盖属性对类扩展(匿名类别)有效,但对常规类别无效。

根据使用类扩展(匿名类别)的 Apple Docs,您可以为公共类创建私有接口,以便私有接口可以覆盖公开公开的属性。即您可以将属性从只读更改为读写。

这种情况的一个用例是,当您编写限制对公共属性的访问的库时,而同一属性需要在库中具有完全的读写访问权限。

Apple 文档链接:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

搜索“使用类扩展来隐藏私人信息”。

所以这种技术对类扩展有效,但对类别无效。

【讨论】:

以上是关于抑制警告“类别正在实现一个方法,该方法也将由其主类实现”的主要内容,如果未能解决你的问题,请参考以下文章

C#:有啥方法可以抑制编译器错误,类似于抑制警告消息?

抑制 PDO 警告

如何抑制 LLVM 自动合成警告?

正确抑制数据表中的警告?

如何在 Kotlin 中抑制检查式警告

抑制 OpenGL 运行时警告