禁止警告“类别正在实现一种方法,该方法也将由其主要类实现”


98

我想知道如何抑制警告:

Category正在实现一种方法,该方法也将由其主类实现。

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

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

通过方法sw。尽管我不会这样做-也许您可以使UIFont子类替代相同的方法,super否则调用它。
艾伦·泽伊诺

4
您的问题不是警告。您的问题是您拥有相同的方法名称,这将导致问题。
gnasher729 2014年

有关为什么不应该使用类别覆盖方法的原因以及其他解决方案,请参见Objective-C中的使用类别覆盖方法。
有意义的2014年

如果您知道设置应用程序范围字体的更优雅的解决方案,我真的很想听听!
To1ne

Answers:


64

类别使您可以向现有类添加新方法。如果要重新实现该类中已经存在的方法,则通常创建一个子类而不是类别。

Apple文档:自定义现有类

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

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

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


1
我完全同意上面解释的那些想法,并在开发过程中尝试遵循它们。但仍然可能存在某些情况,其中类别中的覆盖方法可能是适当的。例如,可以使用多重继承(如c ++)或接口(如c#)的情况。刚在我的项目中遇到过这种情况,并意识到类别中的覆盖方法是最佳选择。
peetonn 2012年

4
当单元测试其中包含单例的某些代码时,此功能很有用。理想情况下,应该将单例作为协议注入代码中,以允许您切换实现。但是,如果您的代码中已经嵌入了一个代码,则可以在单元测试中添加一个单例类别,并覆盖sharedInstance和控制方法以将其变成虚拟对象。
bandejapaisa

谢谢@PsychoDad。我更新了链接,并在与该帖子相关的文档中添加了引号。
2013年

看起来挺好的。Apple是否提供有关使用具有现有方法名称的类别的行为的文档?
jjxtra

1
很棒,不确定我应该选择类别还是子类别:-)
kernix 2015年

343

尽管所有的转瞬即逝都是正确的,但实际上并没有回答您如何抑制警告的问题。

如果您由于某种原因必须包含此代码(就我而言,我的项目中有HockeyKit,并且它们覆盖了UIImage类别中的方法[edit:情况不再如此]),则需要使您的项目进行编译,您可以使用#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也有类似的语句。
本·巴伦

1
测试项目中的警告是链接器警告,而不是llvm编译器警告,因此llvm pragma不会执行任何操作。但是,您会注意到您的测试项目仍然是在“错误警告”的情况下生成的,因为它是链接器警告。
本·巴伦

12
考虑到它确实回答了这个问题,这实际上应该是被接受的答案。
罗伯·琼斯

1
这个答案应该是正确的。无论如何,它的票数都比选择答案的票数还多。
Juan Catalan

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
这不是一个完整的示例。Objective-C运行时实际上没有定义名为EXCHANGE_METHOD的宏。
理查德·罗斯三世

@Vitaly Stil -1。该方法未针对类类型实现。您正在使用什么框架?
理查德·罗斯三世

再次抱歉,请尝试一下,我创建了文件NSObject + MethodExchange
Vitaliy Gervazuk 2012年

使用NSObject上的类别,为什么还要麻烦宏呢?为什么不随便使用“ exchangeMethod”方法呢?
hvanbrug

5

您可以使用swizzling方法抑制此编译器警告。这是当我们将自定义背景与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,您可以为公共类创建私有接口,这样私有接口可以覆盖公开显示的属性。即,您可以将属性从只读更改为读写。

一个用例是编写限制公共属性访问的库,而同一属性需要在库内具有完全读写访问权限。

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

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

因此,此技术对类扩展有效,但对类别无效。


1

类别是一件好事,但它们可能会被滥用。编写类别时,原则上不应重新实现现有方法。这样做可能会引起奇怪的副作用,因为您现在正在重新编写另一个类所依赖的代码。您可能会破坏一个已知的类,并最终使调试器由内而外。这简直是​​糟糕的编程。

如果需要执行此操作,则确实应该将其子类化。

然后,有个令人毛骨悚然的建议,这对我来说是一个很大的禁忌。

在运行时模糊它是一个完全的NO-NO-NO。

您想让香蕉看起来像橙色,但仅在运行时?如果要橙色,请写一个橙色。

不要让香蕉看起来像橘子。更糟糕的是:不要将您的香蕉变成秘密特工,他会在全世界范围内悄悄破坏香蕉以支持桔子。

kes!


3
但是,运行时的混乱可能对于测试环境中的模拟行为很有用。
Ben G

2
尽管很幽默,但您的回答并不能说明所有可能的方法都是不好的。野兽的本质是,有时您确实不能继承子类,因此您将获得一个类别,尤其是如果您不拥有要分类的类的代码时,有时需要进行麻烦处理,并且不受欢迎的方法是无关紧要的。
hvanbrug

1

当我在类别而不是主类中实现委托方法时,即使没有主类实现,也遇到了这个问题。对我来说,解决方案是将主类头文件移到类别头文件,这工作正常

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.