języki, które obsługują funkcje pierwszej klasy, umożliwiają korzystanie z funkcji i metod tak jak każdy inny obiekt lub wartość. Możesz przekazać je jako argumenty, zapisać we właściwościach lub zwrócić z innej funkcji. W wyrazach porządkowych język traktuje funkcje jako „obywateli pierwszej klasy”.

chociaż Swift nie jest pierwszym językiem obsługującym ten sposób obsługi funkcji, zwykle jest to funkcja widoczna w bardziej dynamicznych językach, takich jak JavaScript lub Lua. Tak więc połączenie solidnego systemu typu statycznego Swifta z funkcjami pierwszej klasy staje się dość ciekawą kombinacją i może pozwolić nam zrobić całkiem kreatywne rzeczy😀.

w tym tygodniu przyjrzyjmy się kilku różnym sposobom użycia funkcji pierwszej klasy w języku Swift!

Instabug: rozwiązuj błędy, awarie i inne problemy znacznie szybciej, korzystając ze szczegółowych śladów stosu, dzienników sieciowych i zdarzeń interfejsu użytkownika, które Instabug automatycznie dołącza do każdego zgłoszenia błędu. Używany zarówno przeze mnie, jak i tysiące zespołów programistycznych iOS na całym świecie. Wypróbuj go za darmo i Zintegruj z jednym wierszem kodu.

przekazywanie funkcji jako argumentów

Zacznijmy od podstaw. Ponieważ funkcje mogą być używane jako wartości, oznacza to, że możemy przekazać je jako argumenty. Na przykład, załóżmy, że chcemy dodać tablicę podwidań do widoku. Normalnie, możemy zrobić coś takiego:

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

powyższy kod działa i nie ma w tym nic złego. Ale jeśli skorzystamy z funkcji pierwszej klasy, możemy znacznie zmniejszyć jej gadatliwość.

to, co możemy zrobić, to potraktować metodę addSubview jako zamknięcie Typu (UIView) -> Void (ponieważ akceptuje widok do dodania i nic nie zwraca). To idealnie pasuje do typu argumentu akceptowanego przez forEach (Zamknięcie typu (Element) -> Void, a w tym przypadku Typ Elementto UIView). W rezultacie możemy przekazać view.addSubview bezpośrednio jako argument do naszego wywołania forEach, w ten sposób:

subviews.forEach(view.addSubview)

to całkiem fajne! 😎 Należy jednak pamiętać o tym, że podczas używania metod instancji jako zamknięć takich jak ta, automatycznie zachowujesz instancję tak długo, jak długo zachowujesz zamknięcie. Nie jest to żaden problem, gdy przekazujemy funkcję jako nieusuwalny argument zamknięcia, jak powyżej, ale dla unikania zamykania jest to coś, o czym należy pamiętać, aby uniknąć cykli zatrzymywania.

aby uzyskać więcej informacji na temat argumentów zamykania i przechwytywania, sprawdź „Przechwytywanie obiektów w szybkich zamknięciach”.

przekazywanie inicjalizatorów jako argumentów

fajne jest to, że nie tylko funkcje i metody mogą być używane jako funkcje pierwszej klasy w Swifcie – możesz także używać inicjatorów w ten sposób.

na przykład, załóżmy, że mamy tablicę obrazów, dla których chcielibyśmy utworzyć widoki obrazów i że chcemy dodać każdy z tych widoków do widoku stosu. Korzystając z funkcji pierwszej klasy możemy osiągnąć wszystkie powyższe za pomocą prostego łańcucha map i forEach:

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

to, co lubię w strukturyzowaniu kodu w ten sposób, to fakt, że staje się on bardzo deklaratywny. Zamiast zagnieżdżonych pętli for po prostu deklarujemy, jaki chcemy mieć wynik. Istnieje oczywiście równowaga między deklaratywnym, zwartym kodem i czytelnością, ale dla prostych operacji, takich jak powyższe, myślę, że korzystanie z funkcji pierwszej klasy może być bardzo miłe.

możesz znaleźć więcej przykładów i przypadków użycia przekazywania inicjalizatorów jako zamknięć w „Simple Swift dependency injection with functions” I „time traveling in Swift unit tests”.

tworzenie odniesień do metod instancji

zagłębimy się nieco głębiej we wspaniały świat funkcji pierwszej klasy 😉. Jedną z rzeczy, która mnie zastanawiała przez najdłuższy czas, był fakt, że otrzymałem sugestie automatycznego uzupełniania metody instancji, gdy chciałem wywołać metodę statyczną. Spróbuj wpisać UIView. w Xcode, aby zobaczyć o co mi chodzi, każdą metodę instancji dostajesz jako sugestię 🤔.

na początku myślałem, że to błąd Xcode, ale potem postanowiłem go zbadać. Okazuje się, że dla każdej metody instancji, którą posiada dany typ, istnieje odpowiednia metoda statyczna, która pozwala pobrać tę metodę instancji jako zamknięcie, przekazując instancję jako argument.

na przykład, możemy użyć następującego polecenia, aby pobrać odniesienie do metody removeFromSuperview dla danej instancji UIView :

let closure = UIView.removeFromSuperview(view)

wywołanie powyższego zamknięcia byłoby dokładnie takie samo jak wywołanie view.removeFromSuperview(), co jest interesujące, ale czy naprawdę jest użyteczne? Rzućmy okiem na kilka scenariuszy, w których korzystanie z tej funkcji może prowadzić do całkiem fajnych wyników.

XCTest na Linuksie

jednym ze sposobów, w jaki jedna z frameworków Apple używa tej funkcji, jest uruchamianie testów przy użyciu XCTest na Linuksie. Na platformach własnych Apple, XCTest działa przy użyciu Objective – C runtime, aby wyszukać wszystkie metody testowe dla danego przypadku testowego, a następnie uruchamia je automatycznie. Jednak w Linuksie nie ma Objective-C runtime, więc wymaga to od nas napisania trochę boilerplate, aby nasze testy działały.

najpierw musimy zadeklarować statyczny słownik allTests, który zawiera mapowanie między naszymi nazwami testów a rzeczywistymi metodami do uruchomienia:

następnie przekazujemy powyższy słownik do funkcji XCTMain, aby uruchomić nasze testy:

XCTMain()

pod maską, używa się możliwości wyodrębniania metod instancji przy użyciu ich statycznego odpowiednika, co pozwala nam po prostu odwoływać się do funkcji po nazwie w statycznym kontekście, jednocześnie umożliwiając frameworkowi generowanie metod instancji do uruchomienia. Całkiem sprytne! 👍

bez tej funkcji musielibyśmy napisać coś takiego:

wywołanie metody instancji na każdym elemencie w sekwencji

weźmy tę funkcję na obrót. Tak jak mogliśmy przekazać metodę instancji innego obiektu jako argument do forEach, czy nie byłoby fajnie, gdybyśmy mogli również przekazać metodę instancji, którą chcemy, aby każdy element w sekwencji wykonywał?

na przykład, powiedzmy, że mamy tablicę podzbiorów, które chcemy usunąć z ich superwizji. Zamiast tego:

for view in views { view.removeFromSuperview()}

czy nie byłoby fajnie, gdybyśmy zamiast tego mogli to zrobić?:

views.forEach(UIView.removeFromSuperview)

dobrą wiadomością jest to, że możemy, wszystko co musimy zrobić to stworzyć małe rozszerzenie na Sequence, które akceptuje jedną z tych statycznie odwołujących się metod instancji. Ponieważ są to funkcje, które generują funkcję (Functionception! 😂 ) ich Typ zawsze będzie (Type) -> (Input) -> Output, więc dla naszego rozszerzenia możemy utworzyć przeciążenie forEach, które akceptuje zamknięcie tego typu:

możemy teraz łatwo wywoływać metody instancji na każdym członie dowolnej sekwencji! 🎉

realizacja celu / działania bez celu-C

przyjrzyjmy się jeszcze jednemu przykładowi. W UIKit wzorzec cel/akcja jest bardzo powszechny, od obserwacji kliknięć przycisków po reagowanie na gesty. Osobiście bardzo podoba mi się ten wzorzec, ponieważ pozwala nam łatwo używać metody instancji jako wywołania zwrotnego bez martwienia się o problem cyklu retain, który omówiliśmy wcześniej (podczas odwoływania się do metody instancji jako zamknięcia).

jednak sposób, w jaki cel/akcja jest zaimplementowana w UIKit, opiera się na selektorach Objective-C (dlatego musisz przypisać prywatne metody akcji za pomocą @objc). Załóżmy, że chcieliśmy dodać wzorzec cel / akcja do jednego z naszych niestandardowych widoków i powiedzmy, że chcemy to zrobić bez polegania na selektorach Objective-C. To może zabrzmieć jak dużo pracy i że będzie to strasznie skomplikowane, ale dzięki funkcjom pierwszej klasy – to całkiem proste! 😀

zacznijmy od zdefiniowania typu Action jako funkcji statycznej, która zwraca metodę instancji dla danego typu i wejścia:

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

następnie stwórzmy nasz widok. Utworzymy ColorPicker, która pozwoli użytkownikowi wybrać kolor w aplikacji do rysowania i dodać metodę dodawania do niej akcji docelowej &. Będziemy śledzić wszystkie obserwacje jako zamknięcia, a za każdym razem, gdy zamknięcie jest uruchamiane, generujemy metodę instancji dla danego celu i uruchamiamy ją w ten sposób:

fajną rzeczą jest to, że możemy faktycznie używać funkcji pierwszej klasy nawet więcej powyżej. Korzystając z API map na Optional, możemy wygenerować metodę instancji i wywołać ją za jednym razem, w ten sposób:

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

na koniec użyjmy naszego nowego target/action API w CanvasViewController, który zaprezentuje nasz ColorPicker. Tak jak dodalibyśmy akcję target & do akcji UIButton lub UIGestureRecognizer, możemy po prostu przekazać sam kontroler widoku i metodę instancji do uruchomienia, w ten sposób:

Typ safe targets & akcje bez żadnych selektorów Objective-C lub ryzyka wycieku pamięci, używając zaledwie kilku linii kodu – całkiem fajne! 👍

Wspieraj Swift przez Sundell, sprawdzając tego sponsora:

Instabug: rozwiązuj błędy, awarie i inne problemy znacznie szybciej, korzystając ze szczegółowych śladów stosu, dzienników sieciowych i zdarzeń interfejsu użytkownika, które Instabug automatycznie dołącza do każdego zgłoszenia błędu. Używany zarówno przeze mnie, jak i tysiące zespołów programistycznych iOS na całym świecie. Wypróbuj go za darmo i Zintegruj z jednym wierszem kodu.

wniosek

funkcje pierwszej klasy to bardzo potężna funkcja. Będąc w stanie używać funkcji i metod w znacznie bardziej dynamiczny sposób, możemy osiągnąć całkiem interesujące wyniki, a to może być naprawdę przydatne przy implementacji niektórych rodzajów API.

jednak w słynnych słowach wuja Bena; z wielką mocą przychodzi wielka odpowiedzialność. Chociaż uważam, że bardzo przydatne jest poznanie tego rodzaju funkcji i sposobu ich działania, ważne jest również, aby zachować pewną powściągliwość podczas ich używania. Naszym celem zawsze powinno być tworzenie interfejsów API, które są ładne i łatwe w użyciu, a także pisanie kodu, który jest łatwy do odczytania &. Funkcje pierwszej klasy mogą zdecydowanie pomóc nam w osiągnięciu tego celu, ale jeśli zostaną podjęte zbyt daleko, może to również prowadzić do czegoś odwrotnego. Jak zawsze, moim zaleceniem jest eksperymentowanie, wypróbowanie tych funkcji i sprawdzenie na własne oczy, czy i jak można je wykorzystać we własnym kodzie.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.