Mastodon

Thursday, April 30, 2015

Objective-C Categories in Xamarin.iOS

One of the cool things about Objective-C is how dynamic it is. This includes the ability to add methods to classes that already exist even at runtime. Categories are a feature of Objective-C that can be used to do all kinds of things that might otherwise be difficult or impossible, but until very recently this feature has been inaccessible from C#. This post will explain what categories are, what they're used for, how to use them in C# using Xamarin.iOS 8.10, and a useful example.

What are Categories?

A category in Objective-C allows you to add a method to a class that already exists. This method can then be called from other code as if that method were a normal method on the original class. For example, you could add a ROT13 method to NSString. In Objective-C that would look like this:

@interface NSString (ROT13)

-(NSString*)rot13;

@end

@implementation NSString (ROT13)

-(NSString*)rot13
{
    // ...
}

@end

Once you've written that code anyone who knows that this method exists (by #importing the right header) can call this method on any NSString:

self.textField.text = [self.textField.text rot13];

Categories vs. Extension Methods

Objective-C categories are similar to C# extension methods. An extension method is also a way of adding a new method to an existing class in C#. For instance, you could add the same kind of ROT13 method to the C# string class like this:

public static class MyStringExtensions
{
    public static string Rot13(this string input)
    {
        // ...
    }
}

And then any code that has access to MyStringExtensions can call that method on any string:

textField.Text = textField.Text.Rot13();

Obviously these two features are very similar, but there are a few key differences. One of the biggest differences is that an Objective-C category is treated just like any other method, and every method in Objective-C is virtual. Therefore category methods are also virtual, which means you can define one for a class and then a subclass can override it like any other method. Likewise, you can use a category to override a method from a parent class from outside that class.

For example, consider these classes:

@interface Parent : NSObject

-(void)doSomething;

@end

@interface Child : Parent

@end

@implementation Parent

-(void)doSomething
{
    NSLog(@"Parent");
}

@end

@implementation Child

@end

With this code it is obvious that calling doSomething on an instance of Child will print "Parent" because the child class doesn't have its own implementation. Using a category, however, you could add an implementation from some other file (even if you don't have the source code to either class) like this:

@interface Child (MyCategory)

-(void)doSomething;

@end

@implementation Child (MyCategory)

-(void)doSomething
{
    NSLog(@"Child");
}

@end

Now if you call doSomething on an instance of Child you will see "Child" printed.

Another difference between extension methods and categories is that extension methods are just syntactic sugar for calling a normal static method. That is, these two lines compile to the exact same code:

textField.Text = textField.Text.Rot13();
textField.Text = MyStringExtensions.Rot13(textField.Text);

The method isn't really added to string, and if you use reflection you won't see it listed. On the other hand, Objective-C categories are just like any other method in Objective-C, which means if you use the runtime to list the available methods for NSString you would find your rot13 method listed.

Categories in Xamarin.iOS

Starting in Xamarin.iOS 8.10 you can now create your own categories from C#. This allows you to do some things in C# that you previously might have to resort to writing some Objective-C code to do (see below for a useful example).

The syntax for writing categories in Xamarin.iOS is actually the same as writing an extension method but with a few added attributes. Here is our ROT13 category again in C# this time:

[Category(typeof(NSString))]
public static class MyStringExtensions
{
    [Export("rot13")]
    public static NSString Rot13(this NSString input)
    {
        // ...
    }
}

The cool thing is that this method is now both an extension method and a category. That means that you can call if from C# code:

textField.Text = textField.Text.Rot13();

and from Objective-C:

self.textField.text = [self.textField.text rot13];

Example: Find The First Responder

Like most UI platforms, iOS has the concept of a focused element. There is a class called UIResponder that has methods for things like touch events and deciding what kind of keyboard to show and so on. At any given moment in time there is exactly one of these responder objects that is the "first responder"1. For example, when a UITextField gains focus it becomes the first responder (by calling BecomeFirstResponder()), which ultimately triggers the keyboard to appear. The keyboard can then ask the first responder which type of keyboard should appear.

However, there is an annoying gap in the iOS API for responders: there is no public API for getting the first responder. That is, while the iOS keyboard can ask the first responder which type of keyboard should appear there is no way for your code to even know what the first responder is so that you can ask the question.

However, there is a way to work around this by using categories along with the UIApplication SendEvent method. SendEvent lets you send a message up the responder chain starting from the first responder. "Sending a message" in Objective-C means "calling a method" so this lets you call a method on the first responder. The trick then is to call your method on the first responder, and you can do that if you create a method just for that purpose. Once your method is called you have access to the first responder in the this variable, and you can send that back to the sender.

Here is what it looks like:

/// <summary>
/// Utilities for UIResponder.
/// </summary>
public static class ResponderUtils
{
    internal const string GetResponderSelectorName = "AK_findFirstResponder:";
    private static readonly Selector GetResponderSelector = new Selector(GetResponderSelectorName);

    /// <summary>
    /// Gets the current first responder if there is one.
    /// </summary>
    /// <returns>The first responder if one was found or null otherwise..</returns>
    public static UIResponder GetFirstResponder()
    {
        using (var result = new GetResponderResult())
        {
            UIApplication.SharedApplication.SendAction(GetResponderSelector, target: null, sender: result, forEvent: null);
            return result.FirstResponder;
        }
    }

    internal class GetResponderResult : NSObject
    {
        public UIResponder FirstResponder { get; set; }
    }
}

[Category(typeof(UIResponder))]
internal static class FindFirstResponderCategory
{
    [Export(ResponderUtils.GetResponderSelectorName)]
    public static void GetResponder(this UIResponder responder, ResponderUtils.GetResponderResult result)
    {
        result.FirstResponder = responder;
    }
}

What I'm doing here is adding a method (using a category) to UIResponder, and then I'm calling that method by using SendEvent. Since the call is going to ultimately come from Objective-C I'm using the Selector (the name of the method in Objective-C2) to tell it which method to call, and that Selector matches the string I used in the Export attribute.

When my category method is called the first responder is the this argument of the extension method. Once I have that I just need to somehow send it back to the code that called SendEvent. For that I use a temporary container class allocated in the caller and sent as an argument. The category method just pokes the this argument into the container, and then the caller will have access to it.

There is a complete example of this on GitHub. In the full example application I use GetFirstResponder to find which text field got focus when the keyboard is shown, and I use that to change a property of the text field. This is easier than handling an event on each text field separately. You could also use this to find the view that has focus in order to scroll so that the focused view remains visible.

Summary

Categories are a powerful feature in Objective-C, and now that they are available to C# developers there are all kinds of possibilities for what you can do that was just not possible before (without writing Objective-C).


  1. The word "first" is used because the responders actually form a linked list called the "responder chain", and events can propagate up the responder chain. The responder chain usually mirrors the view hierarchy, but other objects like view controllers, the application delegate, and the application itself also participate in the responder chain. 

  2. For more information about Objective-C selectors, how the runtime works, and how Xamarin.iOS is implemented see this video

10 comments:

  1. Nice post !
    Super cool that categories can be this easily created from C#

    ReplyDelete
  2. Hi,
    I followed your (very nice) post at http://blog.adamkemp.com/2015/04/objective-c-categories-in-xamarinios.html!
    But I have one question: You say, that this method:

    [Category(typeof(NSString))]
    public static class MyStringExtensions
    {
    [Export("rot13")]
    public static NSString Rot13(this NSString input)
    {
    // ...
    }
    }
    is now callable from C# AND from Objective-C. Do you have any examples, how to make C# callable from Objective-C? I mean: How to mix C# and Objective-C code? I have only found the other way around, to make Objective-C accessible in C#, via e.g. http://developer.xamarin.com/guides/ios/advanced_topics/binding_objective-c/objective_sharpie/

    Thanks and regards,
    Christian

    ReplyDelete
  3. That's a good question. The first responder example shows one way in which the code is callable from Objective-C. The SendEvent method is implemented in Objective-C. We tell it which method to call, and it knows which object to call the method on. The call itself comes from Objective-C, though. In code that would look like this:

    [firstResponder performSelector:selector withObject:sender afterDelay:0];

    In a case like that it's callable from Objective-C because we've told it which method to call by name.

    Another approach would be to create an interface in Objective-C and then implement that interface in C#. That's in fact what you are doing every time you subclass an Objective-C class in C#. For instance, when you subclass UIViewController you are allowing Objective-C code (the UIKit framework) to call your C# code.

    You could do the same with your own Objective-C code by creating your own Objective-C interface, subclassing it in C#, and then providing some mechanism to pass the C# implementation to the Objective-C code.

    That's the best answer I can give without knowing more about what you're trying to do, but hopefully it helps clarify things in your mind.

    ReplyDelete
  4. I know that this is a couple of years old, but here is a question for you:

    If I try to use this approach to override a method such as:

    [Export("setAlpha:")]
    public static void SetAlpha(this UIImageView input, nfloat alpha ){
    ...
    }

    Is there any way for me to go call the base or super implementation of SetAlpha? In an Objective C category I can call something like [super setAlpha:alpha], but I can't find a way to do something similar here.

    ReplyDelete
    Replies
    1. That depends on what you are intending the call to super to do. Do you want it to call the original implementation that you replaced or do you want it to call the same code that super would have called in the original (i.e., the parent class's implementation)?

      If you intend to call the original implementation that you replaced then that doesn't work because you're not subclassing. Objective-C categories are for adding methods that don't exist, not modifying existing methods. This same limitation applies even if you are just using Objective-C directly (see this StackOverflow answer: http://stackoverflow.com/a/1405094). If you want to modify an existing method then the first thing you would want to try is subclassing. If that's not adequate then you would need to resort to method swizzling. I thought I remembered seeing a library somewhere that did that in C# Xamarin.iOS, but I can't find it now. I think you'll have to either find that library or learn enough of the Objective-C runtime to implement it yourself.

      If you intend to skip the original implementation and just call the parent class's implementation then you would need to call objc_msgSendSuper. This may be risky, though, depending on how you use it. If the method you're overriding was introduced in the class that you add the category to then you can't assume that a super implementation actually exists. In that case you would have to use a runtime check to see if there is a super implementation. I'm sure that can be done somehow, but I don't know off the top of my head how to do it.

      Overall I would say swizzling is probably a more appropriate technique for this kind of thing.

      Delete
    2. Yeah, it looks like swizzling may be the technique I need to figure out. Ultimately what I am trying to accomplish is to inject some code into UIImageView.setAlpha. I am trying to doing something along the lines of what was done here in Objective C: http://stackoverflow.com/questions/13697215/make-scrollbar-always-visible-on-uiscrollview

      Delete
    3. That would work only because UIImageView does not itself implement setAlpha: currently. If that changed in the future then you would be replacing whatever it does. Swizzling is definitely a safer thing to do for a situation like this, but of course it comes with its own risks. Another approach would be to just roll your own scroll indicators (hide the native ones), which wouldn't be too hard.

      Delete
    4. I've thought about that approach but ideally I would like to replace the scroll indicators not only in my own scroll views, but also in some other external libraries/classes that inherit from scrollview.

      The code at https://gist.github.com/blitzvb/7696435 is pretty much the only example I see. I've seen it used or referenced a few places though.

      Delete
    5. That seems like a particularly bad idea. Scroll views are used in a surprising number of places that you might not think of as being scroll views. They're not always vertical so you can't assume that showing vertical scrollbars will make sense, and they're very often nested so you could end up with overlapping scrollbars.

      Delete
    6. Yeah, I know that it is far from an ideal case. The idea is generally to flag certain scrollbars in certain instances of components that would benefit from this functionality. So the multiple scrollbars shouldn't really be a big issue, as only individual ones may display this behavior.

      It's a rather ugly solution to a non-issue that a few users keep asking about.

      Delete