Promises on the Example of PromiseKit in Objective-C

Promises on the Example of PromiseKit in Objective-C

Sooner or later every programmer encounters the problem of synchronous execution of certain actions. For example: get user information from the API, parse server response, save data to the database, update the view, and many others. To make it even more, at some of these stages you still need to deal with error handling. What should you do exactly?

Blocks

The first thing that comes to mind and that seems to be the most reasonable and popular solution is using blocks. What could such code look like?

// In this example, I omitted the implementation of these methods because they are the classic methods with blocks arguments

[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 {
                            // do something with error
                        }
                    }];
                }
                else {
                    // do something with error
                }
            }];
        }
        else {
            // do something with error
        }
    }];

Although it is such a simple operation, we get quite a large piece of “spaghetti code”, which doesn’t look good. In such situations it is good to use “a promise”, which in our case means PromiseKit for Objective-C.

What our code will look like when we use PromiseKit?

- (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);
        }
    }];
}

// The example of use definied earlier promises

[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);
            // handle error
        })
        .finally(^{
            // turn off activity indicator etc.
        });

It’s obvious that the code is now clearer and more readable. Moreover, we can now handle all errors in one place. Of course, nothing stops you from putting “catch” after any “then”, in such a case an error thrown by “reject (error)” will be caught by a first “catch” encountered in a chain.

Here, you can handle the error and, similarly to how it’s done in “then”, return another promise to continue calling individual “links” of the chain.

The “finally” module is always placed and executed at the end (even if the “catch” doesn’t return anything). It doesn’t return any value, so it’s a good idea to use it to hide the progression of data loading or something similar.

The above example is only a part of what PromiseKit offers, so I encourage you to learn more about this library. It is well described on its official website: www.promisekit.org. In the next part, I will present an example of “packaging” a delegation in Objective-C using PromiseKit.

 

Lukasz - portrait

Łukasz Szyszkowski

iOS/Mac Developer with over 10 years of experience. He doesn’t limit himself only to Apple’s technologies – Python and Ruby are also among the languages he likes to use if needed. When it comes to programming, Łukasz is a problem solver, and he’s always happy to help when others need support. Fan of good cuisine who enjoys experiments in a kitchen. On sunny days, you might find him somewhere on a trail, biking or hiking.

Learn more

How to Send iOS Notifications in Different Languages? Guide to Dynamic Localization

The best way to set a connection between a user, provider, and an app? System notifications. They allow users to get the latest news in no time. This solution is easy to implement in apps dedicated to one market. But it gets complicated when the messages must be displayed in many languages. In such cases, the dynamic localization of remote notifications can be a real game-changer.

Read more

Promises on the Example of PromiseKit in Objective-C

Sooner or later every programmer encounters the problem of synchronous execution of certain actions. For example: get user information from the API, parse server response, save data to the database, update the view, and many others. To make it even more, at some of these stages you still need to deal with error handling. What should you do exactly?

Read more

Bitrise Tests Made Easier: Update JIRA Issues with Build Number and Forget About Delays in QA Testing

When you trigger Bitrise build with changes and forget to tell QA specialists some essential information, there are two likely scenarios. You waste time waiting for the update from the tester, only you don’t know it’s never coming. Or you get so many questions about builds that you can’t keep up. Sounds familiar? If so, let us tell you about the JIRA issue update that keeps workflow in order.

Read more

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2021

Winning app in
EVERYDAY LIFE category

Nagroda Legalnych Bukmacherów

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2020

Nomination in SPORTS & RECREATION category

20

client reviews

Clutch logo