Я слышал, что люди утверждают, что метод метания - опасная практика. Даже название, извилистое, говорит о том, что это немного обман.
Метод Swizzling модифицирует отображение так, что вызов селектора A фактически вызовет реализацию B. Одним из применений этого является расширение поведения классов с закрытым исходным кодом.
Можем ли мы формализовать риски, чтобы каждый, кто решает, использовать ли Swizzling, мог принять осознанное решение, стоит ли оно того, что он пытается сделать.
Например
- Столкновения именования : если впоследствии класс расширит свою функциональность, добавив имя метода, которое вы добавили, это вызовет множество проблем. Уменьшите риск, разумно назвав изжогенные методы.
ios
objective-c
swizzling
Роберт
источник
источник
Ответы:
Я думаю, что это действительно отличный вопрос, и обидно, что вместо того, чтобы заняться реальным вопросом, большинство ответов обходят проблему и просто говорят, что не следует использовать мошенничество.
Использование метода шипения похоже на использование острых ножей на кухне. Некоторые люди боятся острых ножей, потому что думают, что сильно порежутся, но правда в том, что острые ножи безопаснее .
Метод Swizzling может быть использован для написания лучшего, более эффективного, более удобного в обслуживании кода. Им также можно злоупотреблять и приводить к ужасным ошибкам.
Задний план
Как и во всех шаблонах проектирования, если мы полностью осознаем последствия этого шаблона, мы можем принимать более обоснованные решения о том, использовать его или нет. Одиночки хороший пример того, что это довольно спорный, и не зря - они на самом деле трудно правильно реализовать. Однако многие люди все еще предпочитают использовать синглтоны. То же самое можно сказать и о метании. Вы должны составить собственное мнение, как только вы полностью поймете как хорошее, так и плохое.
обсуждение
Вот некоторые из ловушек метода swizzling:
Все эти пункты верны, и при их рассмотрении мы можем улучшить как наше понимание метода метания, так и методологию, используемую для достижения результата. Я возьму каждый по одному.
Метод метания не атомарный
Мне еще предстоит увидеть реализацию метода Swizzling, который безопасно использовать одновременно 1 . На самом деле это не проблема в 95% случаев, когда вы хотите использовать метод Swizzling. Обычно вы просто хотите заменить реализацию метода, и вы хотите, чтобы эта реализация использовалась в течение всего времени жизни вашей программы. Это означает, что вы должны сделать свой метод вхолостую
+(void)load
. Методload
класса выполняется последовательно в начале вашего приложения. У вас не возникнет проблем с параллелизмом, если вы будете здесь заниматься. Однако, если бы вы вступили в игру+(void)initialize
, вы могли бы столкнуться с состоянием гонки в своей реализации, и среда выполнения могла бы оказаться в странном состоянии.Изменяет поведение чужого кода
Это проблема со свистом, но в этом вся суть. Цель состоит в том, чтобы иметь возможность изменить этот код. Причина, по которой люди указывают на это как на большую проблему, заключается в том, что вы не просто меняете вещи для одного экземпляра, для
NSButton
которого вы хотите изменить вещи, но вместо этого для всехNSButton
экземпляров в вашем приложении. По этой причине вам следует быть осторожным, когда вы пьянствуете, но вам не нужно вообще этого избегать.Подумайте об этом следующим образом ... если вы переопределяете метод в классе и не вызываете метод суперкласса, это может вызвать проблемы. В большинстве случаев суперкласс ожидает, что этот метод будет вызван (если не указано иное). Если вы примените эту же мысль к простуде, вы рассмотрели большинство вопросов. Всегда вызывайте оригинальную реализацию. Если вы этого не сделаете, вы, вероятно, слишком изменились, чтобы быть в безопасности.
Возможные конфликты имен
Конфликты имен являются проблемой во всем Какао. Мы часто добавляем имена классов и методов в категории. К сожалению, конфликты имен - это чума на нашем языке. Однако в случае пьянства они не должны быть такими. Нам просто нужно немного изменить способ, которым мы думаем о методе. Большинство мерзлоты делается так:
Это работает просто отлично, но что произойдет, если
my_setFrame:
будет определено где-то еще? Эта проблема не является уникальной для мерзлоты, но мы все равно можем обойти ее. Обходной путь имеет дополнительное преимущество, устраняя и другие подводные камни. Вот что мы делаем вместо этого:Хотя это выглядит немного менее как Objective-C (поскольку он использует указатели функций), он избегает любых конфликтов имен. В принципе, он делает то же самое, что и стандартное пьянство. Это может быть небольшим изменением для людей, которые использовали Swizzling, как это было определено некоторое время, но в конце я думаю, что это лучше. Метод Swizzling определяется следующим образом:
Swizzling путем переименования методов изменяет аргументы метода
Это большой в моей голове. Это причина того, что стандартный метод Swizzling не должно быть сделано. Вы изменяете аргументы, переданные в реализацию исходного метода. Вот где это происходит:
Что делает эта строка:
Который будет использовать среду выполнения для поиска реализации
my_setFrame:
. Как только реализация найдена, она вызывает реализацию с теми же аргументами, которые были даны. Реализация, которую он находит, является оригинальной реализациейsetFrame:
, поэтому он продолжает и вызывает это, но_cmd
аргумент неsetFrame:
такой, каким он должен быть. Это сейчасmy_setFrame:
. Исходная реализация вызывается с аргументом, который он никогда не ожидал получить. Это не хорошо.Есть простое решение - использовать альтернативную технику пьянства, описанную выше. Аргументы останутся без изменений!
Порядок алкогольных напитков имеет значение
Порядок, в котором используются методы, имеет большое значение. Предполагая, что
setFrame:
это только определеноNSView
, представьте себе такой порядок вещей:Что происходит, когда метод
NSButton
включен? Ну большинство swizzling будет гарантировать , что он не заменяет реализациюsetFrame:
для всех представлений, поэтому он будет тянуть метод экземпляра. Это позволит использовать существующую реализацию для переопределенияsetFrame:
вNSButton
классе, чтобы обмен реализациями не влиял на все представления. Существующая реализация определена наNSView
. То же самое произойдет при включенииNSControl
(снова с использованиемNSView
реализации).Когда вы вызываете
setFrame:
кнопку, она вызывает ваш метод swizzled, а затем сразу переходит кsetFrame:
методу, изначально определенному дляNSView
. РеализацииNSControl
иNSView
swizzled не будут вызваны.Но что, если бы порядок был:
Поскольку вид swizzling происходит первый, контроль swizzling сможет подтянуть правильный метод. Кроме того, поскольку управление swizzling было до кнопки swizzling, кнопка будет тянуть swizzled реализацию элемента управления из
setFrame:
. Это немного сбивает с толку, но это правильный порядок. Как мы можем обеспечить этот порядок вещей?Опять же, просто используйте,
load
чтобы взбалтывать вещи. Если вы подключитесьload
и внесете изменения только в загружаемый класс, вы будете в безопасности. Вload
методе гарантирует , что метод супер нагрузки класса будет вызываться до подклассов. Мы получим точный правильный заказ!Трудно понять (выглядит рекурсивно)
Глядя на традиционно определяемый метод, я думаю, что действительно трудно сказать, что происходит. Но, глядя на альтернативный способ, которым мы проделали пьянство выше, это довольно легко понять. Это уже решено!
Сложно отлаживать
Одно из недоразумений во время отладки заключается в том, что вы видите странный след, в котором смешанные имена перепутаны и все перемешано в вашей голове. Опять же, альтернативная реализация решает эту проблему. Вы увидите четко названные функции в следах. Тем не менее, отравление может быть трудным для отладки, потому что трудно вспомнить, какое влияние оказывает воспаление. Хорошо документируйте свой код (даже если вы думаете, что вы единственный, кто когда-либо его увидит). Следуйте хорошей практике, и все будет в порядке. Отладка не сложнее, чем многопоточный код.
Вывод
Метод Swizzling безопасен при правильном использовании. Простая мера безопасности, которую вы можете предпринять, состоит в том, чтобы только влететь
load
. Как и многие вещи в программировании, это может быть опасно, но понимание последствий позволит вам использовать его правильно.1 Используя описанный выше метод Swizzling, вы можете сделать вещи безопасными, если вы будете использовать батуты. Вам понадобятся два батута. В начале метода вам нужно будет присвоить указатель
store
функции функции, которая вращается до тех пор, пока адрес, на которыйstore
указывает указатель, не изменится. Это позволит избежать любого состояния гонки, при котором метод swizzled вызывался до того, как вы смогли установитьstore
указатель на функцию. Затем вам нужно будет использовать батут в случае, когда реализация еще не определена в классе, и вам нужно найти батут и правильно вызвать метод суперкласса. Определяя метод так, чтобы он динамически искал, супер реализация гарантирует, что порядок быстрых вызовов не имеет значения.источник
Сначала я определю, что именно я подразумеваю под методом swizzling:
Метод Swizzling является более общим, чем это, но это тот случай, который меня интересует.
Опасности:
Изменения в оригинальном классе . Нам не принадлежит класс, который мы извергаем. Если класс изменится, наш алкогольный напиток может перестать работать.
Трудно поддерживать . Мало того, что вы должны написать и поддерживать метод Swizzled. Вы должны написать и поддерживать код, который преобразует Swizzle
Трудно отлаживать . Трудно проследить за течением изгиба, некоторые люди могут даже не осознавать, что это было уже сформировано. Если есть ошибки, допущенные в Swizzle (возможно, из-за изменений в исходном классе), их будет трудно устранить.
Таким образом, вы должны продолжать свистеть до минимума и подумать, как изменения в исходном классе могут повлиять на вашу пьянство Также вы должны четко прокомментировать и задокументировать, что вы делаете (или просто избегать этого).
источник
Это не само по себе опасно. Проблема, как вы говорите, в том, что он часто используется для изменения поведения классов инфраструктуры. Предполагается, что вы знаете кое-что о том, как работают эти частные классы, что «опасно». Даже если ваши модификации работают сегодня, всегда есть шанс, что Apple изменит класс в будущем и приведет к тому, что ваша модификация сломается. Кроме того, если это делают многие разные приложения, Apple значительно усложняет изменение инфраструктуры, не нарушая существующего программного обеспечения.
источник
Если использовать его осторожно и с умом, это может привести к созданию элегантного кода, но обычно это приводит к запутанному коду.
Я говорю, что он должен быть запрещен, если только вы не знаете, что он представляет собой очень элегантную возможность для конкретной задачи проектирования, но вам нужно четко знать, почему это хорошо подходит к ситуации, и почему альтернативы не работают элегантно для ситуации ,
Например, одно хорошее применение метода swizzling - это swizzling, то есть, как ObjC реализует наблюдение за ключевыми значениями.
Плохим примером может быть использование метода swizzling как средства расширения ваших классов, что приводит к чрезвычайно высокой связи.
источник
Хотя я использовал эту технику, я хотел бы отметить, что:
источник
Я чувствую, что самая большая опасность заключается в создании многих нежелательных побочных эффектов, совершенно случайно. Эти побочные эффекты могут представлять собой «ошибки», которые, в свою очередь, ведут вас по неверному пути, чтобы найти решение. По моему опыту, опасность неразборчивого, запутанного и расстраивающего кода. Вроде как, когда кто-то злоупотребляет указателями на функции в C ++.
источник
Вы можете получить странно выглядящий код
из реального производственного кода, связанного с магией пользовательского интерфейса.
источник
Метод Swizzling может быть очень полезным в модульном тестировании.
Это позволяет вам писать фиктивный объект и использовать этот фиктивный объект вместо реального объекта. Ваш код остается чистым, а ваш модульный тест - предсказуемым. Допустим, вы хотите протестировать некоторый код, который использует CLLocationManager. Ваш модульный тест может быстро запустить startUpdatingLocation, чтобы он передавал предопределенный набор местоположений вашему делегату, и ваш код не должен был изменяться.
источник