Ken M. Haggerty Ken M. Haggerty - 2 months ago 6
Objective-C Question

Any way to apply Objective-C category only to current class (or equivalent effect)?

Let's say I have a custom subclass of UIView called

MyCustomView
. Let's also say that I have a category on UIView called
UIView+Dictionary
that adds an NSDictionary property called
dictionary
to every UIView.

If I were to import
UIView+Dictionary.h
into
MyCustomView.m
then every view referenced within
MyCustomView.m
would have this added
dictionary
property, which in many situations is exactly the desired behavior.

However, if I wanted
UIView+Dictionary
applied only to
MyCustomView
itself and not to every UIView referenced within
MyCustomView.m
, is there a way to do so (or achieve a similar effect)?

I'd like to avoid making
MyCustomView
a subclass of another custom subclass (e.g.,
MyViewWithDictionary
), as I'd ideally like to be able to import multiple categories for something akin to multiple inheritance (e.g.,
UIView+Dictionary
,
UIView+Border
,
UIView+CustomAnimations
).

In my actual own scenario, I've written a category to automatically implement a custom UINavigationBar in a view controller, but I'd like that category to apply only to the view controller into which I am importing the category and not any other view controllers that may be referenced in that file.

Any and all insights are appreciated! And I apologize in advance as I am fairly certain there are more correct terminologies for the effect described above.

Answer

As Josh pointed out, any methods added in categories are basically inert unless you call them. The issue that I was having was for generated properties and swizzled methods in categories (since, as Josh also pointed out, there are no mixins in Objective-C).

I was able to solve this by adding in a custom BOOL in my category that defaults to NO and acts as a "switch" for whatever category methods and properties I want to specify.

E.g., if I wanted my dictionary property to be lazily instantiated but only within MyCustomView, I could do the following:

// UIView+Dictionary.h

@interface UIView (Dictionary)
@property (nonatomic) BOOL enableDictionary;
@property (nonatomic, strong) NSDictionary *dictionary;
@end

// UIView+Dictionary.m

#import "UIViewController+CustomNavigationBar.h"
#import <objc/runtime.h>

@implementation UIView (Dictionary)

- (void)setEnableDictionary:(BOOL)enableDictionary {
    objc_setAssociatedObject(self, @selector(enableDictionary), @(enableDictionary), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)enableDictionary {
    NSNumber *enableDictionaryValue = objc_getAssociatedObject(self, @selector(enableDictionary));
    if (enableDictionaryValue) {
        return enableDictionaryValue.boolValue;
    }

    objc_setAssociatedObject(self, @selector(enableDictionary), @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return self.enableDictionary;
}

- (void)setDictionary:(NSDictionary *)dictionary {
    objc_setAssociatedObject(self, @selector(dictionary), dictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSDictionary *)dictionary {
    if (!self.enableDictionary) {
        return nil;
    }

    NSDictionary *dictionary = objc_getAssociatedObject(self, @selector(dictionary));
    if (dictionary) {
        return dictionary;
    }

    objc_setAssociatedObject(self, @selector(dictionary), @{}, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return self.dictionary;
}

@end

And then within -[MyCustomView viewDidLoad] I could simply call self.enableDictionary = YES. That way, only instances of MyCustomView will have a non-nil lazily instantiated NSDictionary. (Note that, in this example, all instances of UIViews will still respond to the selector @selector(dictionary), but our behavior will differ based on whether enableDictionary is YES or NO.)

While that is a trivial example, the same strategy can be used for methods that are swizzled within categories. (Again, swizzling methods within categories is probably bad form but a necessary evil in certain scenarios.)

Comments