az első osztályú funkciókat támogató nyelvek lehetővé teszik a függvények és metódusok használatát, mint bármely más objektum vagy érték. Átadhatja őket argumentumként, mentheti őket tulajdonságokba, vagy visszaadhatja őket egy másik funkcióból. A szavak sorrendjében a nyelv “első osztályú állampolgárként”kezeli a funkciókat.

bár a Swift aligha az első nyelv, amely támogatja a funkciók kezelésének ezt a módját, általában olyan funkció, amelyet dinamikusabb nyelveken lát, mint a JavaScript vagy a Lua. Tehát a Swift robusztus statikus típusú rendszerének első osztályú funkciókkal való kombinálása meglehetősen érdekes kombinációvá válik, és lehetővé teszi számunkra, hogy néhány nagyon kreatív dolgot végezzünk.

ezen a héten vessünk egy pillantást néhány különböző módon, hogy az első osztályú funkciókat lehet használni Swift!

Instabug: sokkal gyorsabban oldja meg a hibákat, összeomlásokat és egyéb problémákat a részletes veremnyomok, hálózati naplók és UI események segítségével, amelyeket az Instabug automatikusan csatol az egyes hibajelentésekhez. Mind az én, mind az iOS fejlesztői csapatok ezrei használják szerte a világon. Próbálja ki ingyen, és integrálja csak egy sor kódot.

Passing funkciók argumentumok

kezdjük az alapokkal. Mivel a függvények értékként használhatók, ez azt jelenti, hogy argumentumként átadhatjuk őket. Tegyük fel például, hogy alnézetek tömbjét szeretnénk hozzáadni egy nézethez. Normális esetben, lehet, hogy valami ilyesmi:

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

a fenti kód működik, és nincs semmi baj vele. De ha kihasználjuk az első osztályú funkciókat, valójában nagyon sokat csökkenthetjük a bőbeszédűségét.

amit tehetünk, hogy a addSubview metódust (UIView) -> Void típusú zárásként kezeljük (mivel elfogadja a hozzáadandó nézetet, és nem ad vissza semmit). Ez tökéletesen illeszkedik a forEach által elfogadott argumentum típusához ((Element) -> Void típusú lezárás, ebben az esetben a Element típus UIView). Az eredmény az, hogy átadhatjuk view.addSubview közvetlenül argumentumként a forEach hívásunkhoz, mint ez:

subviews.forEach(view.addSubview)

ez nagyon király! Egy dolgot azonban szem előtt kell tartani, hogy ha a példánymódszereket ilyen bezárásként használja, akkor automatikusan megtartja a példányt, amíg megtartja a bezárást. Ez egyáltalán nem jelent problémát, ha EGY függvényt nem menekülő bezárási argumentumként adunk át, mint a fentiek, de a bezárások elkerülése érdekében tisztában kell lenni a ciklusok megtartásának elkerülése érdekében.

további információt a szökés vs.nem szökés zárási argumentumokról és a rögzítésről az “objektumok rögzítése gyors lezárásokban”című részben talál.

az inicializátorok argumentumként történő átadása

a legjobb dolog az, hogy nem csak a funkciók és módszerek használhatók első osztályú funkcióként a Swift – ben-inicializálókat is használhat.

tegyük fel például, hogy van egy sor képünk, amelyekhez képnézeteket szeretnénk létrehozni, és hogy mindegyik képnézetet hozzá szeretnénk adni egy veremnézethez. Első osztályú függvények használatával a fentieket egy egyszerű lánc segítségével érhetjük el map és forEach:

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

amit szeretek a kód ilyen módon történő strukturálásában, az az, hogy nagyon deklaratívvá válik. A beágyazott for hurkok helyett egyszerűen kijelentjük, hogy mi legyen az eredmény. Természetesen egyensúlyt kell találni a deklaratív, kompakt kód és az olvashatóság között, de a fentiekhez hasonló egyszerű műveleteknél úgy gondolom, hogy az első osztályú funkciók kihasználása szuper szép lehet.

további példákat és használati eseteket találhat az inicializátorok lezárásaként történő átadására az “egyszerű gyors függőségi befecskendezés funkciókkal” és az “időutazás a Swift egység tesztjeiben”.

létrehozása például metódus referenciák

merüljünk egy kicsit mélyebbre a csodálatos világ első osztályú függvények kb. Az egyik dolog, ami a leghosszabb ideig zavarba ejtett, az volt, hogy a példánymódszer automatikus kiegészítési javaslatait kaptam, amikor statikus módszert akartam hívni. Próbáld meg beírni UIView. az Xcode-ban, hogy lássam, mire gondolok, minden példány metódust javaslatként kapsz.

először azt hittem, hogy ez egy Xcode hiba, de aztán úgy döntöttem, hogy kivizsgálom. Kiderül, hogy egy Típus minden példány metódusához tartozik egy megfelelő statikus metódus, amely lehetővé teszi, hogy az adott példány metódust bezárásként lekérje, egy példány argumentumként történő átadásával.

például a következőket használhatjuk a removeFromSuperview metódusra való hivatkozás lekéréséhez egy adott UIView példányhoz:

let closure = UIView.removeFromSuperview(view)

a fenti lezárás hívása pontosan ugyanaz lenne, mint a view.removeFromSuperview() hívása, ami érdekes, de valóban hasznos? Vessünk egy pillantást néhány forgatókönyvre, ahol ennek a funkciónak a használata valóban nagyon jó eredményekhez vezethet.

XCTest Linuxon

az egyik módja annak, hogy az Apple egyik keretrendszere használja ezt a funkciót, amikor teszteket futtat az XCTest segítségével Linuxon. Az Apple saját platformjain az XCTest az Objective-C futási idő használatával megkeresi az adott tesztesethez tartozó összes tesztmódszert, majd automatikusan futtatja azokat. Linuxon azonban nincs Objective-C futási idő, ezért megköveteli tőlünk, hogy írjunk egy kis boilerplate-t a tesztek futtatásához.

először egy statikus allTests szótárt kell deklarálnunk, amely a tesztnevek és a tényleges futtatandó módszerek közötti leképezést tartalmazza:

ezután átadjuk a fenti szótárt a XCTMain függvénynek a tesztek futtatásához:

XCTMain()

a motorháztető alatt ez azt a funkciót használja, hogy képes kibontani a példány metódusokat annak statikus megfelelőjével, amely lehetővé teszi számunkra, hogy statikus kontextusban egyszerűen név szerint hivatkozzunk a függvényekre, miközben továbbra is lehetővé teszi a keretrendszer számára a példány metódusok futtatását. Nagyon okos! 6085>

anélkül, hogy ez a funkció, akkor kellett volna írni valami ilyesmit:

hív egy példány módszer minden eleme egy szekvencia

vegyük ezt a funkciót egy spin magunkat. Csakúgy, mint egy másik objektum példány metódusát argumentumként átadhattuk a forEach – nek, nem lenne jó, ha átadnánk egy példány metódust is, amelyet egy sorozat minden elemének végre akarunk hajtani?

tegyük fel például, hogy van egy sor alnézetünk, amelyeket el akarunk távolítani a superview-ból. Ahelyett, hogy ezt kellene tennie:

for view in views { view.removeFromSuperview()}

nem lenne jó, ha ezt meg tudnánk csinálni helyette:

views.forEach(UIView.removeFromSuperview)

a jó hír az, hogy tudunk, csak annyit kell tennünk, hogy létrehozunk egy kis kiterjesztést a Sequence – en, amely elfogadja ezen statikusan hivatkozott példánymódszerek egyikét. Mivel ezek olyan funkciók, amelyek függvényt generálnak (Functionception! típusuk mindig (Type) -> (Input) -> Output lesz, így a kiterjesztésünkhöz létrehozhatunk egy forEach túlterhelést, amely elfogadja az ilyen típusú lezárást:

most már könnyen hívhatunk példány metódusokat bármely sorozat minden tagjára! ++

cél/cselekvés végrehajtása Objective-C nélkül

vessünk egy pillantást még egy példára. Az UIKit – ben a cél/művelet minta nagyon gyakori, a gombkattintások megfigyelésétől a gesztusokra való válaszadásig. Én személy szerint nagyon szeretem ezt a mintát, mivel lehetővé teszi számunkra, hogy könnyen használjunk egy példánymódszert visszahívásként anélkül, hogy aggódnunk kellene a korábban tárgyalt megtartási ciklus probléma miatt (amikor egy példánymódszert bezárásként hivatkozunk).

az UIKit-ben a target/action megvalósításának módja azonban az Objective-C választókra támaszkodik (ezért a privát cselekvési módszereket @objc – vel kell kommentálni). Tegyük fel, hogy hozzá akarjuk adni a cél/művelet mintát az egyik egyéni nézetünkhöz, és tegyük fel, hogy az Objective-C szelektorokra támaszkodva akarjuk megtenni. Ez úgy hangzik, mint egy csomó munka, és hogy ez teszi a dolgokat borzasztóan bonyolult, de hála az első osztályú funkciók – ez elég egyszerű! ++

kezdjük egy Action típus meghatározásávalalias mint statikus függvény, amely egy adott típusú és bemeneti példány metódust ad vissza:

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

ezután hozzuk létre a nézetünket. Létrehozunk egy ColorPicker értéket, amely lehetővé teszi a felhasználó számára, hogy színt válasszon egy rajzalkalmazásban, és hozzáadjon egy módszert a cél & művelet hozzáadásához. Minden megfigyelést lezárásként követünk, és minden alkalommal, amikor egy lezárást futtatunk, létrehozunk egy példány metódust az adott célhoz, és lefuttatjuk, így:

a legjobb dolog az, hogy az első osztályú függvényeket még jobban tudjuk használni. A map API Optional használatával létrehozhatjuk a példány metódust és meghívhatjuk egy menetben, így:

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

végül használjuk az új target/action API-t egy CanvasViewController – ben, amely bemutatja a ColorPicker – et. Csakúgy, mint egy cél & műveletet hozzáadnánk egy UIButton vagy UIGestureRecognizer – hez, egyszerűen átadhatjuk magát a nézetvezérlőt és egy futtatandó példánymódszert, mint ez:

írja be a biztonságos célokat & műveleteket Objective-C szelektorok vagy memóriaszivárgás kockázata nélkül, csak néhány sornyi kód használatával – elég jó! ②

támogassa a Swift by Sundell-t a szponzor megkeresésével:

Instabug: sokkal gyorsabban oldja meg a hibákat, összeomlásokat és egyéb problémákat a részletes veremnyomok, hálózati naplók és UI események segítségével, amelyeket az Instabug automatikusan csatol az egyes hibajelentésekhez. Mind az én, mind az iOS fejlesztői csapatok ezrei használják szerte a világon. Próbálja ki ingyen, és integrálja csak egy sor kódot.

következtetés

első osztályú funkciók egy nagyon erős funkció. A funkciók és módszerek sokkal dinamikusabb használatával nagyon érdekes eredményeket érhetünk el, és nagyon hasznos lehet bizonyos típusú API-k végrehajtásakor.

Ben bácsi híres szavai szerint azonban a nagy hatalom nagy felelősséggel jár. Bár úgy gondolom, hogy szuper hasznos megismerni az ilyen típusú funkciókat és azok működését, fontos, hogy bizonyos korlátozásokat gyakoroljunk a használatuk során. Célunk mindig az, hogy olyan API-kat hozzunk létre, amelyek szépek és könnyen használhatóak, és olyan kódot írjunk, amely egyaránt könnyen olvasható & karbantartás. Az első osztályú funkciók mindenképpen segíthetnek abban, hogy ezt a célt szolgáljuk, de ha túl messzire vesszük, az éppen az ellenkezőjéhez is vezethet. Mint mindig, azt javaslom, hogy kísérletezz, próbáld ki ezeket a funkciókat, és nézd meg magad, hogy használhatók-e a saját kódodban.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.