Enhanced Events

Simplified Event Dispatching

To dispatch an event, you create an event object and pass it to the “dispatchEvent:” method — you've seen that already.

SPEvent *event = [SPEvent eventWithType:EVENT_TYPE];
[self dispatchEvent:event];

What I didn't show you before is that there is an alternative method that does just the same, but is more concise: you can let Sparrow create the event for you and dispatch it right away.

[self dispatchEventWithType:EVENT_TYPE];
 
// or
[self dispatchEventWithType:EVENT_TYPE bubbles:YES];

That has exactly the same effect as the previous sample. Note, however, that this only works with standard events; if you need to dispatch a custom event subclass, you have to use the traditional method.

Block-based Event Listeners

A rather new feature of Objective-C are “blocks”. Think about a block as an inline-function that you can define anywhere in your code. In many situations, blocks allow you to simplify your code.

That's why Sparrow 2.0 introduced block-based event listeners. In the previous section, you saw event handlers declared at class level: a method would be called on a certain object when an event was dispatched.

The concise alternative to this approach is to define the event listener as a block, like this:

[button addEventListenerWithType:SP_EVENT_TYPE_TRIGGERED block:^(SPEvent *)event
{
   NSLog(@"button triggered");
}];

The thing starting with the caret (^) is the block. In essence, it's really nothing else than a function with one parameter of type “SPEvent *”.

Block event handlers have several advantages:

  • You can add your event handler directly inline,
  • which is less code to type and
  • allows you to directly access any variables of the outer scope.

Let's look at a more complete example. We want to create a button that toggles the visibility of an image object.

- (id)init
{
    if ((self = [super init]))
    {
        SPImage *image = [SPImage imageWithTexture:...];
        SPButton *button = [SPButton buttonWithUpState:...];
 
        [button addEventListenerWithType:SP_EVENT_TYPE_TRIGGERED block:^(SPEvent *)event
        {
            image.visible = !image.visible;
        }];
    }
}

Note that we didn't have to create a new instance method; the block is defined directly inline. But what's even more interesting is that we could access the “image” object directly in the block, even though it's not an instance variable of the class, but just a local variable.

If we used the conventional, selector-based event handler, we would have to convert the button object to an instance variable in order to be able to access it from the method.

Beware of Retain Cycles!

If you use this type of event handler, there is one thing you have to be careful about, though. Per default, a block will “retain” any object that is used inside it. That can easily lead to circular references, in other words: memory leaks.

Let's look at an example.

@implementation Game
 
- (id)init
{
    if ((self = [super init]))
    {
        [self addEventListenerForType:SP_EVENT_TYPE_ADDED_TO_STAGE block:^(id event)
        {
            [self startAnimations];
        }];
    }
}
 
- (void)startAnimations
{
    // ...
}
 
@end

When you compile this, Xcode will display the following warning:

Capturing 'self' strongly in this block is likely to lead to a retain cycle.

Do not ignore this warning! If you do, you will certainly leak some memory.

The reason for this is that the block retains “self” (because it's used in the block), and “self” retains the block (because it stores it in an internal list of event listeners). Such a situation is called “retain cycle” and will prevent the object from ever being released again.

Thankfully, that situation is easy to solve. You can create a new variable that references “self” only weakly, and use this variable instead of the other one inside the block.

__weak Game *weakSelf = self;
 
[self addEventListenerForType:SP_EVENT_TYPE_ADDED_TO_STAGE block:^(id event)
{
    [weakSelf startAnimations];
}];

This will not only happen when you access “self”, but also for any member variable of the class. The solution is just the same: create a “weak” pointer to that variable and use that in the block instead of directly accessing the member variable.

Changing Variables in the Block

In the following sample, we want to count the number of times someone has pressed a button. This is our first attempt at doing so:

int hitCount;
 
[button addEventListenerForType:SP_EVENT_TYPE_TRIGGERED block:^(id event)
{
   hitCount++;
   NSLog(@"Number of hits: %d", hitCount);
}];

Unfortunately, Xcode is not happy: it displays the following error message:

Variable is not assignable (missing __block type specifier)

This time, Xcode was even nice enough to tell us about the solution! Just add a block qualifier to the variable.

__block int hitCount;

That will fix the error.

Conclusion

As you can see, blocks are not 100% fail-safe. Still: if you listen to Xcode's warnings and errors, you should be on the safe side. In exchange, you will be able to massively simplify some of your code with the help of blocks.


Next Section: Touch Events

  manual/enhanced_events.txt · Last modified: 2014/02/05 09:54 by daniel
 
Powered by DokuWiki