limbile care acceptă funcții de primă clasă vă permit să utilizați funcții și metode la fel ca orice alt obiect sau valoare. Puteți să le transmiteți ca argumente, să le salvați în proprietăți sau să le returnați dintr-o altă funcție. În ordine cuvinte, limba tratează funcțiile ca „cetățeni de primă clasă”.

în timp ce Swift nu este prima limbă care acceptă acest mod de gestionare a funcțiilor, este în mod normal o caracteristică pe care o vedeți în limbi mai dinamice, cum ar fi JavaScript sau Lua. Așadar, combinarea sistemului robust de tip static Swift cu funcții de primă clasă devine o combinație destul de interesantă și ne poate permite să facem câteva lucruri destul de creative.

în această săptămână, să aruncăm o privire la câteva moduri diferite în care funcțiile de primă clasă pot fi utilizate în Swift!

Instabug: rezolva bug-uri, accidente și alte probleme mult mai rapid folosind urmele detaliate stivă, jurnalele de rețea și evenimente UI care Instabug se atașează automat la fiecare raport de eroare. Folosit atât de mine, cât și de mii de echipe de dezvoltare iOS din întreaga lume. Încercați-l gratuit și integrați-l cu o singură linie de cod.

trecerea funcțiilor ca argumente

să începem cu elementele de bază. Deoarece funcțiile pot fi folosite ca valori, înseamnă că le putem transmite ca argumente. De exemplu, să presupunem că dorim să adăugăm o serie de subviews la o vizualizare. În mod normal, am putea face ceva de genul asta:

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

codul de mai sus funcționează și nu este nimic în neregulă cu acesta. Dar dacă profităm de funcțiile de primă clasă, putem reduce de fapt verbozitatea destul de mult.

ce putem face este să tratăm metoda addSubview ca o închidere de tip (UIView) -> Void (deoarece acceptă o vizualizare de adăugat și nu returnează nimic). Acest lucru se potrivește perfect tipului de argument pe care forEach îl acceptă (o închidere de tip (Element) -> Void, iar în acest caz tipul Elementeste UIView). Rezultatul este că putem trece view.addSubview direct ca argument la apelul nostru forEach , astfel:

subviews.forEach(view.addSubview)

asta e destul de cool! Cu toate acestea, un lucru de reținut este că atunci când utilizați metode de instanță ca închideri ca aceasta, păstrați automat instanța atâta timp cât păstrați închiderea. Aceasta nu este deloc o problemă atunci când treceți o funcție ca argument de închidere care nu scapă ca mai sus, dar pentru a scăpa de închideri este ceva de care trebuie să fiți conștienți pentru a evita ciclurile de reținere.

pentru mai multe informații despre evadarea vs non-evadarea argumente de închidere și capturarea, a verifica afară „capturarea obiectelor în închidere rapidă”.

trecerea inițializatorilor ca argumente

interesant este că nu numai funcțiile și metodele pot fi utilizate ca funcții de primă clasă în Swift – puteți utiliza și inițializatoarele în acest fel.

de exemplu, să presupunem că avem o serie de imagini pentru care am dori să creăm vizualizări de imagine și că dorim să adăugăm fiecare dintre aceste vizualizări de imagine la o vizualizare stivă. Folosind funcții de primă clasă putem realiza toate cele de mai sus folosind un lanț simplu de map și forEach:

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

ceea ce îmi place la structurarea codului în acest fel este că devine foarte declarativ. În loc de bucle imbricate for, declarăm pur și simplu ceea ce vrem să fie rezultatul. Există, desigur, un echilibru între declarativ, Cod compact și lizibilitate, dar pentru operațiuni simple, cum ar fi cele de mai sus cred că a profita de funcții de primă clasă poate fi super frumos.

puteți găsi mai multe exemple și puteți utiliza cazuri pentru trecerea inițializatorilor ca închideri în „injecție simplă de dependență Swift cu funcții” și „călătoria în timp în testele unitare Swift”.

crearea de referințe metodă instanță

să se arunca cu capul un pic mai adânc în lumea minunată a funcțiilor de primă clasă de la clasa a II-a. Un lucru care m-a nedumerit cel mai mult timp a fost faptul că am primit sugestii de finalizare automată a metodei de instanță atunci când am vrut să apelez la o metodă statică. Încercați să tastați UIView. în Xcode pentru a vedea ce vreau să spun, veți obține fiecare metodă de instanță ca o sugestie.

la început am crezut că acest lucru a fost un bug Xcode, dar apoi am decis să-l investigheze. Se pare că pentru fiecare metodă de instanță pe care o are un tip, există o metodă statică corespunzătoare care vă permite să preluați acea metodă de instanță ca Închidere, trecând o instanță ca argument.

de exemplu, putem folosi următoarele pentru a prelua o referință la metoda removeFromSuperview pentru o instanță UIView dată:

let closure = UIView.removeFromSuperview(view)

apelarea închiderii de mai sus ar fi exact aceeași cu apelarea view.removeFromSuperview(), ceea ce este interesant, dar este cu adevărat util? Să aruncăm o privire la câteva scenarii în care utilizarea acestei funcții poate duce de fapt la unele rezultate destul de interesante.

XCTest pe Linux

o modalitate prin care unul dintre cadrele Apple utilizează această caracteristică este atunci când rulează teste folosind XCTest pe Linux. Pe platformele proprii Apple, XCTest funcționează utilizând runtime-ul Objective-C pentru a căuta toate metodele de testare pentru un caz de testare dat și apoi le rulează automat. Cu toate acestea, pe Linux nu există un timp de rulare Objective-C, așa că ne cere să scriem un pic de boilerplate pentru a face testele noastre să ruleze.

în primul rând, trebuie să declarăm un dicționar static allTests care conține o mapare între numele testelor noastre și metodele reale de rulare:

apoi trecem dicționarul de mai sus la funcția XCTMain pentru a rula testele noastre:

XCTMain()

sub capotă, aceasta folosește caracteristica de a putea extrage metode de instanță folosind echivalentul său static, ceea ce ne permite să ne referim pur și simplu la funcții după nume într-un context static, permițând în același timp cadrului să genereze metode de instanță pentru a rula. Destul de inteligent!

fără această caracteristică, ar fi trebuit să scriem ceva de genul:

apelând o metodă de instanță pe fiecare element dintr-o secvență

să luăm această caracteristică pentru noi înșine. La fel cum am reușit să trecem metoda instanței unui alt obiect ca argument la forEach, nu ar fi grozav dacă am putea trece și o metodă de instanță pe care dorim să o efectueze fiecare element dintr-o secvență?

de exemplu, să presupunem că avem o serie de subview-uri pe care dorim să le eliminăm din superview-ul lor. În loc să trebuiască să faci asta:

for view in views { view.removeFromSuperview()}

nu ar fi grozav dacă am putea face acest lucru în schimb:

views.forEach(UIView.removeFromSuperview)

vestea bună este că putem, tot ce trebuie să facem este să creăm o mică extensie pe Sequence care acceptă una dintre aceste metode de instanță menționate static. Deoarece sunt funcții care generează o funcție (Functionception! * ) tipul lor va fi întotdeauna (Type) -> (Input) -> Output, astfel încât pentru Extensia noastră putem crea o suprasarcină forEach care acceptă o închidere de acest tip:

acum putem apela cu ușurință metode de instanță pe fiecare membru al oricărei secvențe!

implementarea obiectivului/acțiunii fără Obiectiv-C

să aruncăm o privire la încă un exemplu. În UIKit, modelul țintă / acțiune este foarte comun, pentru orice, de la observarea clicurilor butoanelor până la răspunsul la gesturi. Personal îmi place foarte mult acest model, deoarece ne permite să folosim cu ușurință o metodă de instanță ca apel invers, fără a fi nevoie să vă faceți griji cu privire la problema ciclului de reținere pe care am discutat-o mai devreme (când facem referire la o metodă de instanță ca Închidere).

cu toate acestea, modul în care ținta/acțiunea este implementată în UIKit se bazează pe selectorii Objective-C (motiv pentru care trebuie să adnotați metodele de acțiune privată cu @objc). Să presupunem că am vrut să adăugăm modelul țintă / acțiune la una dintre vizualizările noastre personalizate și să spunem că vrem să o facem fără să ne bazăm pe selectorii Objective-C. Asta ar putea suna ca o mulțime de muncă și că va face lucrurile extrem de complicate, dar datorită funcțiilor de primă clasă – este destul de simplu!

să începem prin definirea unui Action typealias ca o funcție statică care returnează o metodă instanță pentru un anumit tip și intrare:

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

apoi, să ne creăm punctul de vedere. Vom crea un ColorPicker care permite utilizatorului să aleagă o culoare într-o aplicație de desen și să adauge o metodă pentru adăugarea unei acțiuni țintă &. Vom urmări toate observațiile ca închideri și, de fiecare dată când se execută o închidere, generăm o metodă de instanță pentru ținta dată și o rulăm, astfel:

interesant este că putem folosi funcții de primă clasă chiar mai sus. Folosind API-ul map pe Optional , putem genera metoda instanței și o putem apela dintr-o dată, astfel:

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

în cele din urmă, să folosim noul nostru API țintă/acțiune într-un CanvasViewController, care va prezenta ColorPicker. La fel cum am adăuga o acțiune țintă & la o acțiune UIButton sau UIGestureRecognizer, putem trece pur și simplu controlerul de vizualizare în sine și o metodă de instanță pentru a rula, astfel:

tastați ținte sigure & acțiuni fără selectori Objective-C sau riscuri pentru scurgeri de memorie, folosind doar câteva linii de cod – destul de cool!

sprijiniți Swift de Sundell verificând acest sponsor:

Instabug: rezolva bug-uri, accidente și alte probleme mult mai rapid folosind urmele detaliate stivă, jurnalele de rețea și evenimente UI care Instabug se atașează automat la fiecare raport de eroare. Folosit atât de mine, cât și de mii de echipe de dezvoltare iOS din întreaga lume. Încercați-l gratuit și integrați-l cu o singură linie de cod.

concluzie

funcții de primă clasă este o caracteristică foarte puternic. Prin posibilitatea de a utiliza funcții și metode într-un mod mult mai dinamic, putem obține câteva rezultate destul de interesante și poate fi cu adevărat util atunci când implementăm anumite tipuri de API-uri.

cu toate acestea, în celebrele cuvinte ale unchiului Ben; cu o mare putere vine o mare responsabilitate. Deși cred că este foarte util să înveți despre aceste tipuri de caracteristici și despre modul în care funcționează, este, de asemenea, important să exersezi o anumită reținere atunci când le folosești. Scopul nostru ar trebui să fie întotdeauna de a crea API-uri care sunt frumos și ușor de utilizat, și pentru a scrie cod care este atât de ușor de citit & menține. Funcțiile de primă clasă ne pot ajuta cu siguranță să îndeplinim acest obiectiv, dar dacă sunt luate prea departe, pot duce și la contrariul. Ca întotdeauna, recomandarea mea este să experimentați, să încercați aceste caracteristici și să vedeți singuri dacă și cum pot fi utilizate în propriul cod.

Lasă un răspuns

Adresa ta de email nu va fi publicată.