Wstęp

16 Kwietnia 2018

Zrozumieć błędy w programie – to kluczowe 🔑 zadanie każdego z nas. Przecież nie bez przyczyny w branży panuje przekonanie, że tworzenie nowego kodu to około 10%, zaś 90% to poprawianie błędów 🐛. Warto zatem rozumień i wiedzieć, jakich błędów szukać i co dokładnie podpowiada nam konsola.

Ośmielę się stwierdzić, że Xcode (dla Swift’a) ma bardzo słaby, o ile nie najsłabszy, sposób listowania błędów w konsoli. Bardzo często debugger zatrzymuje się nie w kodzie, gdzie np. Wystąpił nieoczekiwany nil, ale w miejscu deklaracji klasy App Delegate. Jednym z często pojawiających się błędów w konsoli, który kończy pracę właśnie w tej pozycji, jest <<Unrecognized Selector Sent to Instance>>.

Jak sobie z nim poradzić, co może oznaczać i gdzie szukać odpowiedzi – postaram się pomóc właśnie w tym miejscu 😁👍.

„Przez debugowanie do kodu idealnego”.

Czym jest Selektor

W zasadzie im dalej idziemy z rozwojem Swift (3 i 4), tym mniej pojawiać się będzie błędów związanych z selektorami. Dlaczego? Bo szczerze mówiąc… Selektor to relikt przeszłości wywodzący się – jakby inaczej – z Objective-C.

Selektor jest jak wskaźnik do funkcji – dzięki niemu wskazujemy, która funkcję chcemy wykonać, oraz który obiekt ma ją wywołać.

W Objective-C możemy zadeklarować selektor jako :

@selector()

Możemy także wywołać selektor na danym obiekcie, posiadającym funkcję

performSelector

Możemy także sprawdzić, czy selektor istnieje na danym obiekcie, robiąc to poprzez funkcję:

respondsToSelector

No dobrze… ale zaraz… Objec… Co? Przecież to jest jakaś zamierzchła przeszłość. A my mamy Swift! To gdzie tam selektory? Odpowiedź może być tylko jedna… Objective-C is everywhere. Wszystkie frameworki przez lata były tworzone w oparciu właśnie o tę technologię. Nawet dzisiaj używasz (pośrednio) Objective-C, np. Korzystając z komponentów UIKit – framework’a odpowiadającego za elementy UI aplikacji na iOS.

W Swift też wiele jest jeszcze pozostałości po tym języku. Jedną z nich są właśnie selektory. Selektorów w Swift ja osobiście używam… umiarkowanie często 😅.

Przykłady: Kiedy tworzę przycisk w kodzie i chcę przypisać do niego metodę:

let button = UIButton(type: .system)
button.addTarget(self, action: #selector(clickAction), for: .touchUpInside)

Jest to tzw. target-action design pattern, czyli specjalny sposób kodowania oparty właśnie o wywoływanie metod w selektorach i określania obiektów, na których wywoływane są metody.

Po prostu – target-action design pattern jest wykorzystywany żeby powiedzieć co się musi zadziać, żeby wywołać daną funkcję – tak na zdrowy rozum 🤔.

Selektory w Swift

Jak wspominałem, Objective-C jest nieodłącznym kompanem Swift’a. Dalej uważam, że każdy szanujący się dev powinien znać przynajmniej podstawy tego języka symultanicznie z dalszą nauką Swifta.

Swift od samego początku pozwala również na używanie selektorów. Początkowo (do Swift 2.2) wyglądało to podobnie jak w Obj-C ➡️ na podaniu nazwy funkcji jako parametr konstruktora. Przykład:

Swift:

let gesture = UITapGestureRecognizer(target: self, action: Selector("onTapGesture:"))

Objective-C:

[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];

Jak możesz zauważyć, początkowo podawało się w Swift nazwę funkcji w formie … String’a. Było to bardzo uciążliwe, do Xcode nie był w stanie podpowiedzieć Ci składni ➡️ co często prowadziło do literówek i błędów programu. Co gorsze – Xcode nie informował Cię w żadnym momencie, że taka metoda może nie istnieć!

Na szczęście na odsiecz przybyła wersja 3, wprowadzająca selektory w formie zwykłej nazwy metody:

let gesture = UITapGestureRecognizer(target: self, action: #selector(onTapGesture(_:)))

To rozwiązywało naprawdę masę problemów. Wraz z wersją 4 przyszła jeszcze obsługa funkcji języka Objective-C poprzez deklarację @objc przez nazwami metod, pól etc. Ponieważ Selektor też pochodzi od niego, dlatego teraz podajemy również ten przedrostem przed nazwą funkcji:

@objc func onTapGesture(_ sender: Any)

Unrecognized Selector Sent to Instance

unrecognized-selector-sent-to-instance

Po dawce wiedzy związanej z samymi selektorami, możemy iść dalej w poszukiwaniu rozwiązania sprawy nierozpoznanych selektorów przesłanych do instancji…

  1. Sporo początkujących programistów rozpoczyna programowanie od nauki Swift’a. To bardzo dobrze – to zdecydowanie przyszłościowy kierunek, z dużo mniejszym progiem wejścia i dużo przyjaźniejszy w odbiorze. Dodatkowo często jest tak, że zaczynają oni od budowania widoków przez Interface Builder w Xcode.

    Wyobraź sobie sytuację… tworzysz przycisk, który ma niszczyć świat. Przeciągasz przycisk do kodu, ustawiasz IBAction, nazywasz metodą „destoryTheWorld” i… i działa 😁.

    Bardzo często jednak decydujemy się np. Na odpięcie przycisku, usunięcie metody w kodzie itp. Jednakże ! Zapominamy o odpięciu samego przycisku:

    W ten sposób mamy pierwsze wystąpienie! Selektor został wysłany do instancji, która już nie istnieje! Przyciski w edytorze graficznym też bazują na tych samych selektorach, które omówiłem na początku tego artykułu. Co więcej ➡️podobnie jak kiedyś ➡️ Xcode nie krzyknie o błędzie.

  2. Często tworzymy sobie jakiś widok w Storyboard, np. Właśnie przycisk. Ostylowujemy itp. A następnie kopiujemy na inny widok przez proste cmd+c ➡️ cmd+v. I … zapominamy, że przycisk jest podpięty pod selektor znajdujący się na innym widoku. I znowu błąd!
  3. Selektor nie pozwala na przekazanie parametrów metody. Możemy co najwyżej mieć dostęp do obiektu na którym wykonana jest akcja, o ile jest on poprawnie określony.

    Jeżeli chcemy uzyskać efekt po kliknięciu na główny widok aplikacji, to możemy zrobić coś takiego:

override func viewDidLoad() {
        super.viewDidLoad()
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(onClickAction(_:)))
        self.view.tag = 99
        self.view.addGestureRecognizer(tap)
        
    }
    
    @objc func onClickAction(_ sender: UIView) {
        print(sender.tag)  // 99
    }

To walnie takim błędem że głowa mała! 😎 Przecież funkcja nie jest wykonana na UIView, tylko na TapGestureRecognizer. Poprawnie będzie o tak:

override func viewDidLoad() {
        super.viewDidLoad()
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(onClickAction(_:)))
        self.view.addGestureRecognizer(tap)
        
    }
    
    @objc func onClickAction(_ sender: UITapGestureRecognizer) {
        print(sender.classForCoder)  // UITapGestureRecognizer
    }
  1. Możliwość dobrania się do parametrów sender’a mają obiekty, do których można przypisać target. Np. Przyciski:
override func viewDidLoad() {
        super.viewDidLoad()
        
        let button = UIButton()
            ...  // Dalsza implementacja
            button.tag = 99
        button.addTarget(self, action: #selector(onClickAction(_:)), for: .touchUpInside)
        
    }
    
    @objc func onClickAction(_ sender: UIButton) {
        print(sender.tag)   //  99
    }
  1. Ostatnim przypadkiem jest niewłaściwe zwalnianie pamięci. Kiedy jakiś obiekt ma przypisany selektor, a następnie zostaje zwolniona adres tego obiektu, a na to miejsce wskakuje inny – bez przypisanego selektora. Jest to dość trudne do wychwycenia, szczególnie jest nie wiesz w jaki sposób odpowiednio zwalniać zasoby. Swift bardzo często pozwala nam zapomnieć o tej czynności, bo 90% roboty robi za nas.

    Najlepiej zapobiegać poprzez kontrolowanie wycieków pamięci – tutaj z pomocą przychodzą Instruments, o których więcej kiedyś 😎.

Konkluzje

Naprawione? Załatane? Można wrzucać na test? 💻!

Po tym krótkim omówieniu powinieneś już dać sobie radę kiedy następny raz nawiedzi Cię Unrecognized Selector Sent to Instance 👻. Powinieneś już wiedzieć gdzie szukać i jak unikać powtarzania się tego typu błędów.

I tyle! Jeśli ten artykuł był dla Ciebie pomocny to daj proszę znać w komentarzu lub prywatnie na social media 😅! Będę bardzo wdzięczny za każdą przychylną i mniej przychylną opinię, żeby tworzyć jeszcze lepszy kontent dla Ciebie.

Tymczasem dzięki za przeczytania i zapraszam do pogadania:

Twitter

Instagram

Snapchat

Lub wpadnij na fanpage na Facebook’u!

Dzięki i do następnego wpisu!

@jkornat

Kategorie: Junior Dev

1 Komentarz

Jan Zac · Maj 17, 2018 o 19:54

Hello ,

I saw your tweets and thought I will check your website. Have to say it looks very good!
I’m also interested in this topic and have recently started my journey as young entrepreneur.

I’m also looking for the ways on how to promote my website. I have tried AdSense and Facebok Ads, however it is getting very expensive. Was thinking about starting using analytics. Do you recommend it?
Can you recommend something what works best for you?

Would appreciate, if you can have a quick look at my website and give me an advice what I should improve: http://janzac.com/
(Recently I have added a new page about FutureNet and the way how users can make money on this social networking portal.)

I wanted to subscribe to your newsletter, but I couldn’t find it. Do you have it?

Hope to hear from you soon.

P.S.
Maybe I will add link to your website on my website and you will add link to my website on your website? It will improve SEO of our websites, right? What do you think?

Regards
Jan Zac

Dodaj komentarz

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