Каковы опасности метода Swizzling в Objective C?

Я слышал, что люди утверждают, что метод swizzling является опасной практикой. Даже название swizzling sugests, что это немного обман.

Метод Swizzling изменяет сопоставление так, что вызов селектора a фактически вызовет реализацию B. одно использование этого-расширить поведение классов с закрытым исходным кодом.

можем ли мы формализовать риски, чтобы любой, кто решает, использовать ли swizzling, мог принять обоснованное решение, является ли это стоит того, что они пытаются сделать.

Э. Г.

  • Конфликты Имен: если класс позже расширит свою функциональность, чтобы включить имя метода, которое вы добавили,это вызовет огромные проблемы. Уменьшите риск, разумно называя swizzled методы.

8 ответов


я думаю, что это действительно отличный вопрос, и жаль, что вместо решения реального вопроса большинство ответов обошли проблему и просто сказали не использовать swizzling.

использование метода шипения похоже на использование острых ножей на кухне. Некоторые люди боятся острых ножей, потому что думают, что сильно порежутся, но правда в том, что острые ножи безопаснее.

группирующее метод может быть использован для написания более эффективный, более обслуживаемый код. Он также может быть злоупотреблен и привести к ужасным ошибкам.

фон

как и все шаблоны проектирования, если мы полностью осознаем последствия шаблона, мы можем принимать более обоснованные решения о том, использовать его или нет. Синглеты-хороший пример того, что довольно спорно, и по уважительной причине - их действительно трудно реализовать должным образом. Однако многие люди по-прежнему предпочитают использовать синглеты. То же самое может можно сказать, о выпивке. Вы должны сформировать свое собственное мнение, как только полностью поймете как хорошее, так и плохое.

Обсуждение

вот некоторые из ловушек метода swizzling:

  • метод swizzling не является атомарным
  • изменяет поведение собственного кода
  • возможные конфликты имен
  • Swizzling изменяет аргументы метода
  • порядок swizzles дела
  • трудно понять (выглядит рекурсивный)
  • трудно отлаживать

все эти моменты справедливы, и при их решении мы можем улучшить как наше понимание метода swizzling, так и методологию, используемую для достижения результата. Я возьму по одному.

метод swizzling не является атомарным

мне еще предстоит увидеть реализацию метода swizzling, который безопасен в использовании одновременно1. На самом деле это не проблема в 95% случаев, когда вы хотите использовать метод swizzling. Обычно вы просто хотите заменить реализацию метода, и вы хотите, чтобы эта реализация использовалась в течение всего срока службы вашей программы. Это означает, что вы должны сделать свой метод swizzling в +(void)load. The load метод класса выполняется последовательно в начале вашего приложения. У вас не будет никаких проблем с параллелизмом, если вы сделаете свое swizzling здесь. Если вы должны были выпить в +(void)initialize, однако, вы можете закончить с условием гонки в вашей реализации swizzling, и среда выполнения может оказаться в странном состоянии.

изменяет поведение собственного кода

это проблема с swizzling, но это своего рода весь смысл. Цель состоит в том, чтобы иметь возможность изменить этот код. Причина, по которой люди указывают на это как на большое дело, заключается в том, что вы не просто меняете вещи для одного экземпляра NSButton что вы хотите все изменить, но вместо всех NSButton экземпляры в вашем приложении. По этой причине, вы должны быть осторожны, когда вы swizzle, но вам не нужно избегать его полностью.

подумайте об этом таким образом... если переопределить метод в классе и не вызвать метод суперкласса, могут возникнуть проблемы. В большинстве случаев суперкласс ожидает, что этот метод будет вызван (если не задокументировано иначе). Если вы примените эту же мысль к swizzling, вы покрыли большинство вопросов. Всегда называйте оригинальную реализацию. Если вы этого не сделаете, вы, вероятно, слишком сильно изменитесь, чтобы быть в безопасности.

возможные конфликты имен

конфликты имен являются проблемой во всем какао. Мы часто префиксные имена классов и имена методов в категориях. К сожалению, конфликты имен-это чума нашего языка. Но в случае со спиртным они не должны быть такими. Нам просто нужно немного изменить то, как мы думаем о методе swizzling. Наиболее swizzling делается так:

@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: было определено где-то еще? Эта проблема не уникальна для swizzling, но мы можем обойти ее в любом случае. Обходной путь имеет дополнительное преимущество решения других подводных камней, а также. Вот что мы делаем вместо этого:

@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 (поскольку он использует указатели функций), это позволяет избежать конфликтов именования. В принципе, это делает то же самое, что и обычное пойло. Это может немного измениться для людей, которые используют swizzling, как это было определено некоторое время, но в конце концов, я думаю, что это лучше. Метод swizzling определяется таким образом:

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

Swizzling путем переименования методов изменяет аргументы метода

это большой в моем уме. Именно по этой причине стандартный метод swizzling не должен выполняться. Вы меняете аргументы к оригиналу реализация метода. Вот где это происходит:

[self my_setFrame:frame];

что делает эта строка:

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

который будет использовать среду выполнения для поиска реализации my_setFrame:. После реализации он вызывает реализацию с теми же аргументами, которые были приведены. Реализации он находит оригинальное воплощение setFrame:, поэтому он идет вперед и называет это, но


сначала я точно определю, что я имею в виду под методом swizzling:

  • перенаправление всех вызовов, которые были первоначально отправлены в метод (называемый A) в новый метод (называемый B).
  • у нас есть метод B
  • мы не владеем методом
  • метод B выполняет некоторую работу, затем вызывает метод A.

метод swizzling более общий, чем это, но это тот случай, который меня интересует в.

опасности:

  • изменения в исходном классе. Мы не владеем классом, который мы обжигаем. Если класс изменит наш swizzle может перестать работать.

  • трудно поддерживать. Мало того, что вы должны писать и поддерживать swizzled метод. вы должны написать и поддерживать код, который преформирует swizzle

  • трудно отладить. Трудно следовать поток swizzle, некоторые люди могут даже не осознавать, что swizzle был предварительно сформирован. Если есть ошибки, введенные из swizzle (возможно, взносы на изменения в исходном классе), их будет трудно решить.

В общем, вы должны держать swizzling до минимума и рассмотреть, как изменения в исходном классе могут повлиять на ваш swizzle. Также вы должны четко комментировать и документировать то, что вы делаете (или только совсем избежать его).


Это не само по себе обжигание, что действительно опасно. Проблема, как вы говорите, в том, что он часто используется для изменения поведения классов framework. Предполагается, что вы знаете что-то о том, как работают эти частные классы, что "опасно"."Даже если ваши модификации работают сегодня, всегда есть шанс, что Apple изменит класс в будущем и заставит вашу модификацию сломаться. Кроме того, если это делают многие различные приложения, Apple будет намного сложнее изменить framework, не нарушая много существующего программного обеспечения.


используется тщательно и мудро, это может привести к элегантному коду, но обычно это просто приводит к путанице кода.

Я говорю, что он должен быть запрещен, если вы случайно не знаете, что он представляет собой очень элегантную возможность для конкретной задачи проектирования, но вам нужно четко знать, почему это хорошо относится к ситуации, и почему альтернативы не работают элегантно для ситуации.

например, одно хорошее применение метода swizzling ISA swizzling, которое как objc реализует Ключевое Значение Наблюдения.

плохой пример может полагаться на метод swizzling как средство расширения ваших классов, что приводит к чрезвычайно высокой связи.


хотя я использовал эту технику, я хотел бы отметить, что:

  • он запутывает ваш код, потому что он может вызвать не документированные, хотя и желаемые побочные эффекты. Когда кто-то читает код, он может не знать о побочном эффекте, который требуется, если он не помнит, чтобы искать базу кода, чтобы увидеть, если он был swizzled. Я не уверен, как решить эту проблему, потому что не всегда можно документировать каждое место, где код зависит на побочный эффект свистнуло поведение.
  • Это может сделать ваш код менее многоразовым, потому что кто-то, кто находит сегмент кода, который зависит от swizzled поведения, которое они хотели бы использовать в другом месте, не может просто вырезать и вставить его в какую-то другую базу кода, не найдя и не скопировав swizzled метод.

Я чувствую, что самая большая опасность заключается в создании многих нежелательных побочных эффектов, совершенно случайно. Эти побочные эффекты могут представлять собой "ошибки", которые, в свою очередь, ведут вас по неправильному пути, чтобы найти решение. По моему опыту, опасность-это неразборчивый, запутанный и разочаровывающий код. Как будто кто-то злоупотребляет указателями функций в 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];

с реальным производством код, связанные с некоторые ИП магия.


метод swizzling может быть очень полезен в модульном тестировании.

Это позволяет написать макет объекта и использовать этот макет объекта вместо реального объекта. Код должен оставаться чистым, а модульный тест-предсказуемым. Предположим, вы хотите протестировать код, который использует CLLocationManager. Ваш модульный тест может swizzle startUpdatingLocation так, что он будет подавать заданный набор местоположений вашему делегату, и вашему коду не придется изменение.