Rozszerzenia typów – Extensions

24 Października 2017

Cześć!
Dzisiaj postanowiłem wziąć na warsztat rozrzeszenia. Czym są, jak je stosować i po co?
Jeśli jesteś doświadczonym programistą, trudno będzie Cię w tym temacie zaskoczyć, tym niemniej zachęcam do czytanki i być może odświeżenia wiedzy z zakresu extensions. ✈️’my z tematem.

Czym są rozszerzenia?

Rozszerzenia, czyli tzw. extensions służą do powiększania istniejących już typów o nowe funkcjonalności, strutury, enumeratory czy protokoły. Innymi słowy dzięki rozszerzeniu możemy dopisać sobie do dowolnej klasy jakiś jej nowy fragment, np. metodę. W praktyce wygląda to tak, że np. chcemy dodać do klasy String metodę zastępującą literę “a” na jakiś inny znak. Taką metodę napiszemy właśnie jako rozszerzenie dla klasy String. Tak widzisz, to dotyczy również fragmentów kodu, do którego deweloper nie ma bezpośredniego dostępu. W literaturze taki sposób programowania nazywa się “Modelowanie retroaktywne”.

Programiści Objective-C porównają extensions do kategorii, z tą różnicą, iż rozszerzeniom nie trzeba nadawać nazw.

Co umieją extensions w Swift

Ok, wiesz już co to są rozszerenia, być może już domyślasz się również jak bardzo ułatwiają pracę 😎. Swift pozwala nam przy ich pomocy: definiować nowe metody klasy, tworzyć nowe konstruktory, definiować subscripty czy w końcu w myśl POP dostosowywać klasy do korzystania z konkretnych protokołów. W zasadzie w Swift możesz nawet rozszerzać protokoły!

Przed zakończeniem rozważań teoretycznych – jenda ważna uwaga. 🆘 Otóż rozszerzenia mogą dodawać pewne funkcjonalności do istniejących typów, ale nie mogą nadpisywać już istniejących!

Dobra, mamy komplet.
Teoria jest niczym bez praktyki, zatem ➡️ do kodu!

Implementacja Extension

Rozszerzenia implementuję się bardzo prosto – poprzez słowo kluczowe extension poprzedzające nazwę rozszerzanego typu.


    extension Type {
        // Tu implementujemy swoje funkcjonalności
    }

I już! Możemy dowolnie implementować swoje “dodatki”. Zobaczmy to na przykładzie, o którym wspomminałem na początku tego wpisu. Zamieńmy w napisie typu String dany ciąg znaków na jakąś emotkę, np. na pande 🐼.


    extension String {
        mutating func replaceWithPanda(char: String) -> String{
            return self.replacingOccurrences(of: char, with: "🐼")
        }
    }
    
    
    var string = "I really love panda!" //"I really love panda!"
    
    var stringWithPanda = string.replaceWithPanda(char: "a") //"I really love 🐼!"

No i mamy misia pande 😎. Ok, to może był mało praktyczny przykład, chyba że chcesz zaangażować się w akcję WWF… 🍞 Innym prykładem może być extension of the month z tego miesiąca -> bardzo praktyczny przykład z którego korzystam.

W tym miejscu należy też wspomnieć o tym, że o ile możemy wyliczać sobie różne wartości, modyfikować ich oraz dodawać nowych zmiennych nie możemy 😔. Niestety, nie wszystko na świecie jest takie proste i piękne.

Konstruktory

Jak wspomiałem wcześniej, rozszerzenia dostarczają możliwość deiniowania nowych konstruktorów dla klas rozszerzanych. Należy jednak pamiętać, że dodając konstruktor musimy zadbać o te wartości, bez których nie może powstać obiekt w swoim bazowym konstruktorze. Oznacza to że tworzymy tzw. “poręczny konstruktor”🇵🇱 (Convenience initializer), który wymaga od nas również dostarczenia domyślnych wartości.

Dla przykładu, dodajmy konstruktor do klasu UILabel w UIKit. Chcemy już na początku określać text oraz to, jakiego koloru ma być text w labelce.


    import UIKit
    
    extension UILabel{
        convenience init(text: String, color: UIColor) {
            self.init()
            self.text = text
            self.textColor = color
        }
    }
    
    let label = UILabel(text: "Label z konstruktora", color: UIColor.red)
    
    print(label.text!)
    label.textColor

Zwróć uwagę na potrzebę impementacji domyślnego konstruktora (w tym przypadku bezparametrowego). Bez tego środowisko krzyknie na nas, że źli z nas programiści.

Metody

Implementacje metod pokazałem już wcześniej, należy jednak zwrócić uwagę na jedną ważną różnicę -> pomiędzy zwykłą metodą, a metodą zmieniającą (mutating). W przykładzie z misiami 🐼 metoda zwracała nam string (self) z zastąpionymi literkami lub ciągiem, przekazanym jako parametr. Możemy jednak tworzyć metody, które modyfikują obiekt danej klasy. W tym celu właśnie tworzymy metody mutujące(? to jest świetna sprawa pisanie o Swift po Polsku 😎).

Dla przykładu – chcemy, aby nasza zmienna typu Int zmieniła się w wartość pola kwadratu… nieeeee…. to brzmi jak przykład z wykładów z wprowadzenia do programowania w Javie sprzed 10 lat, nudne… 😅 jestem pewien, że wolisz bardziej praktyczny przykład.

Weźmy zatem coś z programowania z Cocoa -> rozszerzmy klasę CGPoint tak, aby zrobić przesunięcie o podaną ilość punktów. Możemy tak zmieniać sobie w łatwy sposób położenie obiektu na ekranie, jeżeli nie używamy Autolayout’u.

Zatem tak:


    import UIKit
    
    extension CGPoint{
        mutating func move(x: CGFloat){
            self.x = x
        }
    }
    
    var point = CGPoint(x: 0, y: 0)
    
    print(point.x) // 0.0
    point.move(x: 20)
    print(point.x) // 20.0

A teraz dla ćwiczenia możesz wyrzucić słowo mutating z metody i zobaczyć, jaki komunikat pokaże Ci środowisko.
Podobnie jak metody, możemy również dodawać enumeratory. Jest to w zasadzie tak samo proste jak dodawanie metod, więc nie potrzba dodatkowego przykładu.

Rozszerzanie protokołów

Dochodzimy do ostatniego punktu dotyczącego rozszerzeń, czyli do rozszerzania i dodawania protokołów do istniejących klas.
Każdy z typów danych możemy dowolnie adoptować do nowych protokołów, nie musząc przy tym mieć dostępu do samego kodu źródłowego danej klasy.

Przykład:


    protocol ToString {
        var textualDescription: String { get }
    }

Jak widzisz protokół ten ma za zadanie wyświetlać tekstową reprezentację obiektu danego typu za pomocą metody textualDescription(). Zaadoptowanie tego protokołu dla danej klasy będzie wyglądało następująco:


    class Panda{
        var name: String
        init(name: String) {
            self.name = name
        }
    }
    
    
    extension Panda{
        var textualDescription: String {
            return "To jest 🐼 imieniem \(name)"
        }
    }
    
    let panda = Panda(name: "Stefan")
    print(panda.textualDescription)
    

I to w zasadzie tyle! Protokół został zaimplementowany i zaadoptowane w rozszerzeniu klasy Panda.

Konkluzje

Rozszerznie w języku Swift to w zasadzie powszechność, więc jeżeli nie jesteś początkującym programistą, to zapewne nie znalazłeś tutaj nic specjalnie odkrywczego. Należy jednak pamiętać o zaletach, jakie niosą za sobą extension. Ja sam, zaczynając naukę programowania od języka Java bardzo szybko chwyciłem idee kategorii w Objective-C, później również extensions w Swift – to mega pomocne i ciekawe podejście. które ułatwia życie szczególnie w przypadku różnego rodzaju bibliotek. Kidy nie posiadasz bezpośredniego dostępu do kodu, rosrzenienia przychodzą z odsieczą.

Jak zawsze bardzo chciałbym usłyszeć, czy Ci się podobało!😉
Naprawdę dużo pracy wkładam w to, aby wpisy które tutaj widzisz i czytasz były jak najlepszej jakości, dlatego chciałbym żebyś dał znać w komentarzu lub na twitterze czy jest ok, jeśli nie jest, to co nie jest, i te wszytskie takie 😅. Nawet, jeśli wkurzała Cię 🐼 -> muszę się chyba wybrać do zoo…

Anyway, dzięki że dotarłeś aż tutaj, take care i do przeczytania następnym razem!
@jkornat

Rozszerzenia typów – Extensions

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *