Ich habe Leute sagen hören, dass Methoden-Swizzling eine gefährliche Praxis ist. Sogar der Name Swizzling deutet darauf hin, dass es sich um einen Betrüger handelt.
Methode Swizzling ändert das Mapping so, dass der Aufruf von Selektor A tatsächlich die Implementierung B aufruft. Eine Verwendung davon besteht darin, das Verhalten von Closed-Source-Klassen zu erweitern.
Können wir die Risiken formalisieren, damit jeder, der sich für die Verwendung von Swizzling entscheidet, eine fundierte Entscheidung treffen kann, ob es sich für das, was er versucht, lohnt?
Z.B
- Namenskollisionen : Wenn die Klasse später ihre Funktionalität um den von Ihnen hinzugefügten Methodennamen erweitert, führt dies zu einer Vielzahl von Problemen. Reduzieren Sie das Risiko, indem Sie Swizzled-Methoden sinnvoll benennen.
ios
objective-c
swizzling
Robert
quelle
quelle
Antworten:
Ich denke, dies ist eine wirklich großartige Frage, und es ist eine Schande, dass die meisten Antworten das Problem umgangen haben und einfach gesagt haben, dass sie kein Swizzling verwenden sollen, anstatt die eigentliche Frage anzugehen.
Mit der Methode Brutzeln ist wie mit scharfen Messern in der Küche. Einige Menschen haben Angst vor scharfen Messern, weil sie denken, dass sie sich schlecht schneiden werden, aber die Wahrheit ist, dass scharfe Messer sicherer sind .
Das Methoden-Swizzling kann verwendet werden, um besseren, effizienteren und wartbareren Code zu schreiben. Es kann auch missbraucht werden und zu schrecklichen Fehlern führen.
Hintergrund
Wie bei allen Entwurfsmustern können wir fundiertere Entscheidungen darüber treffen, ob wir das Muster verwenden oder nicht, wenn wir uns der Konsequenzen des Musters voll bewusst sind. Singletons sind ein gutes Beispiel für etwas, das ziemlich kontrovers ist, und das aus gutem Grund - sie sind wirklich schwer richtig zu implementieren. Viele Menschen entscheiden sich jedoch immer noch für Singletons. Gleiches gilt für das Swizzling. Sie sollten sich eine eigene Meinung bilden, wenn Sie sowohl das Gute als auch das Schlechte vollständig verstanden haben.
Diskussion
Hier sind einige der Fallstricke des Methoden-Swizzling:
Diese Punkte sind alle gültig, und wenn wir sie ansprechen, können wir sowohl unser Verständnis des Methoden-Swizzling als auch die Methodik, mit der das Ergebnis erzielt wird, verbessern. Ich werde jeden nach dem anderen nehmen.
Methoden-Swizzling ist nicht atomar
Ich habe noch keine Implementierung von Methoden-Swizzling gesehen, die sicher gleichzeitig verwendet werden kann 1 . Dies ist in 95% der Fälle, in denen Sie das Methoden-Swizzling verwenden möchten, eigentlich kein Problem. Normalerweise möchten Sie einfach die Implementierung einer Methode ersetzen und möchten, dass diese Implementierung für die gesamte Lebensdauer Ihres Programms verwendet wird. Dies bedeutet, dass Sie Ihre Methode einschalten sollten
+(void)load
. Dieload
Klassenmethode wird zu Beginn Ihrer Anwendung seriell ausgeführt. Sie werden keine Probleme mit der Parallelität haben, wenn Sie hier Ihre Swizzling machen. Wenn Sie jedoch einschalten+(void)initialize
, könnte dies zu einer Racebedingung in Ihrer Swizzling-Implementierung führen, und die Laufzeit könnte in einem seltsamen Zustand enden.Ändert das Verhalten von nicht im Besitz befindlichem Code
Dies ist ein Problem mit dem Swizzling, aber es ist der springende Punkt. Das Ziel ist es, diesen Code ändern zu können. Der Grund, warum die Leute darauf hinweisen, ist, dass Sie nicht nur Dinge für die eine Instanz ändern, für die
NSButton
Sie Dinge ändern möchten, sondern für alleNSButton
Instanzen in Ihrer Anwendung. Aus diesem Grund sollten Sie beim Swizzeln vorsichtig sein, aber Sie müssen es nicht ganz vermeiden.Stellen Sie sich das so vor ... Wenn Sie eine Methode in einer Klasse überschreiben und die Superklassenmethode nicht aufrufen, können Probleme auftreten. In den meisten Fällen erwartet die Superklasse, dass diese Methode aufgerufen wird (sofern nicht anders dokumentiert). Wenn Sie denselben Gedanken auf das Swizzling anwenden, haben Sie die meisten Probleme behandelt. Rufen Sie immer die ursprüngliche Implementierung auf. Wenn Sie dies nicht tun, ändern Sie wahrscheinlich zu viel, um sicher zu sein.
Mögliche Namenskonflikte
Namenskonflikte sind in ganz Cocoa ein Thema. Wir stellen häufig Klassennamen und Methodennamen in Kategorien voran. Namenskonflikte sind in unserer Sprache leider eine Plage. Im Falle von Swizzling müssen sie es jedoch nicht sein. Wir müssen nur die Art und Weise ändern, wie wir über Methoden denken, die leicht schwanken. Das meiste Swizzling wird so gemacht:
Das funktioniert gut, aber was würde passieren, wenn
my_setFrame:
es woanders definiert würde ? Dieses Problem betrifft nicht nur das Swizzling, aber wir können es trotzdem umgehen. Die Problemumgehung hat den zusätzlichen Vorteil, dass auch andere Fallstricke behoben werden. Folgendes tun wir stattdessen:Dies ähnelt zwar etwas weniger Objective-C (da es Funktionszeiger verwendet), vermeidet jedoch Namenskonflikte. Im Prinzip macht es genau das Gleiche wie Standard-Swizzling. Dies mag für Leute, die Swizzling verwenden, wie es seit einiger Zeit definiert ist, eine kleine Veränderung sein, aber am Ende denke ich, dass es besser ist. Die Swizzling-Methode ist folgendermaßen definiert:
Durch Umbenennen von Methoden werden die Argumente der Methode geändert
Dies ist der große in meinem Kopf. Dies ist der Grund, warum das Swizzling mit Standardmethoden nicht durchgeführt werden sollte. Sie ändern die an die Implementierung der ursprünglichen Methode übergebenen Argumente. Hier passiert es:
Was diese Zeile tut, ist:
Welches wird die Laufzeit verwenden, um die Implementierung von nachzuschlagen
my_setFrame:
. Sobald die Implementierung gefunden wurde, ruft sie die Implementierung mit denselben Argumenten auf, die angegeben wurden. Die Implementierung, die es findet, ist die ursprüngliche Implementierung vonsetFrame:
, also geht es weiter und nennt das, aber das_cmd
Argument ist nicht so,setFrame:
wie es sein sollte. Es ist jetztmy_setFrame:
. Die ursprüngliche Implementierung wird mit einem Argument aufgerufen, das sie niemals erwartet hätte. Das ist nicht gut.Es gibt eine einfache Lösung - verwenden Sie die oben definierte alternative Swizzling-Technik. Die Argumente bleiben unverändert!
Die Reihenfolge der Swizzles ist wichtig
Die Reihenfolge, in der Methoden durcheinander gebracht werden, spielt eine Rolle. Angenommen,
setFrame:
nur definiert ist aufNSView
, stellen Sie sich diese Reihenfolge der Dinge vor:Was passiert, wenn die Methode
NSButton
aktiviert ist? Nun die meisten Swizzling werden dafür sorgen , dass es nicht die Umsetzung des ErsatzsetFrame:
für alle Ansichten, so wird es nach oben zieht die Instanz - Methode. Dadurch wird die vorhandene Implementierung verwendet, umsetFrame:
dieNSButton
Klasse neu zu definieren, sodass der Austausch von Implementierungen nicht alle Ansichten betrifft. Die vorhandene Implementierung ist die am definierteNSView
. Das gleiche passiert beim SwizzlingNSControl
(wieder mit derNSView
Implementierung).Wenn Sie
setFrame:
eine Schaltfläche aufrufen , wird daher Ihre Swizzled-Methode aufgerufen, und Sie springen direkt zu dersetFrame:
ursprünglich definierten MethodeNSView
. DieNSControl
undNSView
swizzled Implementierungen werden nicht aufgerufen.Aber was wäre, wenn die Reihenfolge wäre:
Da die Ansicht Swizzling Platz zuerst stattfindet, wird die Steuer Swizzling der Lage sein, nach oben zu ziehen , die richtigen Methode. Ebenso , da die Steuer Swizzling vor dem Button Swizzling war, wird der Knopf nach oben zieht die umgestellte Umsetzung der Kontrolle
setFrame:
. Das ist etwas verwirrend, aber dies ist die richtige Reihenfolge. Wie können wir diese Ordnung sicherstellen?Wieder nur verwenden
load
, um Dinge zu swizzeln. Wenn Sie einschaltenload
und nur Änderungen an der zu ladenden Klasse vornehmen, sind Sie in Sicherheit. Dieload
Methode garantiert, dass die Lademethode der Superklasse vor allen Unterklassen aufgerufen wird. Wir bekommen genau die richtige Bestellung!Schwer zu verstehen (sieht rekursiv aus)
Wenn ich mir eine traditionell definierte Swizzled-Methode anschaue, denke ich, dass es wirklich schwer ist zu sagen, was los ist. Aber wenn man sich die alternative Art und Weise ansieht, wie wir oben geschwommen sind, ist das ziemlich leicht zu verstehen. Dieser ist bereits gelöst!
Schwer zu debuggen
Eine der Verwirrungen beim Debuggen ist das Sehen einer seltsamen Rückverfolgung, in der die Namen durcheinander gebracht werden und alles in Ihrem Kopf durcheinander gerät. Auch hier spricht die alternative Implementierung dies an. In Backtraces werden klar benannte Funktionen angezeigt. Trotzdem kann das Debuggen schwierig sein, da es schwer zu merken ist, welche Auswirkungen das Swizzling hat. Dokumentieren Sie Ihren Code gut (auch wenn Sie denken, dass Sie der einzige sind, der ihn jemals sehen wird). Befolgen Sie die guten Praktiken, und alles wird gut. Es ist nicht schwieriger zu debuggen als Multithread-Code.
Fazit
Das Methoden-Swizzling ist bei sachgemäßer Anwendung sicher. Eine einfache Sicherheitsmaßnahme, die Sie ergreifen können, besteht darin, nur einzuspritzen
load
. Wie viele Dinge in der Programmierung kann es gefährlich sein, aber wenn Sie die Konsequenzen verstehen, können Sie es richtig verwenden.1 Mit der oben definierten Swizzling-Methode können Sie die Thread-Sicherheit erhöhen, wenn Sie Trampoline verwenden. Sie würden zwei Trampoline brauchen. Zu Beginn der Methode müssten Sie den Funktionszeiger
store
einer Funktion zuweisen, die sich dreht, bis sich die Adresse, auf die sie zeigtstore
, geändert hat. Dies würde jede Racebedingung vermeiden, in der die Swizzled-Methode aufgerufen wurde, bevor Sie denstore
Funktionszeiger setzen konnten. Sie müssten dann ein Trampolin verwenden, wenn die Implementierung noch nicht in der Klasse definiert ist, und die Trampolin-Suche durchführen und die Super-Klassen-Methode ordnungsgemäß aufrufen. Wenn Sie die Methode so definieren, dass sie dynamisch nach der Super-Implementierung sucht, wird sichergestellt, dass die Reihenfolge der Swizzling-Aufrufe keine Rolle spielt.quelle
Zuerst werde ich genau definieren, was ich unter Methoden-Swizzling verstehe:
Methoden-Swizzling ist allgemeiner als dies, aber dies ist der Fall, an dem ich interessiert bin.
Gefahren:
Änderungen in der ursprünglichen Klasse . Wir besitzen nicht die Klasse, die wir schwirren. Wenn sich die Klasse ändert, funktioniert unser Swizzle möglicherweise nicht mehr.
Schwer zu pflegen . Sie müssen nicht nur die Swizzled-Methode schreiben und pflegen. Sie müssen den Code schreiben und pflegen, der den Swizzle ausführt
Schwer zu debuggen . Es ist schwierig, dem Fluss eines Sprühregens zu folgen. Einige Leute bemerken möglicherweise nicht einmal, dass der Sprühregen durchgeführt wurde. Wenn durch das Swizzle Fehler auftreten (möglicherweise aufgrund von Änderungen in der ursprünglichen Klasse), sind diese schwer zu beheben.
Zusammenfassend sollten Sie das Swizzeln auf ein Minimum beschränken und überlegen, wie sich Änderungen in der ursprünglichen Klasse auf Ihr Swizzle auswirken können. Außerdem sollten Sie klar kommentieren und dokumentieren, was Sie tun (oder es einfach ganz vermeiden).
quelle
Es ist nicht das Aufwirbeln selbst, das wirklich gefährlich ist. Das Problem ist, wie Sie sagen, dass es häufig verwendet wird, um das Verhalten von Framework-Klassen zu ändern. Es wird davon ausgegangen, dass Sie etwas über die Funktionsweise dieser Privatklassen wissen, das "gefährlich" ist. Selbst wenn Ihre Änderungen heute funktionieren, besteht immer die Möglichkeit, dass Apple die Klasse in Zukunft ändert und Ihre Änderung bricht. Wenn es viele verschiedene Apps tun, ist es für Apple umso schwieriger, das Framework zu ändern, ohne viel vorhandene Software zu beschädigen.
quelle
Sorgfältig und weise eingesetzt, kann es zu elegantem Code führen, aber normalerweise führt es nur zu verwirrendem Code.
Ich sage, dass es verboten werden sollte, es sei denn, Sie wissen zufällig, dass es eine sehr elegante Gelegenheit für eine bestimmte Entwurfsaufgabe darstellt, aber Sie müssen klar wissen, warum es für die Situation gut gilt und warum Alternativen für die Situation nicht elegant funktionieren .
Eine gute Anwendung des Methoden-Swizzling ist beispielsweise das Swizzling, mit dem ObjC die Key Value-Beobachtung implementiert.
Ein schlechtes Beispiel könnte darin bestehen, sich auf Methoden-Swizzling zu verlassen, um Ihre Klassen zu erweitern, was zu einer extrem hohen Kopplung führt.
quelle
Obwohl ich diese Technik angewendet habe, möchte ich darauf hinweisen, dass:
quelle
Ich bin der Meinung, dass die größte Gefahr darin besteht, viele unerwünschte Nebenwirkungen völlig zufällig zu verursachen. Diese Nebenwirkungen können sich als "Fehler" darstellen, die Sie wiederum auf den falschen Weg führen, um die Lösung zu finden. Nach meiner Erfahrung ist die Gefahr unleserlicher, verwirrender und frustrierender Code. Ein bisschen wie wenn jemand Funktionszeiger in C ++ überbeansprucht.
quelle
Möglicherweise erhalten Sie einen seltsam aussehenden Code wie
vom tatsächlichen Produktionscode im Zusammenhang mit etwas UI-Magie.
quelle
Methoden-Swizzling kann beim Unit-Test sehr hilfreich sein.
Sie können ein Scheinobjekt schreiben und dieses Scheinobjekt anstelle des realen Objekts verwenden lassen. Ihr Code soll sauber bleiben und Ihr Unit-Test hat ein vorhersehbares Verhalten. Angenommen, Sie möchten Code testen, der CLLocationManager verwendet. Ihr Komponententest könnte startUpdatingLocation so schnell wie möglich aktivieren, sodass Ihrem Delegaten eine vorgegebene Gruppe von Speicherorten zugeführt wird und sich Ihr Code nicht ändern muss.
quelle