Sprachen, die erstklassige Funktionen unterstützen, ermöglichen es Ihnen, Funktionen und Methoden wie jedes andere Objekt oder jeden anderen Wert zu verwenden. Sie können sie als Argumente übergeben, in Eigenschaften speichern oder von einer anderen Funktion zurückgeben. Mit anderen Worten, die Sprache behandelt sie als „Bürger erster Klasse“.

Während Swift kaum die erste Sprache ist, die diese Art der Handhabung von Funktionen unterstützt, ist es normalerweise eine Funktion, die Sie in dynamischeren Sprachen wie JavaScript oder Lua sehen. Die Kombination von Swifts robustem statischem Typsystem mit erstklassigen Funktionen wird also zu einer recht interessanten Kombination und kann es uns ermöglichen, einige ziemlich kreative Dinge zusammen zu tun.

Diese Woche werfen wir einen Blick auf einige verschiedene Möglichkeiten, wie erstklassige Funktionen in Swift verwendet werden können!

Instabug: Lösen Sie Fehler, Abstürze und andere Probleme viel schneller mit den detaillierten Stack-Traces, Netzwerkprotokollen und UI-Ereignissen, die Instabug automatisch an jeden Fehlerbericht anhängt. Wird sowohl von mir als auch von Tausenden von iOS-Entwicklungsteams auf der ganzen Welt verwendet. Probieren Sie es kostenlos aus und integrieren Sie es mit nur einer einzigen Codezeile.

Übergeben von Funktionen als Argumente

Beginnen wir mit den Grundlagen. Da Funktionen als Werte verwendet werden können, bedeutet dies, dass wir sie als Argumente übergeben können. Angenommen, wir möchten einer Ansicht ein Array von Unteransichten hinzufügen. Normalerweise könnten wir so etwas tun:

let subviews = subviews.forEach { subview in view.addSubview(subview)}

Der obige Code funktioniert und daran ist nichts auszusetzen. Wenn wir jedoch erstklassige Funktionen nutzen, können wir die Ausführlichkeit erheblich reduzieren.

Wir können die addSubview -Methode als Abschluss vom Typ (UIView) -> Void behandeln (da sie eine hinzuzufügende Ansicht akzeptiert und nichts zurückgibt). Dies entspricht perfekt dem Argumenttyp, den forEach akzeptiert (ein Abschluss vom Typ (Element) -> Void, und in diesem Fall ist der Typ Element UIView ). Das Ergebnis ist, dass wir view.addSubview direkt als Argument an unseren forEach Aufruf übergeben können, wie folgt:

subviews.forEach(view.addSubview)

Das ist ziemlich cool! 😎 Beachten Sie jedoch, dass Sie bei Verwendung von Instanzmethoden als Closures wie dieser die Instanz automatisch beibehalten, solange Sie den closure beibehalten. Dies ist überhaupt kein Problem, wenn Sie eine Funktion wie oben als nicht entweichendes Schließungsargument übergeben, aber zum Entweichen von Schließungen sollten Sie dies beachten, um Beibehaltungszyklen zu vermeiden.

Weitere Informationen zum Escapen von Schließungsargumenten und zum Erfassen ohne Escapen finden Sie unter „Objekte in Swift-Schließungen erfassen“.

Übergeben von Initialisierern als Argumente

Das Coole ist, dass nicht nur Funktionen und Methoden in Swift als erstklassige Funktionen verwendet werden können – Sie können Initialisierer auch auf diese Weise verwenden.

Angenommen, wir haben ein Array von Bildern, für die wir Bildansichten erstellen möchten, und wir möchten jede dieser Bildansichten einer Stapelansicht hinzufügen. Mit erstklassigen Funktionen können wir all dies mit einer einfachen Kette von map und forEach:

images.map(UIImageView.init) .forEach(stackView.addArrangedSubview)

Was ich an der Strukturierung von Code auf diese Weise mag, ist, dass er sehr deklarativ wird. Anstelle von verschachtelten for Schleifen deklarieren wir einfach, was das Ergebnis sein soll. Es gibt natürlich ein Gleichgewicht zwischen deklarativem, kompaktem Code und Lesbarkeit, aber für einfache Operationen wie die oben genannten denke ich, dass es super nett sein kann, erstklassige Funktionen zu nutzen.

Weitere Beispiele und Anwendungsfälle zum Übergeben von Initialisierern als Closures finden Sie in „Simple Swift dependency injection with functions“ und „Time traveling in Swift unit tests“.

Erstellen von Instanz-Methodenreferenzen

Lassen Sie uns etwas tiefer in die wunderbare Welt der erstklassigen Funktionen eintauchen 😉. Eine Sache, die mich am längsten verwirrte, war die Tatsache, dass ich Vorschläge zur automatischen Vervollständigung von Instanzmethoden erhielt, wenn ich eine statische Methode aufrufen wollte. Versuchen Sie, UIView. in Xcode einzugeben, um zu sehen, was ich meine, Sie erhalten jede Instanzmethode als Vorschlag 🤔.

Zuerst dachte ich, dies sei ein Xcode-Fehler, aber dann beschloss ich, ihn zu untersuchen. Es stellt sich heraus, dass es für jede Instanzmethode, die ein Typ hat, eine entsprechende statische Methode gibt, mit der Sie diese Instanzmethode als Abschluss abrufen können, indem Sie eine Instanz als Argument übergeben.

Beispielsweise können wir Folgendes verwenden, um einen Verweis auf die removeFromSuperview -Methode für eine bestimmte UIView -Instanz abzurufen:

let closure = UIView.removeFromSuperview(view)

Das Aufrufen des obigen Abschlusses wäre genau dasselbe wie das Aufrufen von view.removeFromSuperview() , was interessant ist, aber ist es wirklich nützlich? Werfen wir einen Blick auf einige Szenarien, in denen die Verwendung dieser Funktion tatsächlich zu ziemlich coolen Ergebnissen führen kann.

XCTest unter Linux

Eine Möglichkeit, wie eines der Frameworks von Apple diese Funktion verwendet, besteht darin, Tests mit XCTest unter Linux auszuführen. Auf Apples eigenen Plattformen verwendet XCTest die Objective-C-Laufzeit, um alle Testmethoden für einen bestimmten Testfall nachzuschlagen, und führt sie dann automatisch aus. Unter Linux gibt es jedoch keine Objective-C-Laufzeit, daher müssen wir ein wenig Boilerplate schreiben, damit unsere Tests ausgeführt werden können.

Zuerst müssen wir ein statisches allTests Wörterbuch deklarieren, das eine Zuordnung zwischen unseren Testnamen und den tatsächlich auszuführenden Methoden enthält:

Wir übergeben dann das obige Wörterbuch an die Funktion XCTMain, um unsere Tests auszuführen:

XCTMain()

Unter der Haube verwendet dies die Funktion, Instanzmethoden mithilfe ihres statischen Äquivalents extrahieren zu können, wodurch wir einfach in einem statischen Kontext namentlich auf die Funktionen verweisen können, während das Framework weiterhin Instanzmethoden zum Ausführen generieren kann. Ziemlich clever! 👍

Ohne diese Funktion hätten wir so etwas schreiben müssen:

Aufruf einer Instanzmethode für jedes Element in einer Sequenz

Lassen Sie uns diese Funktion selbst ausprobieren. Genauso wie wir die Instanzmethode eines anderen Objekts als Argument an forEach , wäre es nicht cool, wenn wir auch eine Instanzmethode übergeben könnten, die jedes Element in einer Sequenz ausführen soll?

Angenommen, wir haben ein Array von Unteransichten, die wir aus ihrer Übersicht entfernen möchten. Anstatt dies zu tun:

for view in views { view.removeFromSuperview()}

Wäre es nicht cool, wenn wir das stattdessen tun könnten:

views.forEach(UIView.removeFromSuperview)

Die gute Nachricht ist, dass wir nur eine kleine Erweiterung für Sequence erstellen müssen, die eine dieser statisch referenzierten Instanzmethoden akzeptiert. Da es sich um Funktionen handelt, die eine Funktion generieren (Functionception! 😂) ihr Typ ist immer (Type) -> (Input) -> Output , daher können wir für unsere Erweiterung eine forEach Überladung erstellen, die einen Abschluss dieses Typs akzeptiert:

Wir können jetzt einfach Instanzmethoden für jedes Mitglied einer beliebigen Sequenz aufrufen! 🎉

Implementieren von target / action ohne Objective-C

Schauen wir uns ein weiteres Beispiel an. In UIKit ist das Ziel- / Aktionsmuster sehr häufig, von der Beobachtung von Tastenklicks bis zur Reaktion auf Gesten. Ich persönlich mag dieses Muster sehr, da wir damit problemlos eine Instanzmethode als Rückruf verwenden können, ohne uns um das zuvor diskutierte Problem des Beibehaltungszyklus kümmern zu müssen (wenn wir auf eine Instanzmethode als Abschluss verweisen).

Die Art und Weise, wie Ziel / Aktion in UIKit implementiert wird, hängt jedoch von Objective-C-Selektoren ab (weshalb Sie private Aktionsmethoden mit @objc kommentieren müssen). Nehmen wir an, wir wollten das Ziel- / Aktionsmuster zu einer unserer benutzerdefinierten Ansichten hinzufügen, und nehmen wir an, wir möchten dies tun, ohne uns auf Objective-C-Selektoren zu verlassen. Das klingt nach viel Arbeit und macht die Dinge furchtbar kompliziert, aber dank erstklassiger Funktionen ist es ganz einfach!

Definieren wir zunächst ein Action typealias als statische Funktion, die eine Instanzmethode für einen bestimmten Typ und eine Eingabe zurückgibt:

typealias Action<Type, Input> = (Type) -> (Input) -> Void

Als nächstes erstellen wir unsere Ansicht. Wir erstellen eine ColorPicker , mit der der Benutzer eine Farbe in einer Zeichen-App auswählen kann, und fügen eine Methode zum Hinzufügen einer Zielaktion & hinzu. Wir verfolgen alle Beobachtungen als Closures , und jedes Mal, wenn ein Closure ausgeführt wird, generieren wir eine Instanzmethode für das angegebene Ziel und führen sie wie folgt aus:

Das Coole ist, dass wir tatsächlich erstklassige Funktionen verwenden können noch mehr oben. Mit der map -API auf Optional können wir die Instanzmethode generieren und auf einmal wie folgt aufrufen:

observations.append { view in target.map(action)?(view)}

Zum Schluss verwenden wir unsere neue Ziel- / Aktions-API in einer CanvasViewController , die unsere ColorPicker . Genau wie wir einer UIButton oder UIGestureRecognizer eine target & -Aktion hinzufügen würden, können wir einfach den View Controller selbst und eine Instanzmethode wie folgt übergeben:

Geben Sie sichere targets & -Aktionen ohne Objective-C-Selektoren oder Risiken für Speicherlecks mit nur wenigen Codezeilen ein – ziemlich cool! 👍

Unterstützen Sie Swift von Sundell, indem Sie sich diesen Sponsor ansehen:

Instabug: Lösen Sie Fehler, Abstürze und andere Probleme viel schneller mit den detaillierten Stack-Traces, Netzwerkprotokollen und UI-Ereignissen, die Instabug automatisch an jeden Fehlerbericht anhängt. Wird sowohl von mir als auch von Tausenden von iOS-Entwicklungsteams auf der ganzen Welt verwendet. Probieren Sie es kostenlos aus und integrieren Sie es mit nur einer einzigen Codezeile.

Fazit

Erstklassige Funktionen sind eine sehr leistungsstarke Funktion. Indem wir Funktionen und Methoden viel dynamischer verwenden können, können wir einige ziemlich interessante Ergebnisse erzielen, und dies kann bei der Implementierung bestimmter Arten von APIs sehr nützlich sein.

Aber in den berühmten Worten von Onkel Ben; Mit großer Macht kommt große Verantwortung. Während ich denke, dass es sehr nützlich ist, über diese Art von Funktionen zu lernen und wie sie funktionieren, ist es auch wichtig, etwas Zurückhaltung zu üben, wenn man sie benutzt. Unser Ziel sollte es immer sein, APIs zu erstellen, die schön und einfach zu bedienen sind, und Code zu schreiben, der sowohl leicht zu lesen & als auch zu pflegen ist. Erstklassige Funktionen können uns definitiv dabei helfen, dieses Ziel zu erreichen, aber wenn sie zu weit gehen, können sie auch zum Gegenteil führen. Wie immer empfehle ich, zu experimentieren, diese Funktionen auszuprobieren und selbst zu sehen, ob und wie sie in Ihrem eigenen Code verwendet werden können.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.