"카테고리가 기본 클래스에서도 구현 될 메서드를 구현하고 있습니다."경고 표시 안 함


98

경고를 억제하는 방법을 궁금합니다.

카테고리는 기본 클래스에 의해 구현 될 메서드를 구현하고 있습니다.

특정 코드 범주에 대해 다음이 있습니다.

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

방법에 의해 소용돌이. 그렇게하지 않겠지 만-아마도 동일한 메서드를 재정의하는 UIFont 하위 클래스를 만들고 super그렇지 않으면 호출 할 수 있습니다.
Alan Zeino 2012

4
당신의 문제는 경고가 아닙니다. 문제는 동일한 메서드 이름을 가지고 있다는 것입니다. 이로 인해 문제가 발생합니다.
gnasher729

범주를 사용하여 메서드를 재정의 하면 안되는 이유와 대체 솔루션에 대해서는 Objective-C의 범주를 사용하여 메서드 재정의를 참조하세요 .
Senseful 2014-06-26

응용 프로그램 전체 글꼴을 설정하는 더 우아한 솔루션을 알고 있다면 정말 듣고 싶습니다!
To1ne

답변:


64

범주를 사용하면 기존 클래스에 새 메서드를 추가 할 수 있습니다. 클래스에 이미 존재하는 메서드를 다시 구현하려면 일반적으로 범주 대신 하위 클래스를 만듭니다.

Apple 문서 : 기존 클래스 사용자 정의

범주에 선언 된 메서드의 이름이 원래 클래스의 메서드와 같거나 동일한 클래스 (또는 수퍼 클래스)의 다른 범주에있는 메서드와 동일하면 어떤 메서드 구현에서 사용되는지에 대한 동작이 정의되지 않습니다. 실행 시간.

동일한 클래스에서 정확히 동일한 서명을 가진 두 메서드는 각 호출자가 원하는 구현을 지정할 수 없기 때문에 예측할 수없는 동작을 유발합니다.

따라서 범주를 사용하고 클래스에 대해 새롭고 고유 한 메서드 이름을 제공하거나 클래스에서 기존 메서드의 동작을 변경하려는 경우 하위 클래스를 제공해야합니다.


1
위에서 설명한 아이디어에 전적으로 동의하고 개발 중에 따르려고합니다. 그러나 여전히 범주의 재정의 메서드가 적절한 경우가있을 수 있습니다. 예를 들어, 다중 상속 (예 : C ++) 또는 인터페이스 (예 : C #)를 사용할 수 있습니다. 내 프로젝트에서 그 문제에 직면했고 카테고리에서 방법을 재정의하는 것이 최선의 선택이라는 것을 깨달았습니다.
peetonn 2011

4
싱글 톤이있는 일부 코드를 단위 테스트 할 때 유용 할 수 있습니다. 이상적으로는 싱글 톤을 프로토콜로 코드에 삽입하여 구현을 전환 할 수 있어야합니다. 그러나 이미 코드 내에 포함 된 것이 있다면 단위 테스트에 싱글 톤의 범주를 추가하고 sharedInstance 및이를 더미 객체로 변환하기 위해 제어 할 메서드를 재정의 할 수 있습니다.
bandejapaisa 2013

감사합니다 @PsychoDad. 링크를 업데이트하고이 게시물과 관련된 문서에서 인용문을 추가했습니다.
bneely

좋아 보인다. Apple은 기존 메소드 이름과 함께 카테고리를 사용하는 동작에 대한 문서를 제공합니까?
jjxtra

1
내가 :-) 카테고리 또는 서브 클래스로 이동하는 경우는 끝내, 확실하지 않았다
kernix

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


감사합니다! 따라서 pragma도 경고를 억제 할 수 있습니다. - P
콘스탄티노 Tsarouhas

네, 이것들은 LLVM 특정 문장이지만 GCC에도 비슷한 문장이 있습니다.
벤 남작

1
테스트 프로젝트의 경고는 llvm 컴파일러 경고가 아니라 링커 경고이므로 llvm pragma는 아무 작업도 수행하지 않습니다. 그러나 테스트 프로젝트는 링커 경고이기 때문에 "경고를 오류로 처리"가 설정된 상태로 빌드된다는 것을 알 수 있습니다.
Ben Baron

12
이것은 실제로 질문에 대한 답을 제공한다는 점을 감안할 때 허용되는 답이어야합니다.
Rob Jones

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
이것은 완전한 예가 아닙니다. EXCHANGE_METHOD라는 이름의 매크로는 실제로 Objective-C 런타임에 의해 정의되지 않습니다.
Richard J. Ross III

@Vitaly stil -1. 해당 메소드는 클래스 유형에 대해 구현되지 않습니다. 어떤 프레임 워크를 사용하고 있습니까?
리처드 J. 로스 III

다시 한번 시도해보세요. NSObject + MethodExchange
Vitaliy Gervazuk

NSObject의 카테고리로 왜 매크로를 귀찮게 하는가? 왜 'exchangeMethod'를 사용하지 않습니까?
hvanbrug

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에 따르면 공용 클래스에 대한 개인 인터페이스를 생성하여 개인 인터페이스가 공개적으로 노출 된 속성을 재정의 할 수 있습니다. 즉, 속성을 읽기 전용에서 읽기 쓰기로 변경할 수 있습니다.

이에 대한 사용 사례는 공용 속성에 대한 액세스를 제한하는 라이브러리를 작성하는 반면 동일한 속성에는 라이브러리 내에서 전체 읽기 쓰기 액세스가 필요합니다.

Apple 문서 링크 : https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

" 클래스 확장을 사용하여 개인 정보 숨기기 "를 검색 하십시오 .

따라서이 기술은 클래스 확장에는 유효하지만 카테고리에는 유효하지 않습니다.


1

카테고리는 좋은 것이지만 남용 될 수 있습니다. 카테고리를 작성할 때 원칙적으로 기존 방법을 다시 구현하지 않아야합니다. 이렇게하면 다른 클래스가 종속 된 코드를 다시 작성하므로 이상한 부작용이 발생할 수 있습니다. 알려진 클래스를 깨뜨려 디버거를 뒤집을 수 있습니다. 단순히 나쁜 프로그래밍입니다.

당신이 그것을해야한다면, 당신은 정말로 그것을 서브 클래스해야한다.

그런 다음 스위 즐링의 제안은 나에게 큰 NO-NO-NO입니다.

런타임에 Swizzing하는 것은 완전한 NO-NO-NO입니다.

바나나가 주황색처럼 보이기를 원하지만 런타임에만? 오렌지를 원한다면 오렌지를 쓰세요.

바나나를 오렌지처럼 보이게하지 마십시오. 더 나쁜 것은, 바나나를 오렌지를 지원하기 위해 조용히 전세계 바나나를 방해하는 비밀 요원으로 바꾸지 마십시오.

이런!


3
하지만 런타임에 Swizzing은 테스트 환경에서 동작을 조롱하는 데 유용 할 수 있습니다.
Ben G

2
유머러스하지만 당신의 대답은 가능한 모든 방법이 나쁘다고 말하는 것 이상을하지 않습니다. 짐승의 본질은 때때로 당신이 정말로 서브 클래스를 할 수 없다는 것입니다. 그래서 당신은 카테고리가 남게됩니다. 특히 당신이 분류하고있는 클래스에 대한 코드를 소유하고 있지 않다면, 때때로 스위 즐을해야합니다. 바람직하지 않은 방법은 관련이 없습니다.
hvanbrug

1

메인 클래스가 아닌 카테고리에서 델리게이트 메서드를 구현할 때이 문제가 발생했습니다 (메인 클래스 구현이 없었음에도 불구하고). 나를위한 해결책은 메인 클래스 헤더 파일에서 카테고리 헤더 파일로 이동하는 것이 었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.