Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding nested blocks with asynchronous code in objective-c

I have a long series of events that needs to happen in my Objective-C code. Lets say I have 6 things - thingA, thingB, thingC, thingD, thingE and thingF. thingB and thingD return a BOOL. If thingB is NO, then thingC doesn't need to be called. If thingD is NO, then thingE doesn't need to be called.

- (void)doThings:(void(^)())completion {
    [self thingA: ^{
        [self thingB: ^(BOOL success) {
            if (success) {
                [self thingC: ^{
                    [self thingD: ^(BOOL success) {
                        if (thingD) {
                            [self thingE: ^{
                                [self thingF];
                                completion();
                            }];
                            return;
                        }

                        [self thingF];
                        completion();
                    }];
                }];
                return;
            }

            [self thingD: ^(BOOL success) {
                if (success) {
                    [self thingE: ^{
                        [self thingF];
                        completion();
                    }];
                    return;
                }

                [self thingF];
                completion();
            }];
        }];
    }];
}

This can quickly become unwieldy. You can take the things that have different outcomes but lead back into the loop, and make them into new methods, as such:

- (void)doThings:(void(^)())completion {
    [self thingA: ^{
        [self attemptThingB: ^{
            [self attemptThingD: ^{
                [self thingF];
                completion();
            }]
        }];
    }]
}

- (void)attemptThingB:(void(^)())completion {
    [self thingB: ^(BOOL success) {
        if (success) {
            [self thingC: {
                completion();
            }];
            return;
        }

        completion();
    }];
}

- (void)attemptThingD:(void(^)())completion {
    [self thingD: ^(BOOL success) {
        if (success) {
            [self thingE: ^{
                completion();
            }];
            return;
        }

        completion();
    }];
}

This reduces code duplication, but is still messy and difficult to keep track of. It even results in methods with awkward names, which are really just helper methods to this particular case.

There must be a better way. Something that looks a lot like synchronous coding, but is asynchronous. The above code is difficult to read, which makes it dangerous if I ever want to add something new to the flow.

Suggestions of a better solution? Something like this?

- (void)doThings {
    [self thingA];
    BOOL thingBSuccess = [self thingB];
    if (thingBSuccess == YES) {
        [self thingC];
    }
    BOOL thingDSuccess = [self thingD];
    if (thingDSuccess == YES) {
        [self thingE];
    }
    [self thingF];

    return;
}

The immediately evident problem with that proposed solution is that the completion handler can pass out multiple objects, whereas the return value of a block can only handle 1 object. But something with a format similar to this? It's so much cleaner and easy to maintain.

like image 249
Andrew Avatar asked Aug 10 '14 12:08

Andrew


1 Answers

I think you've discovered dispatch groups!

there are 1000s articles on them, no need to pointlessly paste something in here,

Wait until multiple networking requests have all executed - including their completion blocks

cheers!


On a simpler level, it's possible what you're looking for is just simply "breakaway code" which is a critical part of writing, simply, tidy code. Note that this is not always the solution, but often - also, I'm not 100% clear on what you're asking. But in break away code you go like this ...

{
do something;
if ( failure, and you don't want to do any more ) return;
do some other important thing;
if ( failure of that thing, and you don't want to do any more ) return;
do yet another routine here;
if ( some other failed conditions, and you don't want to do any more ) return;
}

Here's a typical example. Often you will see code something like this...

-(void)loadNameInToTheCell
  {
  if ( self.showname != nil && [self.showname notLike:@"blah"] && kount<3 )
        {
        // many many lines of code here
        // many many lines of code here
        // many many lines of code here
        }
  }

You with me? But it's much better to write it like this:

-(void)loadNameInToTheCell
  {
  if ( self.showname == nil ) return;
  if ( [self.showname notLike:@"blah"] return;
  if ( kount<3 ) return;

  // many many lines of code here
  // many many lines of code here
  // many many lines of code here
  }

makes sense?

Note that critically, in any real project it's about the documentation not the code, so you can properly discuss and comment on that ...

-(void)loadNameInToTheCell
  {
  if ( self.showname == nil ) return;
  // don't forget Steve's routine could return nil

  if ( [self.showname notLike:@"blah"] return;
  // should we worry about caps here?

  if ( kount<3 ) return;
  // client wants to change this to 4 in future - Steve
  // screw that ... Biff, 8/2014

  // many many lines of code here
  // many many lines of code here
  // many many lines of code here
  }

Makes sense right? I hope that helps with what you were asking. You have to think the "other way around" you know?

A possible rule of thumb is if you ever have a condition and then a "very long block of code" - it's sort of wrong. Turn it around the other way and have breakaway conditions. Only then, have "the actual relevant code". In a sense .. never put a long slab of important code inside an if block; you're sort of thinking the wrong way around if so.

like image 132
Fattie Avatar answered Oct 20 '22 14:10

Fattie