“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

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 2021

Wygrana w kategorii
ŻYCIE CODZIENNE

Nagroda Legalnych Bukmacherów

Nagroda Legalnych Bukmacherów 2019

Najlepsza aplikacja mobilna

Mobile Trends Awards logo

Mobile Trends Awards 2023

Wygrana w kategorii
MCOMMERCE ROZWÓJ

23

opinie klientów

Clutch logo