Events vs (Objective-C) Delegates

Objective-C delegates (not to be confused with C# delegates) are a powerful way to customize a type thru delegation. However as a .NET developer it often feels a lot more natural to use events to achieve the same goal.

MonoTouch exposes the *Delegate types (found in iOS API) and it also provides, in many cases, event-based alternatives. It does so by including its own internal _*Delegate inner types – all of them generated code from the bindings.

Here’s an example from ATMHud‘s bindings available in monotouch-bindings github repository. It shows how the delegate type, AtmHudDelegate, can be turned into events, using the Events= property on the [BaseType] attribute.

[BaseType (typeof (UIViewController), Name="ATMHud", Delegates=new string [] { "WeakDelegate" }, Events=new Type [] { typeof (AtmHudDelegate)})]
interface AtmHud {
	[Export ("delegate"), NullAllowed]
	NSObject WeakDelegate { get; set; }
		
	[Wrap ("WeakDelegate")]
	AtmHudDelegate Delegate { get; set; }
	// ...

This definition, once processed by btouch, generates the source code for the events, the internal delegate type and the glue between them. E.g.

public event EventHandler UserDidTapHud {
	add { EnsureAtmHudDelegate ().userDidTapHud += value; }
	remove { EnsureAtmHudDelegate ().userDidTapHud -= value; }
}

_AtmHudDelegate EnsureAtmHudDelegate ()
{
	var del = WeakDelegate;
	if (del == null || (!(del is _AtmHudDelegate))){
		del = new _AtmHudDelegate ();
		WeakDelegate = del;
	}
	return (_AtmHudDelegate) del;
}

[Register]
class _AtmHudDelegate : MonoTouch.AtmHud.AtmHudDelegate {
	internal EventHandler userDidTapHud;
	[Preserve (Conditional = true)]
	public override Void UserDidTapHud (MonoTouch.AtmHud.AtmHud hud)
	{
		if (userDidTapHud != null){
			userDidTapHud (hud, EventArgs.Empty);
		}
	}
	// ...
}

In two words: Clever stuff. Still there are important things to keep in mind when using events instead of delegates.

Rule #1: Don’t mix MonoTouch events and [Weak]Delegate on the same types

When you set a [Weak]Delegate property you’re overwriting any existing one. This includes the internal one used to implement events. E.g.

X x = new X ();
// adding the event will create the internal _XDelegate
x.Event += () => { DoSomething (); }
// assigning MyDelegate will replace _XDelegate
// this will break Event from doing something
x.Delegate = new MyDelegate (x);

The inverse is also true. Settings events after the Delegate property will replace your delegate type with the generated one (see EnsureAtmHudDelegate method above). So the following code won’t work like expected:

X x = new X ();
x.Delegate = new MyDelegate (x);
// there's already a user-supplied delegate but setting the Event
// will create (and assign) a new _XDelegate one
x.Event += () => { DoSomething (); }

Rule #2: Set the Delegate or all events before setting properties or using the instance

Using events is not fully identical to using a delegate type. The keyword being fully. At some point it will be identical but setting a Delegate property is more atomic than setting several events. E.g. the following code is easy to type when using multiple events and properties:

X x = new X ();
// set properties and events by alphabetical order
// helped by IDE code completion feature
x.First += () => { DoSomething (); }
x.Name = "name";
x.Second += () => { DoSomethingElse (); }

When you set the first event on X (e.g. First) an internal _XDelegate instance is created and the event is assigned. Other, yet unassigned, events (e.g. Second) still have a default (in general empty) implementation.

Now if you set some properties (e.g. Name in the above code) then some of the _XDelegate methods may be called (from native code) even if only First is set. That might be a problem if something important must occurs in Second (e.g. if it’s only called once). Such behaviour (when delegate methods are called) is not something well documented in Apple documentation.

Similar problems can occur using the [Weak]Delegate (and in Objective-C too). E.g. if you do not set your delegate soon enough.

X x = new X ();
x.Name = "name";
// if setting Name (tried to) call the Delegate
// then nothing will happen
x.Delegate = new MyDelegate (x);

However it’s a bit common (at least when reading Objective-C code) to see the Delegate set early and, once set, everything (i.e. all the methods) becomes available at once.

X x = new X ();
// this _might_ not behave identically to the previous listing
x.Delegate = new MyDelegate (x);
x.Name = "name";

Is this an unlikely scenario ? It’s not common but we found out that different iOS versions calls delegate methods at different times. IOW the fact that some code works today does not mean it will work identically in the future.

An example of this is UISplitViewController where setting the ViewController property, before the events, cause ShouldHideViewController to be called immediately (on iOS 5.1) instead of later (like iOS 5.0 and earlier). Since the boolean result could differ (between your code and the default value) you could end up looking at a very different UI.

Note: While this was written for MonoTouch it also applies to MonoMac which (mostly) shares the same binding tools and Objective-C coexistence.

About these ads

About spouliot

Xamarin Hacker, Mono Contributor, Open Source Enthusiast with strong interests in Code Analysis, Cryptography and Security. Presently working on MonoTouch, previous projects were Moonlight and libgdiplus and a lot of the cryptographic work done on Mono.
This entry was posted in monotouch, mono, xamarin, monomac. Bookmark the permalink.

One Response to Events vs (Objective-C) Delegates

  1. Joe says:

    Great post. This specific problem has been doing my head in!
    I have been trying to use a UISplitView where the UIViewControllers are assigned before wiring up the WillHideViewController, WillShowViewController and ShouldHideViewController events. The MasterView never shows in landscape mode and the events never fire despite the orientation. I thought it was something that only happened after upgrading to iOS 5.1 but couldn’t be sure. You’ve now made it very clear!

    Thanks for your awesome contribution and support of the MT community, especially on StackOverflow.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s