source

목표-C에서 방법 스위블링의 위험은 무엇입니까?

manycodes 2023. 5. 11. 21:35
반응형

목표-C에서 방법 스위블링의 위험은 무엇입니까?

저는 사람들이 방법을 바꾸는 것이 위험한 관행이라고 말하는 것을 들었습니다.심지어 휘청거리는 이름조차도 그것이 약간의 속임수라는 것을 암시합니다.

방법 스위즐링은 선택기 A를 호출하면 실제로 구현 B를 호출하도록 매핑을 수정합니다.이것의 한 가지 용도는 폐쇄 소스 클래스의 동작을 확장하는 것입니다.

스위즐링을 사용할지 여부를 결정하는 사람이라면 누구나 자신이 하려고 하는 일에 대해 가치가 있는지 여부를 정보에 입각한 결정을 내릴 수 있도록 위험을 공식화할 수 있습니까?

예.

  • 명명 충돌: 클래스가 나중에 사용자가 추가한 메서드 이름을 포함하도록 기능을 확장하면 매우 많은 문제가 발생합니다.스위즐 방식을 현명하게 명명하여 위험을 줄입니다.

저는 이것이 정말 훌륭한 질문이라고 생각합니다. 그리고 유감스럽게도 대부분의 답변들이 실제 질문을 다루기보다는 문제를 회피하고 단순히 스와이저를 사용하지 말라고 했습니다.

지글거리는 방법을 사용하는 것은 부엌에서 날카로운 칼을 사용하는 것과 같습니다.어떤 사람들은 자신이 심하게 자를 것이라고 생각하기 때문에 날카로운 칼을 무서워하지만, 사실은 날카로운 칼이 더 안전하다는 것입니다.

메소드 스위즐은 코드를 더 잘, 더 효율적으로, 더 유지 관리 가능하게 작성하는 데 사용될 수 있습니다.그것은 또한 남용될 수 있고 끔찍한 벌레로 이어질 수 있습니다.

배경

모든 디자인 패턴과 마찬가지로, 우리가 패턴의 결과를 완전히 알고 있다면, 우리는 그것의 사용 여부에 대해 더 많은 정보에 입각한 결정을 내릴 수 있습니다.싱글톤은 꽤 논란이 되는 좋은 예입니다. 그럴만한 이유가 있습니다. 제대로 구현하기가 정말 어렵습니다.하지만 많은 사람들은 여전히 싱글톤을 사용하는 것을 선택합니다.스위즐링에 대해서도 마찬가지입니다.좋은 점과 나쁜 점을 완전히 이해하면 당신은 당신 자신의 의견을 형성해야 합니다.

논의

다음은 메소드 스위블링의 함정입니다.

  • 메서드 스위블링은 원자적이지 않습니다.
  • 소유하지 않은 코드의 동작을 변경합니다.
  • 가능한 이름 지정 충돌
  • 스위즐링은 메서드의 인수를 변경합니다.
  • 스위즐의 순서가 중요합니다.
  • 이해하기 어려움(재귀적으로 보임)
  • 디버깅하기 어려움

이러한 점은 모두 유효하며, 이를 해결함으로써 우리는 결과를 달성하는 데 사용되는 방법론뿐만 아니라 방법 스위블링에 대한 이해를 향상시킬 수 있습니다.한 번에 하나씩 가져가겠습니다.

메서드 스위블링은 원자적이지 않습니다.

동시에1 사용해도 안전한 메소드 스위즐의 구현을 아직 보지 못했습니다.이는 실제로 메소드 스위즐을 사용하려는 경우의 95%에서 문제가 되지 않습니다.일반적으로 메소드의 구현을 대체하고 해당 구현이 프로그램의 전체 수명 동안 사용되기를 원합니다.이것은 당신이 당신의 방법을 재빨리 해야 한다는 것을 의미합니다.+(void)load.load클래스 메소드는 응용 프로그램 시작 시 연속적으로 실행됩니다.여기서 스위즐링을 하면 동시성에 문제가 없을 것입니다.,면▁wereizz▁you▁다.+(void)initialize그러나, 당신은 당신의 스위즐링 구현에서 경주 상태로 끝날 수 있고 런타임은 이상한 상태로 끝날 수 있습니다.

소유하지 않은 코드의 동작을 변경합니다.

이것은 어지러운 것에 대한 문제입니다. 하지만 이것이 중요한 부분입니다.목표는 코드를 변경할 수 있는 것입니다.사람들이 이것을 중요한 것으로 지적하는 이유는 당신이 단지 하나의 사례를 위해 상황을 바꾸는 것이 아니기 때문입니다.NSButton것을 에 모든 것에 , 대에변모원두위만해를지하신를, 화이신당위해▁that▁you.NSButton응용 프로그램의 인스턴스.이러한 이유로, 여러분은 지글지글할 때 조심해야 하지만, 여러분은 그것을 완전히 피할 필요는 없습니다.

이런 식으로 생각해 보세요...클래스에서 메서드를 재정의하고 슈퍼 클래스 메서드를 호출하지 않으면 문제가 발생할 수 있습니다.대부분의 경우 슈퍼 클래스는 해당 메서드가 호출될 것으로 예상합니다(다른 문서가 없는 한).이와 같은 생각을 스위즐링에 적용하면 대부분의 문제를 해결할 수 있습니다.항상 원래 구현을 호출합니다.그렇게 하지 않는다면, 여러분은 아마도 안전하기 위해 너무 많이 변하고 있는 것입니다.

가능한 이름 지정 충돌

이름 짓기 충돌은 코코아 전역에서 이슈가 되고 있습니다.카테고리에서 클래스 이름과 메서드 이름 앞에 자주 접두사를 붙입니다.불행하게도, 이름 짓기 충돌은 우리 언어에서 역병입니다.하지만 스와이프의 경우에는 그럴 필요가 없습니다.우리는 단지 우리가 생각하는 방식을 약간 바꿀 필요가 있습니다.대부분의 스위블링은 다음과 같이 수행됩니다.

@interface NSView : NSObject
- (void)setFrame:(NSRect)frame;
@end

@implementation NSView (MyViewAdditions)

- (void)my_setFrame:(NSRect)frame {
    // do custom work
    [self my_setFrame:frame];
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}

@end

이것은 잘 작동하지만, 만약에 만약에my_setFrame:다른 곳에서 정의되었습니까?이 문제는 어지럼증에만 국한된 것은 아니지만, 어쨌든 해결할 수 있습니다.해결 방법에는 다른 문제를 해결할 수 있는 추가적인 이점도 있습니다.대신 다음을 수행합니다.

@implementation NSView (MyViewAdditions)

static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
}

@end

이것은 함수 포인터를 사용하기 때문에 Objective-C와 조금 덜 닮았지만 이름 지정 충돌을 방지합니다.원칙적으로, 그것은 표준 스위즐링과 정확히 같은 일을 합니다.이것은 한동안 정의된 대로 스위즐링을 사용해온 사람들에게는 약간의 변화일 수도 있지만, 결국에는 더 좋다고 생각합니다.스위즐링 방법은 다음과 같이 정의됩니다.

typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)
+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end

메서드 이름을 변경하여 스위즐링하면 메서드의 인수가 변경됩니다.

이것이 제 마음속에 있는 큰 것입니다.이것이 표준 방법 스위즐링을 수행해서는 안 되는 이유입니다.원래 메서드의 구현에 전달된 인수를 변경하고 있습니다.여기서 이러한 현상이 발생합니다.

[self my_setFrame:frame];

이 줄의 기능은 다음과 같습니다.

objc_msgSend(self, @selector(my_setFrame:), frame);

하여 런임을사다구검현다색의 합니다.my_setFrame:구현이 발견되면 지정된 것과 동일한 인수를 사용하여 구현을 호출합니다.은 발된구원구현다니입의 원래 입니다.setFrame:을 부르지만, 그가그부서르만지것을앞으것그로은래서▁so▁the부만르지▁but._cmd논은아닙다가 .setFrame:당연한 것처럼.ㅠㅠㅠmy_setFrame:원래 구현이 수신될 것이라고 예상하지 못한 인수와 함께 호출됩니다.이건 안 돼요.

간단한 해결책이 있습니다. 위에서 정의한 대체 스위즐링 기술을 사용하십시오.인수는 변경되지 않은 상태로 유지됩니다!

스위즐의 순서가 중요합니다.

방법이 바뀌는 순서가 중요합니다.을 가정하여setFrame:는 에만 정의됩니다.에만 됩니다.NSView다음과 같은 순서를 상상해 보십시오.

[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];

메소드가 켜지면 어떻게 됩니까?NSButton휘저어졌습니까?대부분의 스와이프는 이 솔루션이 가상화 솔루션의 구현을 대체하지 않는다는 것을 보장합니다.setFrame:인스턴스(instance) 메서드를 끌어 올립니다.기존 구현을 사용하여 다시 정의합니다.setFrame:에 시대에NSButton구현을 교환하는 것이 모든 뷰에 영향을 미치지 않도록 클래스를 지정합니다.은 존구현다정구의현다에 입니다.NSView스와이프를 계속할 때도 동일한 현상이 발생합니다.NSControl (으)를 합니다.)NSView구현).

에 전화할 때.setFrame:버튼에서, 그러므로 그것은 당신의 스위즐 방식을 부르고, 그리고 나서 바로 점프할 것입니다.setFrame:된 방법은 래정의방법에 있습니다.NSView.NSControl그리고.NSView스위즈된 구현은 호출되지 않습니다.

하지만 명령이 다음과 같다면요.

[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

뷰 스위블링이 먼저 발생하기 때문에 컨트롤 스위블링은 올바른 방법을 끌어 올릴 수 있습니다.마찬가지로, 컨트롤 스위블링은 버튼이 스위블링되기 전이었기 때문에 버튼은 컨트롤의 스위블링된 구현을 끌어올립니다.setFrame:이것은 조금 헷갈리지만, 이것이 정확한 순서입니다.우리는 어떻게 이 질서를 보장할 수 있습니까?

다시 말하지만, 그냥 사용하세요.load일을 뒤죽박죽으로 만드는 것들을.,면▁in▁youizz▁sw.load로드되는 클래스만 변경하면 안전합니다.loadmethod는 슈퍼 클래스 로드 메서드가 하위 클래스보다 먼저 호출된다는 것을 보장합니다.우리는 정확한 주문을 받을 것입니다!

이해하기 어려움(재귀적으로 보임)

전통적으로 정의된 스위즐 방식을 보면, 무슨 일이 일어나고 있는지 구분하기가 정말 어려운 것 같습니다.하지만 위에서 우리가 했던 다른 방법을 보면, 그것은 꽤 이해하기 쉽습니다.이건 이미 해결됐어요!

디버깅하기 어려움

디버깅을 하는 동안 혼란스러운 점 중 하나는 이름이 뒤섞여 머릿속에서 모든 것이 뒤죽박죽이 되는 이상한 역추적을 보는 것입니다.다시 말하지만, 대안적인 구현은 이 문제를 해결합니다.역추적에서 명명된 함수를 볼 수 있습니다.그러나 스위즐링이 어떤 영향을 미치는지 기억하기 어렵기 때문에 스위즐링을 디버깅하는 것은 어려울 수 있습니다.코드를 잘 문서화합니다(자신만이 코드를 볼 수 있다고 생각하더라도).모범 사례를 따르면 괜찮을 것입니다.멀티 스레드 코드보다 디버깅하는 것이 어렵지 않습니다.

결론

방법 스위즐링은 적절히 사용하면 안전합니다.당신이 취할 수 있는 간단한 안전 조치는 단지 재빨리 들어가는 것입니다.load프로그래밍의 많은 것들처럼, 그것은 위험할 수 있지만, 그 결과를 이해하는 것은 당신이 그것을 적절하게 사용할 수 있게 해줄 것입니다.


1 위에서 정의한 스위즐링 방법을 사용하여 트램폴린을 사용할 경우 안전하게 스레드를 만들 수 있습니다.트램폴린 두 개가 필요합니다.메소드를 시작할 때 함수 포인터를 할당해야 합니다.store주까지회함수로한전이 있는 store변경됨을 가리켰습니다.이렇게 하면 스위즐 방식을 설정하기 전에 호출된 레이스 조건을 피할 수 있습니다.store 되어 있지 하고 슈퍼 해야 합니다.그런 다음 클래스에서 구현이 아직 정의되지 않은 경우 트램펄린을 사용하고 트램펄린을 검색하여 슈퍼 클래스 메소드를 올바르게 호출해야 합니다.슈퍼 구현을 동적으로 검색하도록 메소드를 정의하면 스와이저 호출 순서가 중요하지 않습니다.

먼저 제가 의미하는 메소드 스위즐의 정확한 의미를 정의하겠습니다.

  • 원래 메서드(A)로 전송된 모든 호출을 새 메서드(B)로 재라우팅합니다.
  • 우리는 메소드 B를 소유합니다.
  • 우리는 A 방법을 가지고 있지 않습니다.
  • 메소드 B는 일부 작업을 수행한 다음 메소드 A를 호출합니다.

메소드 스위블링은 이보다 더 일반적이지만, 제가 관심 있는 경우입니다.

위험:

  • 원래 클래스의 변경 내용입니다.우리는 우리가 휘젓고 있는 클래스를 소유하지 않습니다.수업이 바뀌면 스위즐이 작동을 멈출 수 있습니다.

  • 유지 관리가 어렵습니다.당신은 혼란스러운 방법을 쓰고 유지해야 할 뿐만 아니라,당신은 스위즐을 형성하는 코드를 작성하고 유지해야 합니다.

  • 디버깅하기 어렵습니다.스위즐의 흐름을 따르는 것은 어렵습니다. 일부 사람들은 스위즐이 실행되었음을 깨닫지 못할 수도 있습니다.스위즐에서 유입된 버그가 있으면(아마도 원래 클래스의 변화에 따른 것일 수 있음) 해결하기 어려울 것입니다.

요약하자면, 여러분은 최소한으로 스와이저를 계속하고 원래 클래스의 변경이 여러분의 스와이저에 어떤 영향을 미칠지 고려해야 합니다.또한 자신이 하고 있는 일에 대해 명확하게 의견을 제시하고 문서화해야 합니다(또는 완전히 회피해야 합니다).

정말로 위험한 것은 윙윙거리는 그 자체가 아닙니다.문제는 말씀하신 것처럼 프레임워크 클래스의 동작을 수정하는 데 자주 사용된다는 것입니다.그것은 당신이 그 사설 수업들이 어떻게 작동하는지에 대해 "위험"하다는 것을 알고 있다고 가정합니다.오늘 수정사항이 적용되더라도 Apple이 향후 클래스를 변경하여 수정사항이 중단될 가능성은 항상 있습니다.또한, 만약 많은 다른 앱들이 그것을 한다면, 애플은 기존의 많은 소프트웨어를 깨지 않고 프레임워크를 바꾸는 것을 훨씬 더 어렵게 만듭니다.

신중하고 현명하게 사용하면 우아한 코드로 이어질 수 있지만, 일반적으로 혼란스러운 코드로 이어질 뿐입니다.

저는 그것이 특정 설계 작업에 매우 우아한 기회를 제공한다는 것을 알지 못하는 한, 그것은 금지되어야 한다고 말합니다. 하지만 당신은 그것이 왜 상황에 잘 적용되는지, 그리고 왜 대안이 상황에 우아하게 작용하지 않는지 명확하게 알아야 합니다.

예, 방법 스위즐링의 좋은 응용 프로그램 중 하나는 ObjC가 키 값 관찰을 구현하는 방법인 스위즐링입니다.

클래스를 확장하는 수단으로 메소드 스위즐을 사용하는 것이 좋지 않은 예일 수 있으며, 이는 매우 높은 결합을 초래합니다.

제가 이 기술을 사용했지만, 다음과 같은 점을 지적하고 싶습니다.

  • 문서화되지 않은 부작용을 일으킬 수 있기 때문에 코드를 난독화합니다.코드를 읽을 때 코드 베이스가 스와이프되었는지 검색하는 것을 잊지 않는 한 필요한 부작용 동작을 모를 수 있습니다.이 문제를 어떻게 완화해야 할지 잘 모르겠습니다. 왜냐하면 코드가 부작용에 의존하는 모든 장소를 문서화하는 것이 항상 가능하지는 않기 때문입니다.
  • 다른 곳에서 사용하고자 하는 스위즐 동작에 의존하는 코드 세그먼트를 찾는 사람은 스위즐 방식을 찾아서 복사하지 않고 다른 코드 베이스에 단순히 잘라 붙여넣을 수 없기 때문에 코드 재사용이 줄어듭니다.

저는 가장 큰 위험은 완전히 우연히 원치 않는 부작용을 일으키는 것이라고 생각합니다.이러한 부작용은 스스로를 '버그'로 표현할 수 있으며, 이는 결국 해결책을 찾기 위한 잘못된 길로 인도합니다.제 경험상, 위험은 읽기 어렵고 혼란스럽고 좌절감을 주는 코드입니다.마치 누군가가 C++의 함수 포인터를 과도하게 사용할 때와 같습니다.

당신은 다음과 같은 이상한 코드로 끝날 수 있습니다.

- (void)addSubview:(UIView *)view atIndex:(NSInteger)index {
    //this looks like an infinite loop but we're swizzling so default will get called
    [self addSubview:view atIndex:index];

일부 UI 마법과 관련된 실제 생산 코드에서.

방법 스위즐링은 단위 테스트에 매우 유용할 수 있습니다.

그것은 여러분이 모의 물체를 쓸 수 있게 해주고 실제 물체 대신 그 모의 물체를 사용하게 해줍니다.코드를 깨끗하게 유지하고 장치 테스트에 예측 가능한 동작이 있습니다.CLLocationManager를 사용하는 일부 코드를 테스트하려고 합니다.장치 테스트에서 startUpdatingLocation을 스위즐하여 사전 지정된 위치 집합을 대리자에게 제공하고 코드를 변경할 필요가 없습니다.

언급URL : https://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c

반응형