“Obietnice” na przykładzie PromiseKit w Objective-C

“Obietnice” na przykładzie PromiseKit w Objective-C
Każdy programista prędzej czy później natrafia na problem synchronicznego wykonania po sobie pewnych czynności. Przykładowo: pobierz informację o użytkowniku z API, sparsuj odpowiedź serwera, zapisz dane do bazy, zaktualizuj widok i wiele innych. Do tego wszystkiego dochodzi jeszcze obsługa błędów na części z tych etapów. Co należy wtedy zrobić?

Bloki

Pierwsze, co nasuwa się na myśl i wydaje się być najbardziej rozsądne oraz “na czasie”, to użycie bloków. Jak mógłby wyglądać taki kod?

// W tym przykładzie pominąłem implementację poniższych metod, ponieważ są to klasyczne metody z arguementami blokowymi

[self downloadUserInfo:^(id result, NSError *error) {
        if (!error) {
            [self parseUserInfo:result completion:^(NSDictionary *userInfo, NSError *error) {
                if (!error) {
                    [self saveUserInfoToDB:userInfo completion:^(BOOL success, NSError *error) {
                        if (!error) {
                            [self updateView];
                        }
                        else {
                            // zrób coś z błędem
                        }
                    }];
                }
                else {
                    // zrób coś z błędem
                }
            }];
        }
        else {
            // zrób coś z błędem
        }
    }];

Mimo tak prostej operacji uzyskujemy spory “spaghetti code”, co nie wygląda najlepiej. W takich sytuacjach dobrze jest skorzystać z “obietnicy”, czyli w naszym przypadku z PromiseKit dla Objective-C.

Jak będzie wyglądał nasz kod, kiedy użyjemy PromiseKita?

- (PMKPromise *)downloadUserInfo
{
    return [PMKPromise new:^(PMKFulfiller fulfill, PMKRejecter reject) {
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        [session dataTaskWithURL:[NSURL URLWithString:@"adres_api.com"]
               completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
                   if (error) {
                       reject(error);
                   }
                   else {
                       fulfill(data);
                   }
               }];
    }];
}

- (PMKPromise *)parseUserInfo:(id)responseObject
{
    return [PMKPromise new:^(PMKFulfiller fulfill, PMKRejecter reject) {
        NSError *error = nil;
        NSDictionary *dictObj =
            [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
        if (error) {
            reject(error);
        }
        else {
            fulfill(dictObj);
        }
    }];
}

- (PMKPromise *)saveUserInfoToDB:(NSDictionary *)userInfo
{
    return [PMKPromise new:^(PMKFulfiller fulfill, PMKRejecter reject) {
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        NSManagedObject *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:context];
        [user setValue:userInfo[@"user_name"] forKey:@"userName"];
        // ...
        NSError *error = nil;
        [context save:&error];
        if (error) {
            reject(error);
        }
        else {
            fulfill(user.objectID);
        }
    }];
}

// Przykład wywołania zdefiniowanych powyżej obietnic

[self downloadUserInfo]
        .then(^(id responseObject) {
            return [self parseUserInfo:responseObject];
        })
        .then(^(NSDictionary *userDict) {
            return [self saveUserInfoToDB:userDict];
        })
        .then(^(NSManagedObject *user) {
            [self updateView];
        })
        .catch(^(NSError *error) {
            NSLog(@"%@", error);
            // obsłuż błąd
        })
        .finally(^{
            // wyłącz activity indictor itp
        });

Od razu widać, że kod jest bardziej przejrzysty i czytelny. Dodatkowo zyskujemy możliwość obsługi błędu w jednym miejscu. Oczywiście nic nie stoi na przeszkodzie, żeby “catch” użyć po dowolnym “then”, wtedy błąd rzucony za pomocą “reject(error)” zostanie złapany przez pierwszy napotkany “catch” w łańcuchu. Można w nim obsłużyć błąd i podobnie, jak w “then” zwrócić kolejny promise, by kontynuować wywołania poszczególnych “ogniw” łańcucha. Moduł finally zawsze umieszczany jest na końcu i wtedy też wykonywany (nawet jeśli “catch” nie zwraca nic). Nie zwraca on żadnej wartości, dlatego dobrze jest użyć go np. do ukrycia progresu ładowania danych.

 

Powyższy przykład to zaledwie część tego, co oferuje nam PromiseKit, dlatego zachęcam do zapoznania się z tą biblioteką. Została ona bardzo dobrze opisana na oficjalnej stronie: www.promisekit.org. W części drugiej opiszę przykład “opakowania” delegacji w Objective-C za pomocą PromiseKit.

Dowiedz się więcej

Clean-Swift – ogólny zarys architektury

Architektura Clean-Swift jest bardzo prostą architekturą, niewymagającą żadnych dodatkowych bibliotek. Składa się z 3 warstw, które bardzo dobrze rozdzielają widoki od logiki. Sprawdź, czym się charakteryzują i jakie są między nimi zależności.
Przeczytaj

Dynamiczna lokalizacja powiadomień na iOS-a, czyli jak wysyłać wiadomości w różnych językach

Jak ustanowić łącznik pomiędzy użytkownikiem, dostawcą a aplikacją? Wykorzystaj powiadomienia systemowe. Dzięki nim użytkownik szybko otrzymuje najnowsze informacje. To rozwiązanie łatwo zastosujesz w aplikacjach zorientowanych na jeden rynek. Problemy zaczynają się, gdy treści muszą być tworzone w różnych językach. Wtedy pomocą służy dynamiczna lokalizacja zdalnych powiadomień bez wykorzystania własnego serwera.
Przeczytaj

WebSockets na iOS-a – komunikacja w czasie rzeczywistym, która nie spowalnia aplikacji

Kiedy chcesz pobrać dane do aplikacji, zwykle pewnie wykorzystujesz interfejs API RESTful. Wystarczy zapytać serwer o paczkę danych i gotowe. To dobra metoda, jeśli aplikacja nie potrzebuje stałego dostępu do nowych informacji. A co jeśli treści muszą się odświeżać w czasie rzeczywistym? Wtedy opóźnienie może np. uniemożliwiać dokonanie zakupu albo postawienie zakładu sportowego. Na szczęście z pomocą przychodzą WebSockets na iOS-a. Sprawdź, jak je zaimplementować i dlaczego warto to zrobić.
Przeczytaj

Wycena projektu

Opowiedz nam o swoim projekcie i napisz, jak możemy Ci pomóc.

Dlaczego warto rozwijać z nami projekty?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nominacja w kategorii
M-COMMERCE

17

opinii klientów

Clutch logo
Logo Legalni bukmacherzy

Nagroda Legalnych Bukmacherów 2019

Najlepsza aplikacja mobilna

60+

zrealizowanych projektów